English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Handwritten PHP Framework: Deeply Understanding the MVC Operation Process

1 What is MVC 

MVC pattern (Model-View-(Controller) is a software architecture pattern in software engineering, dividing the software system into three basic parts: Model (Model), View (View), and Controller (Controller). 

The MVC pattern in PHP is also known as Web MVC, coming from the last century7evolved from the 0s. The purpose of MVC is to achieve a dynamic program design, facilitate the modification and expansion of the program later, and make it possible to reuse a part of the program. In addition, this pattern simplifies complexity, making the program structure more intuitive. While the software system separates its basic parts, it also endows each basic part with the appropriate functions. 

The functions of each part of MVC:
 •Model Model – Manages most of the business logic and all the database logic. The model provides an abstract layer for connecting and operating the database.
 •Controller Controller - Responsible for responding to user requests, preparing data, and deciding how to present data.
 •View View – Responsible for rendering data, presented to the user in HTML form. 

A typical Web MVC process:
 1.Controller intercepts the user's request;
 2.Controller calls Model to complete state read and write operations;
 3.Controller passes data to View;
 4.View renders the final result and presents it to the user. 

2 Why develop an MVC framework yourself 

There are a large number of excellent MVC frameworks available on the Internet. This tutorial is not intended to develop a comprehensive and ultimate MVC framework solution, but to consider it as a good opportunity to learn PHP internally. In this process, you will learn object-oriented programming and MVC design patterns, as well as some注意事项 in development. 

More importantly, you can completely control your framework and integrate your ideas into the framework you are developing. Although it may not be the best, you can develop features and modules in your own way. 

3 Start developing your own MVC framework 

3.1 Directory preparation 

Before we start developing, let's first establish the project. Suppose the project we are building is todo, and the MVC framework can be named FastPHP, then the first step in the next is to set up the directory structure first.

 

Although not all the directories above will be used in this tutorial, it is very necessary to set up the program directory at the beginning for the extensibility of the program in the future. Below, let's talk about the role of each directory in detail:
 •application – application code
 •config – Program configuration or database configuration
 •fastphp - Framework core directory
 •public – Static files
 •runtime - Temporary data directory
 •scripts – Command line tools 

3.2 Code specification

After setting up the directory, we will next specify the code specification:
 1.MySQL table names should be lowercase, such as: item, car
 2.The module name (Models) should have the first letter capitalized, and add "Model" to the end of the name, such as: ItemModel, CarModel
 3.The controller (Controllers) should have the first letter capitalized, and add "Controller" to the name, such as: ItemController, CarController
 4.The view (Views) deployment structure is "ControllerName/Behavior name/view.php, car/buy.php 

Some of the above rules are for better mutual calls within the program. Now let's start the real PHP MVC programming. 

3.3 Redirect 

Redirect all data requests to the index.php file, create a .htaccess file under the todo directory, the content of the file is as follows: 

<IfModule mod_rewrite.c>
  RewriteEngine On
  # Ensure that the requested path is not a filename or directory
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  # Redirect all requests to index.php?url=PATHNAME
  RewriteRule ^(.*)$ index.php?url=$1 [PT,L]
</IfModule>

The main reasons for doing this are:
 1.The program has a single entry;
 2.Except for static programs, all other programs are redirected to index.php;
 3.Can be used to generate SEO-friendly URLs. If you want to configure URLs better, you may need URL routing later. This will not be introduced here first. 

3.4 Entry file 

After completing the above operations, you should know what we need to do, that's right! Add an index.php file under the public directory, the content of the file is as follows:

 < ;63;php
// The application directory is the current directory
define('APP_PATH', __DIR__.'/');
// Enable debug mode
define('APP_DEBUG', true);
// Root URL of the website
define('APP_URL', 'http://localhost/fastphp');
// Loading the framework
require '.'/fastphp/FastPHP.php';

Note that there is no PHP end symbol added in the above PHP code, "?>”, The main reason for doing this is that for files that only contain PHP code, the end symbol ("?>”)should not exist, PHP itself does not need an end symbol. Not adding an end symbol can greatly prevent additional injection content from being added at the end, making the program more secure. 

3.5 Configuration file and main request 

In index.php, we made a request to the FastPHP.php file under the fastphp folder. So what content will be included in the startup file FastPHP.php?

