MVC Refactor, Take 2

Ok, here goes nothing #2. This is going to start over, explaining my idea for MVC without a router. It also entails the “what ifs” Tom threw at me like what if the format needs to be different or the data needs to come from somewhere else. So I made a parameter builder. I’ll explain later.

Ok. Again, our entry is a front controller like Symfony’s.

app.php

<?php

/*
 * Generic entry point/ front controller.    
 */

require_once('HTTPConverter.php'); #


$httpConverter = new HTTPConverter();
$request = $httpConverter->createRequest();
$response = $httpConverter->handleRequest();
$httpConverter->sendResponse();

We call HTTPConverter.php

HTTPConverter.php

<?php


/*
 * A converter to convert HTTP to a request object and handle it to get a response
 *
 */

require_once ('Request.php');
require_once ('View.php');
require_once ('Controller.php');
require_once ('Model.php');

class HTTPConverter
{

    private $request;
    private $response;
    private $controller;
    private $model;


    public function __construct()
    {

        // prepare anything necessary

    }


    public function createRequest()
    {
        // create the request object
        $this->request = new Request();

    }

    public function handleRequest()
    {
        $this->model = new Model($this->request);

        // if the request is a write request (i.e. a post, delete or put), do the controller first
        if ( $this->request->isWrite() ) {
            $this->controller = new Controller($this->request, $this->model);
        }

        // you'll always need a response, so call the view to get it.
         $this->response = new View($this->request);
    }

    public function sendResponse()
    {
        echo  $this->response->render($this->model);

    }

}

This controls our MVC entry and exit points with request and response.

Like in the diagram I presented earlier.

We need the request object.

Request.php

<?php

/*
 * Create the Request object from global variables.
 */

class Request
{
    public $object;
    public $method;
    public $path;
    public $postData;
    public $urlParams;
    public $urlParamCount;

    public function __construct()
    {
       $this->setObject();
       $this->setURLParams();
       $this->filterPostData();
       $this->setMethod();

    }

    private function setObject()
    {
        $urlElements = explode('/', $_SERVER['REQUEST_URI']);
        $this->object = $urlElements[1];
    }

    private function setURLParams()
    {
        $urlElements = explode('/', $_SERVER['REQUEST_URI']);
        // because the object is always the first element and we already define its own variable, drop it
        $this->urlParams = array_slice($urlElements, 2);
        $this->urlParamCount = count($this->urlParams);


    }


    private function filterPostData()
    {
        //no filtering yet, but you get the point
        $this->postData = $_POST;
        return $this->postData;
    }

    private function setMethod()
    {
        switch ($_SERVER['REQUEST_METHOD']) {
            case 'POST':
                $this->method = 'update';
                break;

            case 'PUT':
                $this->method = 'create';
                break;

            case 'DELETE':
                $this->method = 'delete';
                break;

            case 'GET' :
                $this->method = 'read';
                break;

            default: 'read';
        }
    }

    public function isWrite()
    {

        return $this->method == 'update' OR $this->method == 'create' OR $this->method =='delete' ? true : false;

    }

    public function isRead()
    {
        return $this->method == 'read'? true : false;
    }

}

I added some logic to create the urlParams. They will be decisive in the later logic.

Now our first call to our application is only a read, so let’s show the view only.

View.php

<?php

/*
 * View class for creating an output
 *
 */

class View {

	private $template;
	private $model;
        public $headers;


	public function __construct(Request $request)
        {
            $this->template = $request->object . ".html.php";
	}

	public function render($model)
	{
            $headers = [];
            ob_start();
            require ( "./templates/".$this->template );

            return  ob_get_clean();
	}
}

In our HTTPConverter, we had first called on the initial model, which is all we need to render.

We have currently two choices (the parameter mapping needs to be expanded for more flexibility I know, but humor me on this). We can just call “/clock” or we can now call “/clock/ntp/date-only”. The initial result is the same.

Now we make a selection and need to go through a controller for the update and update the model.

Controller.php

<?php

/*
 * Our very, very, very thin controller! The thinnest controller ever!!!
 *
 */

class Controller {

	private $model;
        private $request;

	public function __construct(Request $request, Model $model) {
                $this->request = $request;
		$this->model = $model;
                $this->decideAction();
	}


        private function decideAction()
        {
            switch ($this->request->method){
                case "create":
                    $this->model->create();
                    break;
                case "update":
                    $this->model->update();
                    break;
                case "delete":
                    $this->model->delete();
                    break;
            }

        }

}

Here is our model.

Model.php

<?php

/*
 * A class for our model (building)
 *
 */

require_once('ClientModelFactory.php');



class Model {

	public $violations = [];
        private $request;
        public $clientModel;

        public function __construct(Request $request)
        {
            $this->request = $request;
            $this->buildClientModel();

        }

        public function buildClientModel()
        {
            $this->clientModel = ClientModelFactory::build($this->request);
            return $this->clientModel;
        }

	public function create()
	{
	    // create something new
	}

        public function update()
	{
	    $this->clientModel->update($this->request->postData);
	}

