Building your own Custom PHP Framework: Part 1

PHP has been the language of the web for a very long time due to its easy of learning, community, being free and open source and easy of deployment. This tutorial is written in part as we have a long way to go.You can find the code for this tutorial at :

End result of all tutorial can be found at : https://github.com/wilforlan/CogentPHP

Sections

- Who is this tutorial for

- What do I need to know

- What do I need to do differently

- Building

Who is this tutorial for ?

Really, its for engineers who wants to understand how things work under the hood, the one that wants to break, fix and improve the ways things work.

I really like it when I know that an engineer makes a move to improve how things work

What do you need to know ?

Building modern apps using Laravel, Yii, Silex et al makes a lot of sense as they are built by very intelligent and brilliant engineers, but who says you can’t do better ???

Before diving into the day’s business, there are a couple of things we need to understand.

  1. OOP (Object Oriented Programming)
  2. MVC (Model View Controller)

OOP (Object Oriented Programming)

Being Object Oriented simply means that you see everything as a simple object. To make it more simpler, lets see each object as a human, every being has something in common with other human which we believe is the reason why we can refer to another person as human. For example:

Characteristics of a human

Body — Skin — Head — Hair — Face

The above mentioned properties are just picked and might not be all, but we have a base to work with. So every human all of this properties before they can be called human, and if any is missing, then its not a human.

One thing we notice is that everyone has a skin, but different skin color, head but different shape, body and face but different color and shape, hair but different color and length, but some properties can be tagged private, public or protected e.g the brain

OOP is a concept that is generic to languages that supports it. E.g Java, C++,PHP e.t.c In PHP, we need classes to define our objects and each classes has properties and methods. Lets have a little sample to explain our concept.

We will be using an example of a car. Basic properties of cars; wheels, color, type or model. It also has actions such has; accelerate, stop e.t.c. There are also some methods/actions that are not usable until some

<?php
class Car {
// Defining default class properties
public $wheels = 4;
public $color = 'red';
public $model = 'Toyota';
public function setColor($color){
$this->color = $color;
}
public function setWheels($wheels){
$this->wheels = $wheels;
}
public function setModel($model){
$this->model = $model;
}

}
// We can now create our new objects
$default = new Car();
// We view our properties
echo $default->wheels; // 4
echo $default->color; // red
echo $default->model; // Toyota
$newCar = new Car();
$newCar->setColor('Blue');
$newCar->setWheels('6');
$newCar->setModel('Benz');
echo $newCar->wheels; // 6
echo $newCar->color; // Blue
echo $newCar->model; // Benz
?>

You can view a result of this snippet at http://codepad.org/6VA0ril9

Now we have our sample that demonstrate what OOP looks like. At the end of this tutorial, there are links to resources that gives more in-depth explanation to this concepts.

What really is MVC

MVC Diagram

The above diagram shows in a brief what MVC Means. Separation Of Concern (Soc) Rule applied to MVC, where each component has a particular purpose.

In MVC, (Model-View-Controller), the view is what the users see and interact with, the data holder is the model and the logic/processor is the Controller.

The controller usually controls the logic, calculations and manipulations in the system while the model holds the result of the manipulations then feed the view with the result to display.

Giving an instance of a a page that displays data and accepts user inputs, the model handles the storage of data (Create and Read Methods), the Controller handles the manipulation of the data gotten from the model and the view displays the information. The view can also send some data back to the controller as a feedback/response, then the controller process it and run a create method in the model to save the response. One thing that we need to know well is that the Model should have no influence or knowledge of how this data is been used.

Building

Every framework usually has an entry point that handles all incoming requests and match then with the corresponding components and handlers.

That diagram show how the application receives impulses from the server and how its being dispatched to the rest of the application components.

Project Folder Structure

  • Controllers
  • Models
  • index.php
  • loadcore.php

Firstly, we will need to know how we want to handle our routing and match the request to the respective controllers and methods.

Controllers are actually classes that extends a base class somewhere in the code and the methods will be matched with out route properties.

Example

- accountController.php
<?php
/** Autoloading The required Classes **/
use Core\Core\C_Base;
use Core\Core\Redirect;
class IndexController extends C_Base
{
function __construct( $tile )
{
/** Loading the corresponding Model class **/
$this->model = new $tile;
}
public function index()
{
/** Initializing a index.html view Found in (Views/index.html) **/
Init::view('index');
}
public function pay()
{
/** Initializing a index.html view Found in (Views/index.html) **/
Init::view('index');
}
public function withdraw()
{
/** Initializing a index.html view Found in (Views/index.html) **/
Init::view('index');
}
}
?>

