PHP handwritten MVC (5) – routing

Time:2020-2-26

Routing is an essential component in the framework, whose function is to parse URLs into specific controllers according to predetermined rules.

Here we define two routing rules:

  • Query string. After the path, use the question mark and parameters. For multiple parameters, use the&Separate. Use in profilequerystringExpress
#Controller / method? Parameter 1 = value 1 & parameter 2 = value 2
http://domain/user/info?name=php&chapter=10
  • Path, add parameters and values to the back in the form of path, and use/Separate. Use in configurationrestful
#Controller / method / Parameter1 / value1 / parameter2 / Value2
https://domain/user/info/name/php/chapter/100

main controller

In directorycoreEstablishController.php, which inheritsContainer

<?php

namespace core;

class Controller extends Container
{
    
}

The master controller can add controller common methods, such as page renderingrender(), error code, etc. all controllers must inherit the master controller. Due to master controller inheritanceContainer, so the controller is also a subclass of the dispenser, which can beregister()Get instance.

Controller class

  • Class naming rules

The controller name follows the hump naming rule with the beginning of uppercase, and the suffix is added by defaultController, controller file name is the same as class name, such as controller classUserController, whose file name isUserController.php

  • Method naming rules

The method name follows the camel naming rule starting with lowercase and is added by defaultRequest mode(e.g., get, post, put, etc.) prefix, e.ggetIndex()postUpdate()

Take the above exampleUserControllertake as an example

<?php

namespace controller;

use core\Controller;

class UserController extends Controller
{
    /**
     *Valid when HTTP request mode is get
     *URL is / user / Info
     *
     */
    public function getInfo()
    {
        
    }

    /**
     *Valid when HTTP request mode is post
     *URL is / user / update
     *
     */
    public function postUpdate()
    {
        
    }
}

Route parsing

staycoreCreate under directoryRouter.php

$ cd tinyphp/core
$ touch Router.php

Defining variables in a constructor


<?php

namespace core;

use dispatcher\Container;

class Router extends Container
{
    public $method;
    public $uri;
    public $path;

    public function __construct()
    {
        $this->method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
        $this->uri = $_SERVER['REQUEST_URI'];
        $this->path = $_SERVER['PATH_INFO'];
    }
}

common$_SERVERfield

  1. $_SERVER['PATH_INFO']Path information for the URL, such as / user / Info
  2. $_SERVER['REQUEST_METHOD']Request method, such as post, get
  3. $_SERVER['REQUEST_URI']Full URL, such as / user / info? Id = 1 & name = Lucy

staystart()Resolve URL in method

protected function start()
{
    /**
     *It can also be written as config:: get ('default. Route ',' querystring ');
     *
     */
    $route = Config::get('default.route') ?? 'querystring';

    //Parse controller and action
    $path = explode('/',trim($this->path,'/'));

    if (empty($path[0])) {
        $path[0] = Config::get('default.controller','index');
    }
    $controller = ucfirst($path[0]).'Controller';

    //Get request method
    $method = strtolower($this->method);
    $action = $method.ucfirst($path[1] ?? Config::get('default.action','index'));
    //Get parameters
    $args = [];
    if (method_exists($this,$route)) {
        $args = call_user_func_array([$this,$route],[$this->uri]);
    }
    return ['controller'=>$controller,'action'=>$action,'args'=>$args];
}

querystring()Parameter parsing

private function querystring($url)
{
    $urls = explode('?', $url);
    if (empty($urls[1])) {
        return [];
    }
    $param_arr = [];
    $param_tmp = explode('&', $urls[1]);
    if (empty($param_tmp)) {
        return [];
    }
    foreach ($param_tmp as $param) {
        if (strpos($param, '=')) {
            list($key,$value) = explode('=', $param);
            //Whether the variable name is a compound rule
            if (preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $key)) {
                $param_arr[$key] = $value;
            }
        }
    }
    return $param_arr;
}

The parameters of querystring are?Later part, for multiple parameters&Separate.

restful()Parameter parsing

