HiveBrain v1.2.0
Get Started
← Back to all entries
patternphpMinor

Building a model-view-controller application in PHP

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
applicationphpbuildingviewcontrollermodel

Problem

I've seen at least two recent PHP questions that would do well from a Model-View-Controller ('MVC' from here-on-out in this question) setup. Now, me, being the horrible person I am, I wanted to build one because it's been a while since I've done any actual PHP work, and as much trash as I have been known to talk about it it's still a very popular and, to be fair, very easy language to use.

So, I've built an MVC structure in it. It's very basic, but it does exactly what an MVC application should. Separation of view from code.

It all starts with the .htaccess file, this file basically allows the use of /Controller/Action/Id?QueryString structured URL's, so I feel it's a good starting place:

RewriteEngine on
# Rewrite /Controller as /Controller/Index
RewriteRule ^([a-zA-Z0-9]+)/?$ index.php?Controller=$1&Action=Index [L,QSA]
# Rerwite /Controller/Action(/Id?Querystring)
RewriteRule ^([a-zA-Z0-9]+)/([a-zA-Z0-9]+)/?(/([a-zA-Z0-9]+)/?)?$ index.php?Controller=$1&Action=$2&Id=$4 [L,QSA]


This is pretty self-explanatory, and it should make sense.

The next step is directing the user to index.php. This is where magic starts happening, and this is where we start making actual MVC decisions:

$methodModelMethod();

        $methodModel = new $methodModelName();

        foreach (get_object_vars($methodModel) as $var => $value) {
            $methodModel->$var = $_POST[$var];
        }

        echo $controller->$action($methodModel);
    } else if ($requestType === "GET") {
        echo $controller->$action($id);
    } else {
        die("HTTP request type '{$requestType}' is unsupported.");
    }
?>


Nothing here needs to be modified to use the MVC system, this is the landing so that index.php can take all the work and send it away.

This next controller is an example, because I want to demonstrate how easy this MVC system is to use:

```
Title = "Test";
$model->Message = "Some awesome message";
$model->Items = $this->items;

Solution

I would consider moving your routing definition logic out of .htaccess and into your PHP code. I think you will find it much easier over the long haul to maintain your logic solely in PHP vs. being split between PHP and Apache.

This could leave you with a simple front controller redirect in Apache to index.php, with PHP then inspecting the request for routing.

You may consider autoloading your model/view/controller classes rather than loading everything for every request. Consider PSR-4 autloading standards (there are example implementation available as well here https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader-examples.md

This would have you also introducing namespacing in your classes, something you are not currently doing.

You should consider building a class to represent the request, so you don't need to be arbitrarily doing things like determining URI in your HTML class. You would instead inject the Request dependency to any code (like router, controller, view, etc. that need it).

Combining this with the thought of having a PHP Routing class, you could be simplifying your main index page down to something like this (shown without error handling):

// require your application bootstrapping file
// here you specify config, autoloading, session handling, error/exception handling, etc.
require(...);

// set up request object based on request
$request = new Request();

// perhaps load route configuration from a file somewhere
Router::loadConfig();

// route to controller based on Request
Router::route($request);


That is really all you need in your index file.

You then have the router:

  • determine the appropriate controller based on URI and HTTP action (GET, POST, PUT, DELETE, HEAD, etc.);



  • instantiate the appropriate controller;



  • pass the request and execution control to the controller.



So in Router class the key methods may look like:

public static route(Request $request) {
    $controller = Router::getController($request);
    $controller->execute();
}

protected static getController(Request $request) {
    // here the resolve controller method could either return an appropriate controller
    // or specify an error controller if no route is found
    $controllerName = Router::resolveController($request);
    return new {$controllerName}($request);
}

protected static resolveController(Request $request) {
    // maybe Request class has method to determine the requested resource
    // from URI
    $resource = $request->getRequestedResource();
    $httpAction = $request->getHttpAction();

    // not shown - logic here to determine controller name based on resource
    // named in URI and HTTP action as compared against route configuration
    return $controllerName;
}


The main thing here is that you are driving towards a granular approach to handling routing, controller instantiation, etc. with each method doing one specific thing, rather than an intermixing of responsibilities as is done in your current index file.

I don't think you have clean separation between your controllers, models, and views. Your controller for example should focus solely on marshalling the appropriate data model to be rendered and then passing that data to the view for rendering. Right now, your controller is actually populating that data into the view. And doing really inappropriate things like evaling view related code.

Extending my example from above, the key controller methods may look like:

public function execute() {
    // get data model based on action which the controller can determine
    // based on the fact it has stored the request
    $data = $this->executeAction();
    // instantiate a view object, handing it the request and model info
    $view = $this->getView();

    // pass data from model and execution off to view object.
    $view->render($data);
}

protected function executeAction() {
    // the class inheriting the base controller would typically
    // specify the model to be worked with so we can instantiate it.
    $model = $this->getModel();
    // now, let's get the method name based on the info from request
    // let's assume you can get URI from request
    $uri = $this->request->getUri();
    // and that the inheriting controller can resolve the URI
    // into an appropriate method name to call on model
    $method = $this->resolveMethod($uri);

    // now call the model, along with request (which still holds query string
    // post data, etc. that may be needed by the model).
    return $model->{$method}($this->request);
}

protected function getView() {
    // not shown
    // determine view based on request settings (i.e. do we need to return HTML,
    //  JSON, XML, etc.)
    // the inheriting controller class would then specify the specific
    // view class to be initialized
    return new {$view}();
}


Why the need for eval() here? You should be able to design this away.

Once you get to the point of calling

Code Snippets

// require your application bootstrapping file
// here you specify config, autoloading, session handling, error/exception handling, etc.
require(...);

// set up request object based on request
$request = new Request();

// perhaps load route configuration from a file somewhere
Router::loadConfig();

// route to controller based on Request
Router::route($request);
public static route(Request $request) {
    $controller = Router::getController($request);
    $controller->execute();
}

protected static getController(Request $request) {
    // here the resolve controller method could either return an appropriate controller
    // or specify an error controller if no route is found
    $controllerName = Router::resolveController($request);
    return new {$controllerName}($request);
}

protected static resolveController(Request $request) {
    // maybe Request class has method to determine the requested resource
    // from URI
    $resource = $request->getRequestedResource();
    $httpAction = $request->getHttpAction();

    // not shown - logic here to determine controller name based on resource
    // named in URI and HTTP action as compared against route configuration
    return $controllerName;
}
public function execute() {
    // get data model based on action which the controller can determine
    // based on the fact it has stored the request
    $data = $this->executeAction();
    // instantiate a view object, handing it the request and model info
    $view = $this->getView();

    // pass data from model and execution off to view object.
    $view->render($data);
}

protected function executeAction() {
    // the class inheriting the base controller would typically
    // specify the model to be worked with so we can instantiate it.
    $model = $this->getModel();
    // now, let's get the method name based on the info from request
    // let's assume you can get URI from request
    $uri = $this->request->getUri();
    // and that the inheriting controller can resolve the URI
    // into an appropriate method name to call on model
    $method = $this->resolveMethod($uri);

    // now call the model, along with request (which still holds query string
    // post data, etc. that may be needed by the model).
    return $model->{$method}($this->request);
}

protected function getView() {
    // not shown
    // determine view based on request settings (i.e. do we need to return HTML,
    //  JSON, XML, etc.)
    // the inheriting controller class would then specify the specific
    // view class to be initialized
    return new {$view}();
}

Context

StackExchange Code Review Q#154778, answer score: 5

Revisions (0)

No revisions yet.