To generate URL from the Above controller, We have

localhost/account/
The URL Maps to accountController -> index()
Since no Method is Specified in URL, It maps it to index method by default.
localhost/account/pay
This URL Maps to accountController -> pay()
localhost/account/withdraw
This URL Maps to accountController -> withdraw()
localhost/account/begin
This results in a 404 error, Because the method begin() doesn't exist in the Class
The URL is Case Insensitive as:
accountController -> withdrawMoney() ==
1. localhost/account/withdrawmoney
2. localhost/account/withdrawMoney
They Both Evaluate to the same Method

Firstly, we need to re-route all our request to index.php to handle, so we need a htaccess file

.htaccess file should contain this
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.+)$ index.php/$1 [L]

In case you need to read up what a .htaccess file check http://www.htaccess-guide.com/how-to-use-htaccess/

Then we can now have an index.php file to handle our request.

Lets try a simple echo

<?php
echo "<h1>Served Index file</h1>";
?>

We’d be using this command to serve our app.

php -S localhost:5000
Endpoint should be localhost:5000

Should show “Served Index File Regardless of the address we enter.

Image showing result of index.php

One important thing is that we only need the controller, methods and route params so we can import required files.

<?php
echo "<h1>Served Index file</h1>";
/** Get Server Path
* For http://localhost:5000/user/login, We get
* /user/login
**/
echo "Server Path </br>";
$path= $_SERVER['PATH_INFO'];
print_r($path);
// Then we split the path to get the corresponding controller and method to work with
echo "<br/><br/>Path Split<br/>";
print_r(explode('/', ltrim($path)));
/** Then we have our controller name has [1]
* method name as [2]
**/
// We now need to match controllers and methods
?>

So we start by creating matches for index route.

Create the file

Controllers/indexController.php

<?php
/** Autoloading The required Classes **/
class IndexController {
private $model;
function __construct( $tile )
{
/** Loading the corresponding Model class **/
$this->model = new $tile;
}
public function index()
{
return "Index Method";
}
public function login()
{
echo "Login Method";
}
}
?>

Models/indexModel.php

<?php
/** Autoloading The required Classes **/
class IndexModel
{
function __construct(){
}
}
?>

We than need to update our index.php file

index.php

<?php
// Check if path is available or not empty
if(isset($_SERVER['PATH_INFO'])){
$path= $_SERVER['PATH_INFO'];
// Do a path split
$path_split = explode('/', ltrim($path));
}else{
// Set Path to '/'
$path_split = '/';
}
if($path_split === '/'){
/* match with index route
*   Import IndexController and match requested method with it
*/
require_once __DIR__.'/Models/indexModel.php';
require_once __DIR__.'/Controllers/indexController.php';
$req_model = new IndexModel();
$req_controller = new IndexController($req_model);
/**
*Model and Controller assignment with first letter as UPPERCASE
*@return Class;
*/
$model = $req_model;
$controller = $req_controller;
/**
*Creating an Instance of the the model and the controller each
*@return Object;
*/
$ModelObj = new $model;
$ControllerObj = new $controller($req_model);
/**
*Assigning Object of Class Init to a Variable, to make it Usable
*@return Method Name;
*/
$method = $req_method;
/**
*Check if Controller Exist is not empty, then performs an
*action on the method;
*@return true;
*/
if ($req_method != '')
{
/**
*Outputs The Required controller and the req *method respectively
*@return Required Method;
*/
print $ControllerObj->$method($req_param);
}
else
{
/**
*This works in only when the Url doesnt have a parameter
*/
print $ControllerObj->index();
}
}else{
// Controllers other than Index Will be handled here
}
?>

The Code contains comments to explain what statement functions are.

localhost:5000 resolves to default index route

Now we need to match other routes as intended.

Than we update our index.php with