< ;63;php
// Initialize constants
defined('FRAME_PATH') or define('FRAME_PATH', __DIR__.'/');
defined('APP_PATH') or define('APP_PATH', dirname($_SERVER['SCRIPT_FILENAME']).'/');
defined('APP_DEBUG') or define('APP_DEBUG', false);
defined('CONFIG_PATH') or define('CONFIG_PATH', APP_PATH.'config/');
defined('RUNTIME_PATH') or define('RUNTIME_PATH', APP_PATH.'runtime/');
// Include the configuration file
require APP_PATH . 'config/config.php';
//Include the core framework class
require FRAME_PATH . 'Core.php';
// Instantiate the core class
$fast = new Core;
$fast->run();

These files can be included directly in the index.php file, and constants can also be defined directly in index.php. We do this for the sake of easier management and expansion in the future, so that the programs that need to be loaded and run at the beginning are unified into a separate file for reference.

Let's take a look at the config file under the config.php file, which is mainly used to set some configuration items and database connections for the program, the main content is as follows: 

 < ;63;php
/** Variable configuration **/
define('DB_NAME', 'todo');
define('DB_USER', 'root');
define('DB_PASSWORD', 'root');
define('DB_HOST', 'localhost');

It should be said that config.php does not involve much content, just some basic database settings. Let's take a look at how to write the Core.php entry file under fastphp.
 

< ;63;php
/**
 * FastPHP core framework
 */
class Core
{
  // Run the program
  function run()
  {
    spl_autoload_register(array($this, 'loadClass'));
    $this->setReporting();
    $this->removeMagicQuotes();
    $this->unregisterGlobals();
    $this->Route();
  }
  // Route handling
  function Route()
  {
    $controllerName = 'Index';
    $action = 'index';
    if (!empty($_GET['url'])) {
      $url = $_GET['url'];
      $urlArray = explode('/', $url);
      // Get the controller name
      $controllerName = ucfirst($urlArray[0]);
      // Get the action name
      array_shift($urlArray);
      $action = empty($urlArray[0]) ?63; 'index' : $urlArray[0];
      //Get URL parameters
      array_shift($urlArray);
      $queryString = empty($urlArray) ?63; array() : $urlArray;
    }
    // Handling empty data
    $queryString = empty($queryString) ?63; array() : $queryString;
    // Instantiate the controller
    $controller = $controllerName . 'Controller';
    $dispatch = new $controller($controllerName, $action);
    // If the controller and action exist, this calls and passes the URL parameters.
    if ((int)method_exists($controller, $action)) {
      call_user_func_array(array($dispatch, $action), $queryString);
    } else {
      exit($controller . "Controller does not exist");
    }
  }
  // Detect development environment
  function setReporting()
  {
    if (APP_DEBUG === true) {
      error_reporting(E_ALL);
      ini_set('display_errors','On');
    } else {
      error_reporting(E_ALL);
      ini_set('display_errors','Off');
      ini_set('log_errors', 'On');
      ini_set('error_log', RUNTIME_PATH. 'logs/error.log');
    }
  }
  // Delete sensitive characters
  function stripSlashesDeep($value)
  {
    $value = is_array($value) ? array_map('stripSlashesDeep', $value) : stripslashes($value);
    return $value;
  }
  // Detect and delete sensitive characters
  function removeMagicQuotes()
  {
    if ( get_magic_quotes_gpc()) {
      $_GET = stripSlashesDeep($_GET );
      $_POST = stripSlashesDeep($_POST );
      $_COOKIE = stripSlashesDeep($_COOKIE);
      $_SESSION = stripSlashesDeep($_SESSION);
    }
  }
  // Detect and remove custom global variables (register globals)
  function unregisterGlobals()
  {
    if (ini_get('register_globals')) {
      $array = array('_SESSION', '_POST', '_GET', '_COOKIE', '_REQUEST', '_SERVER', '_ENV', '_FILES');
      foreach ($array as $value) {
        foreach ($GLOBALS[$value] as $key => $var) {
          if ($var === $GLOBALS[$key]) {
            unset($GLOBALS[$key]);
          }
        }
      }
    }
  }
  // Automatically load controller and model classes 
  static function loadClass($class)
  {
    $frameworks = FRAME_PATH . $class . '.class.php';
    $controllers = APP_PATH . 'application/controllers/' . $class . '.class.php';
    $models = APP_PATH . 'application/models/' . $class . '.class.php';
    if (file_exists($frameworks)) {
      // Load framework core class
      include $frameworks;
    } elseif (file_exists($controllers)) {
      // Load application controller class
      include $controllers;
    } elseif (file_exists($models)) {
      //Load application model class
      include $models;
    } else {
      /* Error code */
    }
  }
}

