Welcome to second part of PHP Frameworking tutorial (Part 1 here), today we are going to learn how to respond and display content based on the request url, and also how to autoload our scripts (less require statements!).
Separate configuration
So far we only needed to define $viewPath, but from now on we will create more and more configurable options. We need a place where to put those options, and a place where to run initial scripts for our framework. Let's create a configuration, and a bootstrap file.
config.php:
<?php
define('BASEPATH', __DIR__);
bootstrap.php:
<?php
require('config.php');
require('core/view/viewLoader.php');
require('core/view/view.php');
$viewLoader = new ViewLoader(BASEPATH.'/views/');
$view = new View($viewLoader);
public/index.php:
<?php
require('../bootstrap.php');
$view->display('hello.php');
We have moved require calls from index file to bootstrap file, which separates the logic a bit, same as BASEPATH has been declared inside config.php.
Why should i care about autoloading?
In previous tutorial we have created just two library files we needed to include for our framework to work. Today we are going to create more and more files, and taking care of multiple require statements can get overhelming. To avoid writing tons of require statements, PHP provides us convinient way to load in files which are required to run the script properly. At begining we will use our own solution for autoloading, and later on we will implement standardized way (composer) to autoload files. Let's start by creating Autoload class:
core/autoload/autoload.php:
<?php
class Autoload{
private $autoloadable = [];
public function register($name, $loader = false){
if( is_callable($loader) || $loader == false){
$this->autoloadable[$name] = $loader;
return;
}
throw new Exception('Loader must be callable '.$name);
}
public function load($name){
$name = strtolower($name);
$filepath = BASEPATH.'/core/'.$name.'/'.$name.'.php';
if( !empty($this->autoloadable[$name]) ){
return $this->autoloadable[$name]($name);
}
if( file_exists($filepath) ){
return require($filepath);
}
throw new Exception($name.' is not loaded or registred for autoloading');
}
}
Method register enables us to register a class to be loaded using a custom loader, which is a function called when class is required to be loaded. Load method is called by spl_autoload_register function, which is shown below. Everytime when we request a class which is not yet available for us, spl_autoload_register calls our load method in order to load the requested class. If we haven't registred the class which is requested to be loaded, autoloader attempts to load it by following a basic standart: core/classname/classname.php
Now with our primitive autoloading class, we can adjust our bootstrap file to autoload our files.
bootstrap.php:
<?php
require('config.php');
require('core/autoload/autoload.php');
$autoloader = new Autoload();
spl_autoload_register([$autoloader, 'load']);
$autoloader->register('viewloader', function(){
return require(BASEPATH.'/core/view/viewLoader.php');
});
$view = new View( new ViewLoader(BASEPATH.'/views/') );
As we can see, both View & ViewLoader are being lazyloaded (loaded on demand), so we don't have to call require everytime we want to use them. They get loaded either by standart (core/...) or by registred loader ($autoloader->register...)
Now we have basic autoloader ready to be used, as well separate files for bootstraping and configuration.
Responding to different URLs
Browsing the web you may notice different paths after domain names. Let it be /contact-us, /about-us or /article/amazing-article-name. At the moment our framework shows the same reponse, without minding request URL. When you attempt to load a site, in our case when you attempt to load adress of your local server where the framework is running, browser will send a request, to the server, and request will hold an information about which URL you want to see, and we need to take advantage of that!
core/router/router.php:
<?php
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'] ){
return $action();
}
}
call_user_func_array($this->notFound,[$_SERVER['REQUEST_URI']]);
}
}
Router class holds an array of routes which our site can respond to properly, and also holds a 404 callback, in case we do not have route registred for a proper response. To add a new available route, we have to use add method, passing in url, and a closure as a callback. Route callback get's executed whenever route url get's matched to current request URL. Finally we have out dispatch method, which takes current request url, and attempts to find a match in routes array, if found, calls it's callback, if any of routes does not have required URL, notFound callback is executed. 404 callback can be modified by calling setNotFound method and passing a respective callback to it.
Let's add router to bootstrap file.
bootstrap.php:
<?php
require('config.php');
require('core/autoload/autoload.php');
$autoloader = new Autoload();
spl_autoload_register([$autoloader, 'load']);
$autoloader->register('viewloader', function(){
return require(BASEPATH.'/core/view/viewLoader.php');
});
$view = new View( new ViewLoader(BASEPATH.'/views/') );
$router = new Router();
Now we can apply our router in index file.
public/index.php:
<?php
require('../bootstrap.php');
$router->add('/',function() use ($view){
$view->display('hello.php');
});
$router->add('/about-us',function() use ($view){
$view->display('about.php');
});
$router->dispatch();
As you may see, we are registering two routes, / & /about-us, both of them have their own callbacks, which are executed once their URLs are requested by client. Final step is to register routes in a separate file.
routes.php:
<?php
$router->add('/',function() use ($view){
$view->display('hello.php');
});
$router->add('/about-us',function() use ($view){
$view->display('about.php');
});
public/index.php:
<?php
require('../bootstrap.php');
require('../routes.php');
$router->dispatch();
Thanks for reading
In next tutorial we will revise what we have learned so far, and upgrade our framework to match latest autoloading standarts, also we will add advanced router functionality, such as URL variables or implement more sophisticated & advanced object-oriented approach to code we've written.
I hope you have enjoyed PHP Frameworking tutorial so far, if you have any question or concerns, leave me a comment down below, or contact me via Facebook or Twitter.
Last updated 2024-01-11 UTC