<?php
// Check if path is available or not empty
if(isset($_SERVER['PATH_INFO'])){
$path= $_SERVER['PATH_INFO'];
// Do a path split
$path_split = explode('/', ltrim($path));
}else{
// Set Path to '/'
$path_split = '/';
}
if($path_split === '/'){
/* match with index route
*   Import IndexController and match requested method with it
*/
require_once __DIR__.'/Models/indexModel.php';
require_once __DIR__.'/Controllers/indexController.php';
$req_model = new IndexModel();
$req_controller = new IndexController($req_model);
/**
*Model and Controller assignment with first letter as UPPERCASE
*@return Class;
*/
$model = $req_model;
$controller = $req_controller;
/**
*Creating an Instance of the the model and the controller each
*@return Object;
*/
$ModelObj = new $model;
$ControllerObj = new $controller($req_model);
/**
*Assigning Object of Class Init to a Variable, to make it Usable
*@return Method Name;
*/
$method = $req_method;
/**
*Check if Controller Exist is not empty, then performs an
*action on the method;
*@return true;
*/
if ($req_method != '')
{
/**
*Outputs The Required controller and the req *method respectively
*@return Required Method;
*/
print $ControllerObj->$method($req_param);
}
else
{
/**
*This works in only when the Url doesnt have a parameter
*@return void;
*/
print $ControllerObj->index();
}
}else{
// fetch corresponding controller
$req_controller = $path_split[1];
/**
*Set Required Model name
*@return model;
*/
$req_model = $path_split[1];
/**
*Set Required Method name
*@return method;
*/
$req_method = isset($path_split[2])? $path_split[2] :'';
/**
*Set Required Params
*@return params;
*/
$req_param = array_slice($path_split, 3);
/**
*Check if Controller Exist
*@return void;
*/
$req_controller_exists = __DIR__.'/Controllers/'.$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/'.$req_model.'Model.php';
require_once __DIR__.'/Controllers/'.$req_controller.'Controller.php';
/**
*Model and Controller assignment with first letter as UPPERCASE
*@return Class;
*/
$model = ucfirst($req_model).'Model';
$controller = ucfirst($req_controller).'Controller';
/**
*Creating an Instance of the the model and the controller each
*@return Object;
*/
$ModelObj = new $model;
$ControllerObj = new $controller(ucfirst($req_model.'Model'));
/**
*Assigning Object of Class Init to a Variable, to make it Usable
*@return Method Name;
*/
$method = $req_method;
/**
*Check if Controller Exist is not empty, then performs an
*action on the method;
*@return true;
*/
if ($req_method != '')
{
/**
*Outputs The Required controller and the req *method respectively
*@return Required Method;
*/
print $ControllerObj->$method($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
}
}
?>

We can now create new controllers and matching methods.

Using our tiny framework.

Connecting Models to Controllers.

You will notice that we passed the model object to the controller construct which makes it easier for us to access the methods that belongs to the model.

$ModelObj = new $model;
$ControllerObj = new $controller(ucfirst($req_model.'Model'));

With this we have:

function __construct( $tile )
{
/** Loading the corresponding Model class **/
$this->model = new $tile;
}

Which allows us to use

$this->model

So every method in the model is available in the controller.

Add this snippet to the indexModel.php file

public function getUsers(){
$users = [
["name" => "Williams Isaac", "Phone Number" => "090982xxxxxx"],
["name" => "Oji Mike", "Phone Number"=> "080982xxxxxx"]
];
return json_encode($users);
}

then we can have this in our indexController.php

public function showUsers(){
print_r($this->model->getUsers());
}

The result of http://localhost:5000/index/showusers

That’s our tiny little framework in action.

You can find the code for this tutorial at

https://github.com/wilforlan/php-framework-tutorial

End result of all tutorial can be found at

https://github.com/wilforlan/CogentPHP

You can Pull, Comment, Raise issues and practice with this framework.

In the next tutorial, more things will be added such as

  • Adding Resuable Forms
  • Adding an ORM
  • Adding Views and Templates
  • Adding Base Classes and Models
  • Adding Helpers
  • Security consideration
  • Using Generators
  • e.t.c

Leave a question if things unclear or changes needed.

NOTE: This framework should not be used in a production environment.

For production, check

Laravel, YII, CodeIgniter, Slim or Silex

We have the liberty to learn and you can submit request for new tutorials at

williamscalg@gmail.com

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

Resources

https://www.sitepoint.com/the-mvc-pattern-and-php-1/

http://requiremind.com/a-most-simple-php-mvc-beginners-tutorial/

Show your support

Clapping shows how much you appreciated Williams Isaac’s story.