Building your own Custom PHP Framework: Part 2

Williams Isaac
Nur: The She Code Africa Blog
8 min readMar 28, 2018

Now that we have gotten past the basic stage of building our own tiny framework, we will be working on adding more features to it to make it more usable.

Featured
Views and Components

What is a View?
A view is what your users see — simple!

It usually contains the content (template and data) for the browser to show.
Its job is only to collect data from every corner of our application and throw that to a browser to display.

Designed by Williams Isaac. :)

The only thing a view should do is to display data and not process it. I follow this set of rules and I think you should too:

My Views;
Communicate with controllers
Only display data
Always be less ambiguous
Only send data to controllers
Leave major data work to the backend.

But all those things are really not like so anymore, because building monolithic applications is rare these days.

what do you think about this typography?

A monolith application is always built as a single, autonomous unit. The problem with a monolithic architecture, though, is that all change cycles usually end up being tied to one another. A modification made to a small section of an application might require building and deploying an entirely new version. If you need to scale specific functions of an application, you may have to scale the entire application instead of just the desired components.

Since the era of javascript frameworks, what everyone does is just to build APIs for there frontends, But for now, we are trying to build a Monolithic Framework.
Read more on monolithic applications

In this tutorial, we be will using Twig as our template engine to render HTML and data passed into it.

Our folder structure should look like this:

core should contain are core files
Views will contain our view files and layouts
static will contain our css, js files. (you really should use a CDN for this. Amazon s3 is very good)

We will create a new core/app.php

If you are following from the previous post we will make minor edits to our index.php First, we will install Twig Package using composer

composer require twig/twig

After installing, Twig_Loader_Filesystem and Twig_Environment should be available for us to use.

Our core/app.php should be;

<?phpclass Init {/** Class Properties Declaration*/public $environment;public $capsule;public $controller_path;public $req_controller;public $req_model;public $view_render;public $req_param;public $req_method;public $load;/***Split server path request into Array*@return capsule;*/public function path_split($path){$this->capsule = explode('/', ltrim($path));return $this->capsule;}/***Check is url results to a trailing Slash*@return bool;*/public static function is_slash($path){/***Is path = '/'*@return true;*/if ($path == '/') {return true;}/***Is path != '/'*@return false;*/else{return false;}}public static function view($template_name, $data = array()) 
{
$loader = new Twig_Loader_Filesystem('views');$twig = new Twig_Environment($loader);$template = $twig->loadTemplate($template_name . '.html');echo $template->render($data);}}

Now we should be ready to use our view method. The question is what the heck is happening!
Twig_Loader_Filesystem lets you set the folder to load templates from.
Twig_Environment lets you load configuration for the current environment in the loader
loadTemplate allows you to load the template while render lets you import data into the template via array bindings

Our index.php file should now contain:

