It seems like you are using an ad blocker. To enhance your experience and support our website, please consider:

  1. Signing in to disable ads on our site. Our users don't see ads
  2. Or, Sign up if you don't have an account yet
  3. Or, disable your adblocker by doing this:
    • Click on the adblocker icon in your browser's toolbar.
    • Select "Pause on this site" or a similar option for lancecourse.com.

PHP Frameworking - Composer & Controllers (Part 3)

By zooboole

Welcome to third part of PHP Frameworking tutorial, today we are going to implement advanced autoloading mechanism to our framework, via composer. We'll introduce controllers, and upgrade our router to support them.

Starting with this tutorial, code base of the framework is available here on github.

Composer - autoloading(and more) the proper way

In last episodes of tutorial we've been using custom autoloading mechanism, but from now on we will standardize the way we autoload! Linux/OS X users can install using this tutorial, Windows users click here.

Composer is a brilliant tool which helps us with autoloading, dependency management and such. To get started let's create a file to describe what we want from composer.

composer.json:

{
    "autoload":{
        "psr-4":{
            "lancecourse\\core\\": "./core"
        }
    }
}

By the file above we are telling composer that we would like to autoload our files by psr-4 standart. Here is an example, let's autoload our router, first we need to rename router.php -> Router.php, because our class starts with an uppercase R, and composer requires a sort of standart pattern to follow (filename = classname). In composer.json we've specified that requesting a class with namespace lancecourse\core, will result into loading a file from /core/ClassName.php. In our case we want to load in router, which is in core/router/Router.php file, but to make that possible we need to add a namespace to Router source file.

core/router/Router.php:

<?php

namespace lancecourse\core\router;

class Router{
// class continues as in previous tutorial

Almost ready to apply composer autoloading to our bootstrap file, but first we need to let composer install files which are required to run our configuration specified above in composer manifest. Folder vendor will be craeted with all the neccessary files. To achieve that open your terminal and run the following:

$ composer install 

Command above works if you have composer installed globally, otherwise please look up on official composer site, how to properly install from composer.json.

Now let's apply our new autoloading mechanism:

bootstrap.php:

<?php

require('config.php');
require('vendor/autoload.php');

$router = new lancecourse\core\router\Router();

That's for Router, now let's rename both core/view/view.php and core/view/viewLoader.php to match their class names, and insert the namespace as shown for router (lancecourse\core\view).

bootstrap.php:

<?php

require('config.php');
require('vendor/autoload.php');

$router = new lancecourse\core\router\Router();
$view = new lancecourse\core\view\View(
    new lancecourse\core\view\ViewLoader(BASEPATH.'/views/')
);

Now everything should be working as expected with the rest of framework!

public/index.php:

<?php

require('../bootstrap.php');
require('../routes.php');

$router->dispatch();

Code above from previous tutorial should remain untouched, and work properly after latest edits.

Controlling application flow

In previous part we've used anonymous functions (closures) to respond on specific URL requests, now it's time to shift this part of application logic into separate files, and teach our router how to open and use those files. First create a folder controllers, and head to composer.json to teach our framework how to autoload controllers.

composer.json:

{
    "autoload":{
        "psr-4":{
            "lancecourse\\core\\": "./core",
            "app\\controllers\\": "./controllers"
        }
    }
}

And don't forget to run composer install again in order to get app\controllers namespace available for autoloading. Let's create our first controller now.

controllers/IndexController.php:

<?php

namespace app\controllers;

class IndexController{

    public function index(){
        echo "welcome to my site";
    }
}

And verify that everything works as expected by instantiating that controller.

public/index.php:

<?php

require('../bootstrap.php');
require('../routes.php');

$router->dispatch();

$indexController = new app\controllers\IndexController();
$indexController->index();

Instead of writin simple echo statement, we would like to use our view classes. Since only controllers will be manipulating with views, let's create a View instance in a shared controller class.

controllers/BaseController.php:

<?php

namespace app\controllers;

class BaseController{

    public function __construct(){
        $this->view = new \lancecourse\core\view\View(
            new \lancecourse\core\view\ViewLoader(BASEPATH.'/views/')
        );
    }
}

Do not forget to remove $view from bootstrap.php, it's no longer needed there. Now we can use View class inside our controller!

controllers/IndexController.php:

<?php

namespace app\controllers;

class IndexController extends BaseController{

    public function index(){
        $this->view->display('hello.php');
    }
}

public/index.php:

<?php

require('../bootstrap.php');
require('../routes.php');

$indexController = new app\controllers\IndexController();
$indexController->index();

Now it's time to teach our router how to handle both closures and controllers.

core/router/Router.php:

<?php

namespace lancecourse\core\router;

class Router{

    private $routes = [];
    private $notFound;

    public function __construct(){
        $this->notFound = function($url){
            echo "404 - $url was not found!";
        };
    }

    public function add($url, $action){
        $this->routes[$url] = $action;
    }

    public function setNotFound($action){
        $this->notFound = $action;
    }

    public function dispatch(){
        foreach ($this->routes as $url => $action) {
            if( $url == $_SERVER['REQUEST_URI'] ){
                if(is_callable($action)) return $action();

                $actionArr = explode('#', $action);
                $controller = 'app\\controllers\\'.$actionArr[0];
                $method = $actionArr[1];

                return (new $controller)->$method();
            }
        }
        call_user_func_array($this->notFound,[$_SERVER['REQUEST_URI']]);
    }
}

Notice changes in dispatch() method, which now allow us to do the following:

routes.php:

$router->add('/','IndexController#index');

Thanks for reading

I hope you have enjoyed this extensive part of our frameworking tutorial, please leave me a comment, or message me on facebook!

Last updated 2024-01-11 UTC