1
\$\begingroup\$

I am creating an MVC-Framework to learn more about how PHP and software architecture works in general. My goal is to create a simple website with it.

Right now I have trouble on what exactly is the job of the bootstrap file and the FrontController Class.

Can you have a look if what I did makes sense to you?

My Bootstrap-File looks like this:

<?php

use app\core\FrontController;
use app\core\Router;

// include configuration, database and autoloader
require_once(realpath(__DIR__ .DS.'..') . "/config/config.php");
require_once(realpath(__DIR__ .DS.'..') . "/config/db.php");
require_once(realpath(__DIR__ .DS.'..'.DS.'..') . "/vendor/autoloader.php"); // autoload for the core MVC Framework

// load composer autoloader
require_once(realpath(__DIR__ .DS.'..'.DS.'..') . "/vendor/autoload.php");

// set routes
$route = isset($_GET['route']) ? $_GET['route'] : null;
$action = isset($_GET['action']) ? $_GET['action'] : null;

// get FrontController
$fc = new FrontController(new Router, $route, $action);

echo $fc->output();

And the FrontController Class

<?php

namespace app\core;

class FrontController {
    public $pdo;
    private $route, $routeName, $model, $controller, $view, $twig;

    public function __construct(Router $router, $routeName, $action = null) {
        $this->pdo = new \PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME, DB_USER, DB_PASS);

        //Fetch a route based on a name, e.g. "search" or "list" or "edit"
        $this->route = $router->getRoute($routeName);
        $this->routeName = $routeName;

        //Fetch the names of each component from the router
        $modelName = "\app\model\\".$this->route->model;
        $controllerName = "\app\controller\\".$this->route->controller;
        $viewName = "\app\\view\\".$this->route->view;

        //Instantiate each component
        $this->model = new $modelName($this->pdo);
        $this->controller = new $controllerName($this->model);
        $this->view = new $viewName($this->model);

        //Run the controller action
        if(!empty($action) && method_exists($this->controller, $action)) $this->controller->{$action}();

                // load TWIG
                $loader = new \Twig_Loader_Filesystem(realpath(__DIR__ .DS.'..'.DS.'..') . "/site/themes/".TPL_DEFAULT."/templates/"); // *!* replace TPL_DEFAULT with $theme
                $this->twig = new \Twig_Environment($loader, array(
                    'cache' => realpath(__DIR__ .DS.'..'.DS.'..') . "/cache/compilation/",
                ));
    }

    public function getRouteName() {
        return $this->routeName;
    }

    public function getModel() {
        return $this->model;
    }
    public function getController() {
        return $this->controller;
    }
    public function getView() {
        return $this->view;
    }

        public function output() {
                var_dump($this->twig);
                /* removed testing code for getting twig template on code Review SO */

        $nav = $this->view->output($this->routeName, "nav");

        var_dump($nav); // *!* test

        $page = $this->view->output($this->routeName);

        $title = $page["title"];
        $content = $page["content"];

        return $header . "<h1>" . $title . "</h1>" . $content . $footer;
    }
}

I thought maybe you could review these two files and tell me if I understood the use of them correctly.

For me it's like bootstrap is there to just import stuff like config, autoloader, FrontController. And the FrontController does the basic handling of the Framework.

I hope this code makes at least some sense. You can find the whole project here btw.

\$\endgroup\$
5
  • \$\begingroup\$ Please remove the unused code. In addition to that, I'm pretty sure that your code isn't compiling. \$\endgroup\$ Oct 13, 2017 at 12:14
  • \$\begingroup\$ I might suggest you change your title to something that is not a question: "Learning PHP MVC: bootstrapping and front-controller". Simple and gets the point across. No need to explain that it isn't twitter bootstrap. The people who are going to review this will know what you mean. Also, do check your code. If @mheinzerling is right and your code doesn't work, it will be closed as off-topic. Make sure everything works and if there are any bugs you know about fix them first. \$\endgroup\$ Oct 13, 2017 at 12:18
  • \$\begingroup\$ @mheinzerling done. Well the code works for me. why do you think it would not compile? \$\endgroup\$
    – Dollique
    Oct 13, 2017 at 12:21
  • \$\begingroup\$ Just learned that a constructor without parentheses is valid: stackoverflow.com/questions/3873111/… . Never seen that before. \$\endgroup\$ Oct 13, 2017 at 12:26
  • \$\begingroup\$ @mheinzerling indeed. I don't do it myself because it can cause that "huh?" reaction to people who have never seen it before (which is to say that I had that same reaction myself the first time I saw it), but it is valid. \$\endgroup\$ Oct 13, 2017 at 12:27