        public function delete()
        {
            // delete somthing
        }

}

And the ClientModelFactor.php

<?php

/*
 * A stimple factory to build our clientModels
 *
 */


class ClientModelFactory
{
    public static function build(Request $request)
    {
        $class = (ucfirst($request->object))."Model";
        require_once ("models/". $class .".php");
        if (class_exists($class)) {
            return new $class($request);
        }
        else {
            throw new Exception("Invalid object given.");
        }
    }
}

The trick now is the added logic to the client’s ClockModel.php, where the client creates all the necessary logic. This is actually where I want the parameters (the configuration) too, as it is ALL in one place. No crazy config files needed really. But, we can discuss the need, as there will be some for sure. At any rate, what was missing in my above example was the client’s model.

ClockModel.php

<?php

/*
 * Our clock model class
 *
 */

require_once('ObjectParameters.php');


class ClockModel {

    private $request;
    public $timezones = ['Europe/London' => 'London',
                          'America/New_York' => 'New York',
                          'America/Los_Angeles' => 'Los Angeles'];

    // this is the URL structure. The first elements is always the object, everything after the object will be your object parameters
    // we'll need to come up with a more flexible method later
    public $urlPathMap = '/clock/dataSource/dateFormat';

    // since you have them in your path map above, you must also declare the variable defaults! This should also be automated!
    public $objectParams = ['dataSource' => 'default',
                                   'dateFormat' => 'default'];
    public $timezone;
    public $submitted = false;
    public $time;

    public function __construct(Request $request)
    {
        $this->request = $request;
        $this->getObjectParams();
    }

    private function getObjectParams()
    {
        $objectParameters = new ObjectParameters($this->request, $this->urlPathMap, $this->objectParams);
        $this->objectParams = $objectParameters->parameters;
    }

    public function create()
    {
        // create something new
    }

    public function update($postData)
    {
        if ( $this->isValid($postData) ) {
            $this->submitted = true;
            $this->timezone = $postData['timezone'];
        }
    }

    public function delete()
    {
        // delete something
    }

    public function isValid($post)
    {
        return (null !== isset($post['timezone'])) ? true : false;
    }

    public function getTime()
    {   //var_dump($this->objectParams->parameters);
         if ( $this->objectParams['dataSource'] === 'default' ) {
            $timezone = new DateTimeZone($this->timezone);
            $date = new DateTime;
            $date->setTimeZone($timezone);
            return $date;
        }elseif (  $this->objectParams['dataSource'] === 'ntp' ){
            $date = $this->getNTPTime();
            return $date;
        }
    }

    public function getNTPTime()
    {
        // go and get the time from some NTP server (for now just do the same as default as an example)
        $timezone = new DateTimeZone($this->timezone);
        $date = new DateTime;
        $date->setTimeZone($timezone);
        return $date;
    }

    public function getCity()
    {
        return $this->timezones[$this->timezone];
    }

    public function success()
    {
            $this->submitted = true;
    }
}

I realize this client model is getting very fat. But, it wouldn’t be at all hard to refactor and make some new sub-objects, like for the different tasks, like UpdateClockModel, DeleteClockModel, CreateClockModel.

The new part I’ve added it the Object Parameters object.

ObjectParameters.php

<?php

/*
 * This is where we setup the object parameters.
 */

class ObjectParameters
{
    public $parameters = array();
    private $request;
    private $clientURLPathMap;
    private $parameterKeys;
    public $defaultObjectParameters;

    public function __construct(Request $request, $urlPathMap, $defaulObjectParameters )
    {
        $this->request = $request;
        $this->defaultObjectParameters = $defaulObjectParameters;
        $this->clientURLPathMap = $urlPathMap;
        $this->setParameterKeys();
        $this->buildParameters();
    }

    private function setParameterKeys()
    {
         $mapElements = explode( '/',$this->clientURLPathMap);
         $mapLength = count($mapElements);
         $this->parameterKeys = array_slice($mapElements, 2, $mapLength - 1);

    }

    public function buildParameters()
    {
        // this is in flexible, as we can't set up for different parameter needs. Need to refactor.
        if ( $this->request->isRead() AND $this->request->urlParamCount === count($this->parameterKeys) ) {
            for($i=0; $i < $this->request->urlParamCount; $i++) {
                $this->parameters[$this->parameterKeys[$i]] = $this->request->urlParams[$i];
            }
       }else{
           $this->parameters = $this->defaultObjectParameters;
       }

       // this is ugly. Refactor later. But, needed to set up hidden fields for our parameters.
       if ( $this->request->isWrite() ) {
           foreach( $this->request->postData as $key => $value ) {
               foreach ( $this->parameterKeys as $parameterKey ) {
                   if ( $key === $parameterKey ) {
                       $this->parameters[$parameterKey] = $value;
                   }
               }
           }
       }
    }

}

I am not too proud of this class, as I know it is mofugly code. But, I hope it gets the point across at what I am attempting to accomplish.

Edit: Of course, it also works.

:blush:

Scott