The following focuses on the main request method callHook(), first we want to see how our URL will be:
yoursite.com/controllerName/actionName/queryString

The function of callHook() is to obtain the URL from the global variable G ET['url'] and split it into three parts: controller, action, and queryString. 

For example, the URL link is: todo.com/item/view/1/first-item, then
 • $controller is: item
 • $action is: view
 • The Query String Query String is: array(1, first-item) 

After splitting, a new controller will be instantiated: $controller.'Controller' (where '.' is a hyphen), and its method $action will be called. 

3.6 Controller/Controller base class 

The next operation is to establish the base classes required for the program in fastphp, including the base classes for controllers, models, and views. 

The base controller class is created as Controller.class.php, and the main function of the controller is to perform overall dispatch, the specific content is as follows:
 

< ;63;php 
/**
 * Controller base class
 */
class Controller
{
  protected $_controller;
  protected $_action;
  protected $_view;
  // Constructor, initialize properties, and instantiate the corresponding model
  function __construct($controller, $action)
  {
    $this->_controller = $controller;
    $this->_action = $action;
    $this-_view = new View($controller, $action);
  }
  // Assign variables
  function assign($name, $value)
  {
    $this-_view-assign($name, $value);
  }
  // Render view
  function __destruct()
  {
    $this-_view-render();
  }
}
 

The Controller class implements communication between all controllers, models, and views (View class). When the destructor is executed, we can call render() to display the view (view) file.

3.7 Model base class Model

Create a new model base class as Model.class.php, and the model base class Model.class.php is as follows:

 < ;63;php
class Model extends Sql
{
  protected $_model;
  protected $_table;
  function __construct()
  {
    // Connect to database
    $this-connect(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
    // Get model name
    $this-_model = get_class($this);
    $this-_model = rtrim($this-_model, 'Model');
    // The database table name is consistent with the class name
    $this-_table = strtolower($this-_model);
  }
  function __destruct()
  {
  }
}

 Considering that the model needs to handle the database, a separate database base class Sql.class.php is established, and the model base class inherits from Sql.class.php. The code is as follows:

 < ;63;php
class Sql
{
  protected $_dbHandle;
  protected $_result;
  // Connect to database
  public function connect($host, $user, $pass, $dbname)
  {
    try {
      $dsn = sprintf("mysql:host=%s;dbname=%s;charset=utf8"$host, $dbname);
      $this-_dbHandle = new PDO($dsn, $user, $pass, array(PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC));
    } catch (PDOException $e) {
      exit('Error: ' . $e->getMessage());
    }
  }
  // Query all
  public function selectAll()
  {
    $sql = sprintf("select * from `%s`", $this->_table);
    $sth = $this->_dbHandle->prepare($sql);
    $sth->execute();
    return $sth->fetchAll();
  }
  // Query based on condition (id)
  public function select($id)
  {
    $sql = sprintf("select * from `%s` where `id` = '%s'", $this->_table, $id);
    $sth = $this->_dbHandle->prepare($sql);
    $sth->execute();
    return $sth->fetch();
  }
  // Delete based on condition (id)
  public function delete($id)
  {
    $sql = sprintf("delete from `%s` where `id` = '%s'", $this->_table, $id);
    $sth = $this->_dbHandle->prepare($sql);
    $sth->execute();
    return $sth->rowCount();
  }
  // Custom SQL query, return the number of affected rows
  public function query($sql)
  {
    $sth = $this->_dbHandle->prepare($sql);
    $sth->execute();
    return $sth->rowCount();
  }
  // Add new data
  public function add($data)
  {
    $sql = sprintf("insert into `%s` %s", $this->_table, $this->formatInsert($data));
    return $this->query($sql);
  }
  // Modify data
  public function update($id, $data)
  {
    $sql = sprintf("update `%s` set %s where `id` = '%s'", $this->_table, $this->formatUpdate($data), $id);
    return $this->query($sql);
  }
  // Convert array to SQL insert statement format
  private function formatInsert($data)
  {
    $fields = array();
    $values = array();
    foreach ($data as $key => $value) {
      $fields[] = sprintf("`%s`", $key);
      $values[] = sprintf("'%s'", $value);
    }
    $field = implode(',', $fields);
    $value = implode(',', $values);
    return sprintf("(%s) values (%s)", $field, $value);
  }
  // Convert the array into an SQL statement in update format
  private function formatUpdate($data)
  {
    $fields = array();
    foreach ($data as $key => $value) {
      $fields[] = sprintf("`%s` = '%s'", $key, $value);
    }
    return implode(',', $fields);
  }
}

It should be said that Sql.class.php is a core part of the framework. Why? Because through it, we have created a SQL abstraction layer that can greatly reduce the programming work of the database. Although the PDO interface is already very concise, the framework's flexibility is even higher after abstraction. 

3.8 View class View 

The content of the View class View.class.php is as follows:

