📜 ⬆️ ⬇️

Creating a simple MVC system in PHP 5

Foreword


In this tutorial, you will learn how to build a simple MVC (Model-View-Controller, Model-Display-Controller) MVC architecture system using PHP 5.1 using SPL (Standard PHP Library, PHP Standard Library) features.




Introduction


Welcome to the first comprehensive guide for PHP 5 in PHPit . You will need PHP 5.1 with SPL installed, since we will use some of the latest features of PHP 5.
')
In this tutorial, I'm going to show you how to build a simple MVC system (MVC architecture is the most common design pattern for large web applications). I will take you through all the steps from the beginning to the end of creating a full-fledged MVC system.


Single entry point


One of the important things in MVC is one entry point to the application instead of a heap of PHP files that do something like the following:

<? php
include ( 'global.php' );

// Here is the page code

?>



We will have one file that processes all requests. This means that we will not have to worry about connecting global.php every time we need to create a new page. This “one entry point” will be called index.php and at the moment will be like this:

<? php


// Do something here


?>



As you can see, this script is not doing anything yet, but wait a minute.

To send all requests to the main page, we will use mod_rewrite and install the RewriteRule directive in .htaccess. Paste the following code into the .htaccess file and save it in the same directory as index.php:

 RewriteEngine on 

 RewriteCond% {REQUEST_FILENAME}! -F
 RewriteCond% {REQUEST_FILENAME}! -D

 RewriteRule ^ (. *) $ Index.php? Route = $ 1 [L, QSA]


First we check if the requested file exists using the RewriteCond directive, and if not, redirect the request to index.php. Such a check for the existence of a file is necessary, since otherwise index.php will try to process all requests to the site, including requests for images. And this is exactly what we don’t need.

If you are unable to use .htaccess or mod_rewrite, then you will have to manually address all requests to index.php. In other words, all links should be of the form “index.php? Route = [here-going request]”. For example, “index.php? Route = chat / index”.

Now that all requests go through a single entry point, we can start writing the index.php script. The first thing we have to do is initialize the system. Create an includes directory, and in it the startup.php file (we will have it an initialization file). Paste the following code into index.php:

<? php

error_reporting ( E_ALL );

if ( version_compare ( phpversion (), '5.1.0' , '<' ) == true ) {die ( 'PHP5.1 Only' ); }


// Constants:

define ( 'DIRSEP' , DIRECTORY_SEPARATOR );


// Find the path to the site files

$ site_path = realpath ( dirname ( __FILE__ ). DIRSEP . '..' . DIRSEP ). DIRSEP ;

define ( 'site_path' , $ site_path );


In this example, we declare some constant, find out where the system files are, and also check that the PHP version is, well, at least, 5.1.

The next thing to do is the Registry object (log, registry) to store global values. It will be transferred to individual objects of the system and used to access global values, without needing to designate variables as “global” or access $ GLOBALS array. Read the article “Using global values ​​in PHP” for more detailed information about the registry object.

Add the following code to the startup.php file after the code in the previous example:

$ registry = new Registry ;


If you try to start the system now, you can see the following error:

Fatal error: Class 'Registry' not found in g:\Projects\PHPit\content\simple mvc php5\demo\includes\startup.php on line 12


This, of course, is not a big surprise for us, because we have not written the Registry class itself yet. The file with the class could simply be connected using the include () function ( Note. Lane: by the way, include () is not such a function, but still an expression of the language, a control structure, if you look at the mana ), but let's use one of the new features in PHP 5: __autoload ().

The magic function __autoload () is used to dynamically load classes. When PHP detects a non-existent class, it first calls the __autoload () function and then throws an error. We can use this opportunity to load classes on the fly.

Paste this code before the code from the previous example:

// Load classes on the fly

function __autoload ( $ class_name ) {

$ filename = strtolower ( $ class_name ). '.php' ;

$ file = site_path . 'classes' . DIRSEP . $ filename ;


if ( file_exists ( $ file ) == false ) {

return false ;

}


include ( $ file );

}


Our __autoload () function takes the class name passed as an argument to it and checks if there is a file with a similar name in the class directory. If the file does not exist, the function simply returns false and a fatal error pops up. But if the file exists, it will be loaded. Those. the required class will appear, and there will be no error.