<?php/**---------------------------------------------------------------* CogentPHP ADS Developed by Williams Isaac*---------------------------------------------------------------**Include the neccesary Class (Init) For intialization of the System*Note:: This Index will not work without the include file set to the right path!*/require 'core/app.php';require 'vendor/autoload.php';require 'LoadCore.php';$app = new Init;define('ENVIRONMENT', 'production');/**---------------------------------------------------------------* ERROR REPORTING*---------------------------------------------------------------** Different environments will require different levels of error reporting.* By default development will show errors but testing and live will hide them.*/if (defined('ENVIRONMENT')){switch (ENVIRONMENT){case 'development':error_reporting(E_ALL);break;case 'testing':case 'production':error_reporting(0);break;default:exit('The application environment is not set correctly.');}}/***Run checks for server path*/if (isset($_SERVER['PATH_INFO'])){/***Set var $path to Predefined $_SERVER['PATH_INFO']*/$path = $_SERVER['PATH_INFO'];/***Split result or Sever path request into Array*@return $path;*/$server = $app->path_split($path);}/*Assign default '/' if previous check result to False*/else{/***Set path to '/'*/$server = '/';}switch (Init::is_slash($server)){case true:require_once __DIR__.'/Models/indexModel.php';require_once __DIR__.'/Controllers/indexController.php';$app->req_model = new IndexModel();$app->req_controller = new IndexController($app->req_model);/***Model and Controller assignment with first letter as UPPERCASE*@return Class;*/$model = $app->req_model;$controller = $app->req_controller;/***Creating an Instance of the the model and the controller each*@return Object;*/$ModelObj = new $model;$ControllerObj = new $controller($app->req_model);/***Assigning Object of Class Init to a Variable, to make it Usable*@return Method Name;*/$method = $app->req_method;/***Check if Controller Exist is not empty, then performs an*action on the method;*@return true;*/if ($app->req_method != ''){/***Outputs The Required controller and the req *method respectively*@return Required Method;*/print $ControllerObj->$method($app->req_param);}else{/***This works in only when the Url doesnt have a parameter*@return void;*/print $ControllerObj->index();}break;case false:/***Set Required Controller name ;*@return controller;**/$app->req_controller = $server[1];/***Set Required Model name*@return model;*/$app->req_model = $server[1];/***Set Required Method name*@return method;*/$app->req_method = isset($server[2])? $server[2] :'';/***Set Required Params*@return params;*/$app->req_param = array_slice($server, 3);/***Check if Controller Exist*@return void;*/$req_controller_exists = __DIR__.'/Controllers/'.$app->req_controller.'Controller.php';if (file_exists($req_controller_exists)){/***Requiring all the files needed i.e The Corresponding Model and Controller*@return corresponding class respectively;*/require_once __DIR__.'/Models/'.$app->req_model.'Model.php';require_once __DIR__.'/Controllers/'.$app->req_controller.'Controller.php';/***Model and Controller assignment with first letter as UPPERCASE*@return Class;*/$model = ucfirst($app->req_model).'Model';$controller = ucfirst($app->req_controller).'Controller';/***Creating an Instance of the the model and the controller each*@return Object;*/$ModelObj = new $model;$ControllerObj = new $controller(ucfirst($app->req_model.'Model'));/***Assigning Object of Class Init to a Variable, to make it Usable*@return Method Name;*/$method = $app->req_method;/***Check if Controller Exist is not empty, then performs an*action on the method;*@return true;*/if ($app->req_method != ''){/***Outputs The Required controller and the req *method respectively*@return Required Method;*/print $ControllerObj->$method($app->req_param);}else{/***This works in only when the Url doesnt have a parameter*@return void;*/print $ControllerObj->index();}}else{header('HTTP/1.1 404 Not Found');die('404 - The file - '.$app->req_controller.' - not found');//require the 404 controller and initiate it//Display its view}break;default:print 'An Error Occured';break;}

Changes is loading *$app = new Init; and using the path_split and is_slash method.

The path_split method helps to split parts of the url onto an array so we could match our route to the right methods

public function path_split($path) {
$this->capsule = explode('/', ltrim($path));
return $this->capsule;
}

is_slash checks if its index page so we could re-route if negative.

public static function is_slash($path) {
/**
*Is path = '/'
*@return true;
*/

if ($path == '/') {
return true;
}

/**
*Is path != '/'
*@return false;
*/

else {
return false;
}
}

Now lets create a new file View/index.html

<h1> Welcome to Index View </h1>

NOTE: Do this to make the core classes available globally

I have a bit hacky way of loading core files like so:

<?phpfunction autoloader($classname) {$lastSlash = strpos($classname, '\\') + 1;$classname = substr($classname, $lastSlash);$directory = str_replace('\\', '/', $classname);$filename = __DIR__ . '/' . $directory . '.php';require_once($filename);}spl_autoload_register('autoloader'); ?>

Change the content index method of the indexController to

/** Initializing a index.html view Found in (Views/index.html) **/Init::view('index');

we will be using localhost:5000 through this application and it can be started with built in php server php -S localhost:5000

Running the above folder in our project folder , we should have;

You can create a simple file to start the server like so

touch start.sh # create file
echo php -S localhost:5000 >> start.sh # write command to file
chmod 700 start.sh # make file executable
./start.sh # start our server/app

After all commands, your cmd should look like this

We can css, js anything to use in our index.html like a normal html file

Twig allows us to use layouts, so we will save stress of re-writing multiple things like headers and footers (partials).

view/layouts/layouts.html

<!DOCTYPE html><html><head>{% block head %}<link href="/static/css/custom.css" rel="stylesheet"/><title>{% block title %}{% endblock %} - First Lesson</title>{% endblock %}</head><body><div id="container">
<h1> This is from layout file </h1>
{% block content %}{% endblock %}</div></body></html>

Our index.html file should now be

{% extends "layouts/layouts.html" %}{% block head %}{{ parent() }}{% endblock %}{% block content %}<h3> This is from index.html <h3>{% endblock %}

We should have this;

Now we know our layout file works. hooray

Passing data to Template

Add the following method to indexController

public function data(){Init::view('main/data', array('posts' => ['post1','post2'],'comments' => ['comment1','comment2']));}

create view main/data.html

{% extends "layouts/layouts.html" %}{% block head %}{{ parent() }}{% endblock %}{% block content %}<h2> Posts </h2>{% for post in posts %}{{post}}{% endfor %}<h2> Comments </h2>{% for comment in comments %}{{comment}}{% endfor %}{% endblock %}

We should have this

You can pass any value into the view, But it has to be an array of something. It could be array of objects and can be accessed with {{[key].[property]}}

Twig is very powerful and its used in thousand of applications, read more here

You can find the code for this tutorial at

https://github.com/wilforlan/php-framework-tutorial/tree/part-2

End result of all tutorial can be found at

https://github.com/wilforlan/CogentPHP

Next Chapters

  • Adding an ORM
  • Building Scaffolders
  • Building a simple user management application

NOTE: This framework should not be used in a production environment, at least not yet. lol

For production, check

Laravel, Yii, CodeIgniter, Slim or Silex

Contact me at:

williamscalg@gmail.com

To know more about me, check https://williamsisaac.com

--

--