 < ;63;php
/**
 * View base class
 */
class View
{
  protected $variables = array();
  protected $_controller;
  protected $_action;
  function __construct($controller, $action)
  {
    $this->_controller = $controller;
    $this->_action = $action;
  }
  /** Assign variables **/
  function assign($name, $value)
  {
    $this->variables[$name] = $value;
  }
  /** Rendering display **/
  function render()
  {
    extract($this->variables);
    $defaultHeader = APP_PATH . 'application/views/header.php';
    $defaultFooter = APP_PATH . 'application/views/footer.php';
    $controllerHeader = APP_PATH . 'application/views/' . $this->_controller . '/header.php';
    $controllerFooter = APP_PATH . 'application/views/' . $this->_controller . '/footer.php';
    // Header file}}
    if (file_exists($controllerHeader)) {
      include ($controllerHeader);
    } else {
      include ($defaultHeader);
    }
    // Page content file
    include (APP_PATH . 'application/views/' . $this->_controller . '/' . $this->_action . '.php');
    // Footer file
    if (file_exists($controllerFooter)) {
      include ($controllerFooter);
    } else {
      include ($defaultFooter);
    }
  }
}
 

So our core PHP MVC framework is written, and now we start writing the application to test the framework features.

4 Application

4.1 Database deployment

Create a new todo database in SQL and add the item table with the following statements and insert2records:

CREATE DATABASE `todo` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
USE `todo`;
CREATE TABLE `item` (
  `id` int(11) NOT NULL auto_increment,
  `item_name` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO `item` VALUES(1, 'Hello World.');
INSERT INTO `item` VALUES(2, 'Lets go!'); 

4.2 Deploy the model 

Then, we also need to create an ItemModel.php model in the models directory with the following content:

 < ;63;php
class ItemModel extends Model
{
  /* Implementation of business logic layer */
}

The model content is empty. Because the Item model inherits from Model, it has all the functions of Model.

4.3 Deploy the controller 

Create an ItemController.php controller in the controllers directory with the following content:

 < ;63;php
class ItemController extends Controller
{
  // Home method, test framework custom DB query
  public function index()
  {
    $items = (new ItemModel)->selectAll();
    $this->assign('title', 'All entries');
    $this->assign('items', $items);
  }
  // Add record, test framework DB record creation (Create)
  public function add()
  {
    $data['item_name'] = $_POST['value'];
    $count = (new ItemModel)->add($data);
    $this->assign('title', 'Add successful');
    $this->assign('count', $count);
  }
  // View record, test framework DB record read (Read)
  public function view($id = null)
  {
    $item = (new ItemModel)->select($id);
    $this->assign('title', 'Viewing ' . $item['item_name']);
    $this->assign('item', $item);
  }
  // Update record, test framework DB record update (Update)
  public function update()
  {
    $data = array('id' => $_POST['id'], 'item_name' => $_POST['value']);
    $count = (new ItemModel)->update($data['id'], $data);
    $this->assign('title', 'Update successful');
    $this->assign('count', $count);
  }
  // Delete record, test framework DB record deletion (Delete)
  public function delete($id = null)
  {
    $count = (new ItemModel)->delete($id);
    $this->assign('title', 'Deletion successful');
    $this->assign('count', $count);
  }
}

4.4 Deploy view 

In the 'views' directory, create two header and footer template files, header.php and footer.php, with the following content. 

header.php, content:

 <html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title><?php echo $title ;63;></title>
  <style>
    .item {
      width:400px;}
    }
    input {
      color:#222222;
      font-family:georgia,times.NewReader();
      font-size:24px;
      font-weight:normal;
      line-height:-height:1.2em;
      color:black;
    }
    a {
      color:blue;
      font-family:georgia,times.NewReader();
      font-size:20px;
      font-weight:normal;
      line-height:-height:1.2em;
      text-decoration:none;
     }
    a:hover {
      text-decoration:underline;
    }
    h1 {
      color:#000000;
      font-size:41px;
      letter-spacing:-spacing:-2px;
      line-height:-height:1em;
      font-family:helvetica,arial,sans-serif;-serif;
      border-bottom:1px dotted #cccccc;
    }
    h2 {
      color:#000000;
      font-size:34px;
      letter-spacing:-spacing:-2px;
      line-height:-height:1em;
      font-family:helvetica,arial,sans-serif;-serif;
    }
  </style>
</head>
<body>
  <h1><63;php echo $title ;63;></h1>
footer.php,内容:
 </body>
</html>

然后,在 views/item 创建以下几个视图文件。 

index.php,浏览数据库内 item 表的所有记录,内容:

 <form action="< ;63;php echo APP_URL &63;>/item/add" method="post">
  <input type="text" value="点击添加" onclick="this.value=''" name="value">
  <input type="submit" value="添加">
</form>
<br ;/><br ;/>
< ;63;php $number = 0;63;>
< ;63;php foreach ($items as $item): ;63;>
  <a class="big" href="<63;php echo APP_URL &63;>/item/view/< ;63;php echo $item['id'] &63;>" title="[#1#]">
    <span class="item">
      < ;63;php echo ++$number ;63;>
      < ;63;php echo $item['item_name'] ;63;>
    </span>
  </a>
  ----
  <a class="big" href="<63;php echo APP_URL &63;>/item/delete/< ;63;php echo $item['id'] ;63;>删除</a>
<br ;/>
< ;63;php endforeach ;63;>

add.php,添加记录,内容:
 <a class="big" href="<63;php echo APP_URL &63;>/item/index">成功添加< ;63;php echo $count &63;>条记录,点击返回</a> 

view.php,查看单条记录,内容:

 <form action="< ;63;php echo APP_URL &63;>/item/update" method="post">
  <input type="text" name="value" value="< ;63;php echo $item['item_name'] ;63;>
  <input type="hidden" name="id" value="<63;php echo $item['id'] &63;>
  <input type="submit" value="Modify">
</form>
<a class="big" href="<63;php echo APP_URL &63;>/item/;index>Return</a>

update.php, change record, content:
 <a class="big" href="<63;php echo APP_URL &63;>/item/;index>Successfully modified<63;php echo $count &63;>Item, click to return</a> 

delete.php, delete record, content:
 <a href="<63;php echo APP_URL &63;>/item/;index>Successfully deleted<63;php echo $count &63;>Item, click to return</a> 

4.5 Application Test 

So, access the todo program in the browser: http://localhost/todo/item/index/, you can see the effect. 

The above code has been released on github, the key parts are commented, repository address:https://github.com/yeszao/fastphp, welcome to clone and submit.

To design a better MVC or use it more standardized, please seePrinciples of Responsibility Division in MVC Architecture .

That's all for this article, I hope it will be helpful to everyone's learning, and I also hope everyone will support the Yana Tutorial more.

Declaration: The content of this article is from the network, the copyright belongs to the original author, the content is contributed and uploaded by Internet users spontaneously, this website does not own the copyright, has not been manually edited, and does not assume relevant legal liability. If you find any content suspected of copyright infringement, please send an email to: notice#oldtoolbag.com (Please replace # with @ when sending an email for reporting, and provide relevant evidence. Once verified, this site will immediately delete the infringing content.)

You May Also Like