We have not yet created the Registry class itself, so the error will still appear. Let's do it.


Creating a Registry Class


The Registry class is used to transfer global values ​​between individual objects. This is actually a fairly simple class in which you need to implement several small methods.

First, create a classes directory and a registry.php file in it. Paste the following code into registry.php:

<? php


Class Registry {

private $ vars = array ();


}


?>



Now we have the “skeleton” of the Registry class and we need to load it with methods. Write 2 methods: set () to set values ​​and get () to get values. You can also write a remove () method to remove values. Add these methods to the Registry class:

function set ( $ key , $ var ) {

if (isset ( $ this -> vars [ $ key ]) == true ) {

throw new Exception ( 'Unable to set var `' . $ key . '`. Already set.' );

}


$ this -> vars [ $ key ] = $ var ;

return true ;

}


function get ( $ key ) {

if (isset ( $ this -> vars [ $ key ]) == false ) {

return null ;

}


return $ this -> vars [ $ key ];

}


function remove ( $ var ) {

unset ( $ this -> vars [ $ key ]);

}


?>



These methods are simple; they set, get, and remove elements from the $ vars array, which is an attribute of a class. In the set () method, we at the same time check whether a value with the specified key already exists, and, if it exists, we generate an exception. This is to avoid accidentally overwriting values.

Now we have a full Registry class, but we will not stop there. We use one of the features of the library SPL: ArrayAccess. SPL (abbreviated from Standard PHP Library, Standard PHP Library) is a collection of interfaces and classes designed to solve common problems. One of the SPL interfaces, ArrayAccess, can be used to provide access to an object as if it were a regular array. Let's look at this example:

<? php


$ registry = new Registry ;


// Set some value

$ registry -> set ( 'name' , 'Dennis Pallett' );


// Get the value using get ()

echo $ registry -> get ( 'name' );


// Get the value using access as an array

echo $ registry [ 'name' ]


?>



The trick is that $ registry becomes like an array, although in reality it is an object. Of course, ArrayAccess does not give any special advantages, but it allows you to reduce the amount of code, since you do not have to write "-> get ()" every time. To use this interface, you need to fix the first line of the class ("Class Registry") as follows:

Class Registry Implements ArrayAccess {



The keyword “Implements” tells the interpreter that we are implementing this class of interface, which is actually ArrayAccess.

The class that implements the ArrayAccess interface should have the following methods:

function offsetExists ( $ offset ) {

return isset ( $ this -> vars [ $ offset ]);

}


function offsetGet ( $ offset ) {

return $ this -> get ( $ offset );

}


function offsetSet ( $ offset , $ value ) {

$ this -> set ( $ offset , $ value );

}


function offsetUnset ( $ offset ) {

unset ( $ this -> vars [ $ offset ]);

}



These methods should be self-explanatory. Further information can be found in the SPL documentation.

Now, having implemented the ArrayAccess interface, we can access the object as a regular array. This is clearly demonstrated, both in the previous example and in this:

<? php


$ registry = new Registry ;


// Set some value

$ registry [ 'name' ] = 'Dennis Pallett' ;


// Get the value using get ()

echo $ registry -> get ( 'name' );


// Get the value using access as an array

echo $ registry [ 'name' ]


?>



The Registry class is now complete, and if you try to start the system, everything should work (although nothing will be output yet). We are done with the initialization file, and we can proceed to the next step of writing our MVC system: implementing database access, which is called “Model” in the MVC architecture.



Model


An “M” or model is part of the MVC system, which is responsible for querying the database (or another external source) and providing information to the controller. It would be possible to load the required model depending on the request, but I prefer to slightly erase the boundaries between the model and the controller in this particular place, i.e. the controller works with the database directly through the library of interaction with the database, rather than through a separate model. Maybe you want to do it differently, it's a matter of taste.

We need to write the code necessary to establish the connection with the database and place it in index.php. There are many great libraries for working with databases (including my own, AutoCRUD ), but in PHP 5 there is already such a library - PDO. Therefore, there is no need to use any other.

Paste the following code into the index.php file (after connecting the initialization file):

# Connecting to DB

$ db = new PDO ( 'mysql: host = localhost; dbname = demo' , '[user]' , '[password]' );

$ registry -> set ( 'db' , $ db );



In this example, we first create a new instance of the PDO library and connect to our MySQL database. Then we make the $ db variable globally accessible using our Registry class.

The model component of our system is ready, so let's move on to writing a controller.

Writing a controller also means writing the Router class responsible for loading the desired controller depending on the request (remember, the $ route variable is passed to the index.php URL).



Class router


The Router class will parse the request, and then load the required controller. Create a "skeleton" class:

<? php


Class Router {

private $ registry ;

private $ path ;

private $ args = array ();


function __construct ( $ registry ) {

$ this -> registry = $ registry ;

}


}


?>



Then add the following lines to index.php:

# Download router

$ router = new Router ( $ registry );

$ registry -> set ( 'router' , $ router );



We have added the Router class to our MVC system, but it is not doing anything yet, so let's add the methods necessary for our work to it.

The first thing we write is the setPath () method to set the directory where all our controllers will be located. The method is as follows and should be added to the Router class:

function setPath ( $ path ) {

$ path = trim ( $ path , '/ \\' );

$ path . = DIRSEP ;


if ( is_dir ( $ path ) == false ) {

throw new Exception ( 'Invalid controller path: `' . $ path . '`' );

}


$ this -> path = $ path ;

}



Then add the following lines to index.php:

$ router -> setPath ( site_path . 'controllers' );



Now that we have set the path to our controllers, we will write the method responsible for loading the controller. This method will be called delegate (), it will analyze the request. The first piece of this method is:

function delegate () {

// Analyze the path

$ this -> getController ( $ file , $ controller , $ action , $ args );



As you can see, it uses another method, getController (), to get the name of the controller and several other variables. This method looks like this:

private function getController (& $ file , & $ controller , & $ action , & $ args ) {

$ route = (empty ( $ _GET [ 'route' ]))? '' : $ _GET [ 'route' ];


if (empty ( $ route )) { $ route = 'index' ; }


// Get the separate parts

$ route = trim ( $ route , '/ \\' );

$ parts = explode ( '/' , $ route );


// Find the right controller

$ cmd_path = $ this -> path ;

foreach ( $ parts as $ part ) {

$ fullpath = $ cmd_path . $ part ;


// Is there a folder with this path?

if ( is_dir ( $ fullpath )) {

$ cmd_path . = $ part . DIRSEP ;

array_shift ( $ parts );

continue;

}


// Find the file

if ( is_file ( $ fullpath . '.php' )) {

$ controller = $ part ;

array_shift ( $ parts );

break;

}

}


if (empty ( $ controller )) { $ controller = 'index' ; };


// Get the action

$ action = array_shift ( $ parts );

if (empty ( $ action )) { $ action = 'index' ; }


$ file = $ cmd_path . $ controller . '.php' ;

$ args = $ parts ;

}



Let's run through this method. First, it takes the value of the $ route variable from the request, then splits it into pieces using the explode () function. For example, the query “members / view” is converted to the following array: array ('members', 'view').

Then, using the foreach loop, it passes through each part and checks whether this part is a directory. If so, he attributes it to the path to the file and checks the next part. This allows you to put controllers in subdirectories and, thus, get a hierarchy of controllers. If the current part of the request is not a directory, but is a file, it is stored in the $ controller variable, and we exit the loop, because we have found the controller that we need.

After the loop, we check the variable with the name of the controller. If it is empty, then we use the “index” controller, which will be our default controller. Then the method determines the action to be performed. A controller is a class that consists of several methods. The action points to a specific method. If no action is specified, we will use “index” - the default action.

And finally, we get the full path to the controller file, combining three variables: the path, the name of the controller, and the extension "php".

Now that we have analyzed the request, it's time to call the delegate () method to load the controller and execute the action. The complete delegate () method looks like this:

function delegate () {

// Analyze the path

$ this -> getController ( $ file , $ controller , $ action , $ args );


// Is the file available?

if ( is_readable ( $ file ) == false ) {

die ( '404 Not Found' );

}


// Connect the file

include ( $ file );


// Create a controller instance

$ class = 'Controller_' . $ controller ;

$ controller = new $ class ( $ this -> registry );


// Is the action available?

if ( is_callable (array ( $ controller , $ action )) == false ) {

die ( '404 Not Found' );

}


// perform the action

$ controller -> $ action ();

}



After analyzing the request using the getController () method, we check whether the file actually exists and, if not, return a simple error message.

After that we connect the file with the controller and create an instance of its class, which should be called “Controller_ [name]”. A little later we will talk about controllers in more detail.

Then we check if there is a specified action (i.e. a method) and if it is possible to access it (use the is_callable () function for this). Finally, we perform the actual action itself, on which the role of the Router class ends.

Having written the delegate () method completely, we will add the following line to the index.php file:

$ router -> delegate ();



If we try to start the system now, we will see the following error (of course, if there are no controllers directory yet):

Fatal error: Uncaught exception 'Exception' with message 'Invalid controller path: `g:\Projects\PHPit\content\simple mvc php5\demo\controllers\`' in g:\Projects\PHPit\content\simple mvc php5\demo\classes\router.php:18 Stack trace: #0 g:\Projects\PHPit\content\simple mvc php5\demo\index.php(13): Router->setPath('g:\Projects\PHP...') #1 {main} thrown in g:\Projects\PHPit\content\simple mvc php5\demo\classes\router.php on line 18


Or we will see the error “404 Not Found”, as there is not a single controller yet. But this is what we are going to do now.



Controller


The controllers in our MVC system will be fairly simple and take quite a bit of time. First, make sure that the controllers directory exists. Create a file controller_base.php in the classes directory and paste the following code into it:

<? php


Abstract Class Controller_Base {

protected $ registry ;


function __construct ( $ registry ) {

$ this -> registry = $ registry ;

}


abstract function index ();

}


?>



This abstract class will be the parent class for all of our controllers. It will do only two things: save a local copy of the Registry class and, using the abstract index () method, force all child controllers to implement this method.

Let's write our first controller. Create an index.php file in the controllers directory and paste the following code into it:

<? php


Class Controller_Index Extends Controller_Base {


function index () {

echo 'Hello from my MVC system' ;

}


}


?>



We have just created our first controller and, if we try to start the system, we can see the following:


(Full size, 1024x357, 115 KB)


This means that the Router class performed its work and launched the required action from the required controller. Let's write another controller that will match the query "members / view". Create a file members.php in the controller directory and paste the following code into it:

<? php


Class Controller_Members Extends Controller_Base {


function index () {

echo 'Default index of the members `controllers' ;

}


function view () {

echo 'You are viewing the members / view request' ;

}


}


?>



Now we’ll go to our MVC system at the request of "members / view" or "index.php? Route = members / view". We should see the following result:



(Full size, 1024x320, 117 KB)


Only by writing a new controller and adding a method to it, we were able to create a new page, and nothing had to be changed in the system itself. In addition, our controllers do not need to include the global.php file or do something like that.

Now that we have controllers, only one thing remains: "V" or "View" ("Display").



Display


As is the case with models, there are several different options for creating a View component in an MVC system. We could teach the Router class to automatically load another file, named something like this: “view_ {name} .php”. But in order to make the manual more understandable, we will write the Template class, which will deal with the output of templates.

First, create a file template.php in the classes directory and paste the following code into it:

<? php


Class Template {

private $ registry ;

private $ vars = array ();


function __construct ( $ registry ) {

$ this -> registry = $ registry ;

}


}


?>



Now we have the main structure of our Template class. The next step is to add this code to the index.php file right before the lines associated with the Router class:

# Create a template object

$ template = new Template ( $ registry );

$ registry -> set ( 'template' , $ template );



Since we will need to use values ​​from models and controllers, we will write the set () method to set the variables available in the templates. Let's look at an example:

function set ( $ varname , $ value , $ overwrite = false ) {

if (isset ( $ this -> vars [ $ varname ]) == true AND $ overwrite == false ) {

trigger_error ( 'Unable to set var `' . $ varname . '`. Already set, and not allowed.' , E_USER_NOTICE );

return false ;

}


$ this -> vars [ $ varname ] = $ value ;

return true ;

}


function remove ( $ varname ) {

unset ( $ this -> vars [ $ varname ]);

return true ;

}


The set () and remove () methods are fairly simple and are used, respectively, to set and remove variables.

Let's write the show () method that will display the templates. The easiest way is to create a separate templates directory where to store all template files, and use include () to output the template. Of course, your own show () method can be completely different and load templates from the database or do something else. Look at kuso

Source: https://habr.com/ru/post/31270/


All Articles