1 Answer 1

2
\$\begingroup\$

I would say you are generally on the right track but have some mixed responsibilities: there are some things in the front-controller that belong to the bootstrapping and some things in the front-controller that belong in the actual controller.

Also a major comment: I think as long as you are doing this you should take the time and also implement a dependency injection container (of some sort, even if a very simple one). DI and Inversion of Control are standards in not just the PHP MVC world but also the OOP world in general. As long as this is a learning exercise for you, you might as well learn another very critical component of such a system. No bootstrapping/front-controller would be complete without proper use of dependency injection, if for no other reason that to enable proper unit/integration testing.

Starting at the beginning then:

  1. A nitpick, but I would give your bootstrap file a namespace. If it is using things from app\core, I think it makes sense for it to live in app\core itself. Related to that: is this your index.php file, or is it included by your index.php file? Personally I split things up a bit more even, and have the bootstrap simply initialize the app (i.e. require all necessary files, initialize error catchers, etc) and let the index.php file require the bootstrap file. Then, I have the index.php file load and call the FrontController. That's certainly not a requirement, but I find it to be helpful as you might have a couple different "environments" that run differently, but still require bootstrapping (i.e. command line, testing, and responding to web requests).
  2. You have a lot of lines like this: realpath(__DIR__ .DS.'..'.DS.'..'). Many systems will define constants for these sorts of things in the bootstrap so that you don't have to repeat it so often (in particular because knowing where directories are is often needed by other parts of the system, i.e. file caching, uploads, etc...).
  3. Because you are passing the router into the FrontController, you are already halfway to dependency injection. However, you then have the FrontController directly build the database connection. Especially since the database connection isn't used by the FrontController, there is no reason for the FrontController to be building it. You make it public which implies that you intend for other things to get the database out of the FrontController, but it is not clear how they would access it, nor is this a good way of passing necessary resources back and forth. Again, a DI container would fix all of these issues, and then you can have a class that is a simple wrapper around the PDO object, and which initializes the DB connection when it is actually needed. You just have to design your DI container so that you can make your database object a "singleton" which is only created once.
  4. Your FrontController is initializing both the model and view. Ignoring issues revolving around using a DI container to instantiate resources, it doesn't make sense for the FrontController to be doing anything with models or views at all. The point of the FrontController is to act more like a directory. It takes the route, determines what controller should be handling the route, and then defers execution to the actual controller assigned to the route. The final controller is then responsible for instantiating models (if needed), calling views, etc. As a for instance, what if a view isn't needed because a particular route represents an API call which returns JSON?
  5. In essence, your biggest issue is that your front-controller is strongly coupled to the rest of your application, leaving no room for flexibility on how the application should respond to a given request. Effectively, your system is requiring one model, view, and controller for every route. It rarely works out that cleanly in practice, and so decoupling your FrontController from the rest of the application is a necessity. Again, you should think of it more as a gatekeeper than as something that actually performs any actions itself.

All in all I would say that this is a good starting point. You obviously have best-practices and design patterns in mind, and that is always a good thing. From here on out it is all about understanding and implementing good SOLID principles, in particular a good division of labor and separation of concerns between all the various actors of your system.

\$\endgroup\$
2
  • \$\begingroup\$ Thank you very much for your detailed answer. This is exactly what I was hoping for. This really helps me get the basics right. For now I will go through this step by step and see what I can do myself. Thanks! :) Should I first look into DI-Containers or first clean FrontController so it's only the gatekeeper? For what I understand the DI-Container is more important as it sets the responsibility of all files/classes. \$\endgroup\$
    – Dollique
    Oct 13, 2017 at 13:14
  • 1
    \$\begingroup\$ @Toby It's all stuff I'm sure you'll figure out in the long run, so go with whatever is most appealing to you at the moment! \$\endgroup\$ Oct 13, 2017 at 13:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.