private function restful($url)
{
    $path = explode('/', trim(explode('?', $url)[0], '/'));
    $params = [];
    $i = 2;
    while (1) {
        if (!isset($path[$i])) {
            break;
        }
        $params[$path[$i]] = $path[$i+1] ?? '';
        $i = $i+2;
    }
    return $params;
}

The restful parameter is the path after the method.

The complete code is as follows:

<?php

namespace core;

use dispatcher\Container;

class Router extends Container
{
    public $method;
    public $uri;
    public $path;

    public function __construct()
    {
        $this->method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
        $this->uri = $_SERVER['REQUEST_URI'];
        $this->path = $_SERVER['PATH_INFO'];
    }

    protected function start()
    {
        $route = Config::get('default.route') ?? 'querystring';
    
        //Parse controller and action
        $path = explode('/',trim($this->path,'/'));
    
        if (empty($path[0])) {
            $path[0] = Config::get('default.controller','index');
        }
        $controller = ucfirst($path[0]).'Controller';
    
        //Get request method
        $method = strtolower($this->method);
        $action = $method.ucfirst($path[1] ?? Config::get('default.action','index'));
        
        //Get parameters
        $args = [];
        if (method_exists($this,$route)) {
            $args = call_user_func_array([$this,$route],[$this->uri]);
        }
        return ['controller'=>$controller,'action'=>$action,'args'=>$args];
    }
    
    /**
     *Query string parameters
     *? After, parameters are separated by & &
     *
     */
    private function querystring($url)
    {
        $urls = explode('?', $url);
        if (empty($urls[1])) {
            return [];
        }
        $param_arr = [];
        $param_tmp = explode('&', $urls[1]);
        if (empty($param_tmp)) {
            return [];
        }
        foreach ($param_tmp as $param) {
            if (strpos($param, '=')) {
                list($key,$value) = explode('=', $param);
                //Whether the variable name is a compound rule
                if (preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $key)) {
                    $param_arr[$key] = $value;
                }
            }
        }
        return $param_arr;
    }
    /**
     *Path parameters
     *Controller / method / Parameter1 / value1 / parameter2 / Value2
     *
     */
    http://domain/user/info/name/entner?name=php&chapter=10
    private function restful($url)
    {
        $path = explode('/', trim(explode('?', $url)[0], '/'));
        $params = [];
        $i = 2;
        while (1) {
            if (!isset($path[$i])) {
                break;
            }
            $params[$path[$i]] = $path[$i+1] ?? '';
            $i = $i+2;
        }
        return $params;
    }
}

Route call mode is

<?php

$router = Rouer::start();

Test routing

In profileapp/conf/config.phpSet the default route toquerystring

<?php

return [
    'default' => [
        'controller' => 'index',
        'action' => 'index',
        'route' = > querystring ', // can also be set to restful
    ],
    'view' => [
        'dir' => 'layout',
        'file' => 'base',
    ]
];

staycore/Application.phpIn filerun()Method to implement routing call

<?php
...
public function run()
{
    $router = Router::start();
    echo '<pre>';
    print_r($router);
}
...

Start PHP built-in server

$ cd tinyphp/public
$ php -S localhost:8080

Enter http://localhost:8080/course/document? Name=php&&chapter=10 in the browser
The output is

Array
(
    [controller] => CourseController
    [action] => getDocument
    [args] => Array
        (
            [name] => php
            [chapter] => 10
        )
)

The same can be testedrestfulRouting rules.

Call controller method

After the route resolution, get the controller name, method and parameter to be called. Since the controller inherits the dispenser, you can use theregister()Get instance, editcore/Applicaiton.php

<?php

...
public function run()
{
    $router = Router::start();
    //Pay attention to using namespace
    $controller = "controller\".$router['controller'];
    $action = $router['action'];
    $args = $router['args'];
    
    echo call_user_func_array([$controller::register(),$action],$args);
}
...

In this way, method calls can be implemented, but the method parameters cannot be controlled. For example, sometimes we need to use an object instance in the method parameters, which is called dependency injection, that is, inject the instance to be used into the method, which can be realized through the high-level feature reflection of PHP.