PHP手写MVC (五)—— 路由-程序员宅基地

技术标签: php  mvc  开发语言  

路由是一个框架中必不可少的组件,其作用是把 URL 按照预定规则解析到特定控制器中。

我们在这里定义了两种路由规则:

  • 查询字符串。在路径后面使用问号加参数,多个参数用 & 分隔。在配置文件使用 querystring 表示
#控制器/方法?参数1=值1&参数2=值2
http://domain/user/info?name=php&chapter=10
  • 路径,以路径的形式将参数和值添加到后面,中间用 / 分隔。配置中使用 restful
#控制器/方法/参数1/值1/参数2/值2
https://domain/user/info/name/php/chapter/100
主控制器

在目录 core 创建 Controller.php,该类继承 Container

<?php

namespace core;

class Controller extends Container
{
    
}

主控制器可以添加控制器公共方法,如页面渲染 render(),错误代码等,所有控制器必须继承主控制器。由于主控制器继承 Container,因此,控制器也是分发器的子类,可以通过 register() 获取实例。

控制器类
  • 类命名规则

控制器命名遵循大写开头的驼峰命名规则,并且默认添加后缀 Controller,控制器文件命名和类命名一样,如控制器类 UserController,其文件命名为 UserController.php

  • 方法命名规则

方法命名遵循小写开头的驼峰命名规则,并且默认添加请求方式(如,get,post,put等)前缀,如 getIndex()postUpdate()

以上例 UserController 为例

<?php

namespace controller;

use core\Controller;

class UserController extends Controller
{
    /**
     * HTTP 请求方式为 GET 时有效
     * url 为 /user/info
     *
     */
    public function getInfo()
    {
        
    }

    /**
     * HTTP 请求方式为 POST 时有效
     * url 为 /user/update
     *
     */
    public function postUpdate()
    {
        
    }
}

路由解析

core 目录下创建 Router.php

$ cd tinyphp/core
$ touch Router.php

在构造函数中定义变量


<?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'];
    }
}

常见 $_SERVER 字段

  1. $_SERVER['PATH_INFO'] URL的路径信息,如 /user/info
  2. $_SERVER['REQUEST_METHOD'] 请求方法,如 POST,GET
  3. $_SERVER['REQUEST_URI'] 完整 URL,如 /user/info?id=1&name=Lucy

start() 方法中解析 URL

protected function start()
{
    /**
     * 也可以写成 Config::get('default.route','querystring');
     *
     */
    $route = Config::get('default.route') ?? 'querystring';

    //解析 controller 和 action
    $path = explode('/',trim($this->path,'/'));

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

    //获取请求方法
    $method = strtolower($this->method);
    $action = $method.ucfirst($path[1] ?? Config::get('default.action','index'));
    //获取参数
    $args = [];
    if (method_exists($this,$route)) {
        $args = call_user_func_array([$this,$route],[$this->uri]);
    }
    return ['controller'=>$controller,'action'=>$action,'args'=>$args];
}

querystring() 参数解析


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);
            //变量名是否复合规则
            if (preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $key)) {
                $param_arr[$key] = $value;
            }
        }
    }
    return $param_arr;
}

querystring 的参数为 ? 后面的部分,多个参数用 & 分隔。

restful() 参数解析

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;
}

restful 的参数为方法后面的路径。

完整代码如下:

<?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';
    
        //解析 controller 和 action
        $path = explode('/',trim($this->path,'/'));
    
        if (empty($path[0])) {
            $path[0] = Config::get('default.controller','index');
        }
        $controller = ucfirst($path[0]).'Controller';
    
        //获取请求方法
        $method = strtolower($this->method);
        $action = $method.ucfirst($path[1] ?? Config::get('default.action','index'));
        
        //获取参数
        $args = [];
        if (method_exists($this,$route)) {
            $args = call_user_func_array([$this,$route],[$this->uri]);
        }
        return ['controller'=>$controller,'action'=>$action,'args'=>$args];
    }
    
    /**
     * 查询字符串参数
     * ?后,参数通过&&分隔
     *
     */
    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);
                //变量名是否复合规则
                if (preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $key)) {
                    $param_arr[$key] = $value;
                }
            }
        }
        return $param_arr;
    }
    /**
     * 路径参数
     * 控制器/方法/参数1/值1/参数2/值2
     *
     */
    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;
    }
}

路由调用方式为

<?php

$router = Rouer::start();

测试路由

在配置文件 app/conf/config.php 中设置默认路由为 querystring

<?php

return [
    'default' => [
        'controller' => 'index',
        'action' => 'index',
        'route' => 'querystring',//还可以设置为 restful
    ],
    'view' => [
        'dir' => 'layout',
        'file' => 'base',
    ]
];

core/Application.php 文件中 run() 方法实现路由调用

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

启动 PHP 内置服务器

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

在浏览器中输入 http://localhost:8080/course/document?name=php&&chapter=10
输出结果为

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

同理可以测试 restful 路由规则。

调用控制器方法

路由解析后,获得需要调用的控制器名,方法和参数。由于控制器继承分发器后,可以通过 register() 获取实例,编辑 core/Applicaiton.php

<?php

...
public function run()
{
    $router = Router::start();
    //注意使用命名空间
    $controller = "controller\\".$router['controller'];
    $action = $router['action'];
    $args = $router['args'];
    
    echo call_user_func_array([$controller::register(),$action],$args);
}
...

通过这种方式可以实现方法调用,但是无法控制方法参数,比如,有时候我们需要在方法参数中使用某个对象实例,术语称为依赖注入,即把需要使用的实例注入到方法中,那么可以通过PHP的高级特性反射来实现。

作者:entner

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_42396932/article/details/137925550

智能推荐

分布式光纤传感器的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告_预计2026年中国分布式传感器市场规模有多大-程序员宅基地

文章浏览阅读3.2k次。本文研究全球与中国市场分布式光纤传感器的发展现状及未来发展趋势,分别从生产和消费的角度分析分布式光纤传感器的主要生产地区、主要消费地区以及主要的生产商。重点分析全球与中国市场的主要厂商产品特点、产品规格、不同规格产品的价格、产量、产值及全球和中国市场主要生产商的市场份额。主要生产商包括:FISO TechnologiesBrugg KabelSensor HighwayOmnisensAFL GlobalQinetiQ GroupLockheed MartinOSENSA Innovati_预计2026年中国分布式传感器市场规模有多大

07_08 常用组合逻辑电路结构——为IC设计的延时估计铺垫_基4布斯算法代码-程序员宅基地

文章浏览阅读1.1k次,点赞2次,收藏12次。常用组合逻辑电路结构——为IC设计的延时估计铺垫学习目的:估计模块间的delay,确保写的代码的timing 综合能给到多少HZ,以满足需求!_基4布斯算法代码

OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版-程序员宅基地

文章浏览阅读3.3k次,点赞3次,收藏5次。OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版

关于美国计算机奥赛USACO,你想知道的都在这_usaco可以多次提交吗-程序员宅基地

文章浏览阅读2.2k次。USACO自1992年举办,到目前为止已经举办了27届,目的是为了帮助美国信息学国家队选拔IOI的队员,目前逐渐发展为全球热门的线上赛事,成为美国大学申请条件下,含金量相当高的官方竞赛。USACO的比赛成绩可以助力计算机专业留学,越来越多的学生进入了康奈尔,麻省理工,普林斯顿,哈佛和耶鲁等大学,这些同学的共同点是他们都参加了美国计算机科学竞赛(USACO),并且取得过非常好的成绩。适合参赛人群USACO适合国内在读学生有意向申请美国大学的或者想锻炼自己编程能力的同学,高三学生也可以参加12月的第_usaco可以多次提交吗

MySQL存储过程和自定义函数_mysql自定义函数和存储过程-程序员宅基地

文章浏览阅读394次。1.1 存储程序1.2 创建存储过程1.3 创建自定义函数1.3.1 示例1.4 自定义函数和存储过程的区别1.5 变量的使用1.6 定义条件和处理程序1.6.1 定义条件1.6.1.1 示例1.6.2 定义处理程序1.6.2.1 示例1.7 光标的使用1.7.1 声明光标1.7.2 打开光标1.7.3 使用光标1.7.4 关闭光标1.8 流程控制的使用1.8.1 IF语句1.8.2 CASE语句1.8.3 LOOP语句1.8.4 LEAVE语句1.8.5 ITERATE语句1.8.6 REPEAT语句。_mysql自定义函数和存储过程

半导体基础知识与PN结_本征半导体电流为0-程序员宅基地

文章浏览阅读188次。半导体二极管——集成电路最小组成单元。_本征半导体电流为0

随便推点

【Unity3d Shader】水面和岩浆效果_unity 岩浆shader-程序员宅基地

文章浏览阅读2.8k次,点赞3次,收藏18次。游戏水面特效实现方式太多。咱们这边介绍的是一最简单的UV动画(无顶点位移),整个mesh由4个顶点构成。实现了水面效果(左图),不动代码稍微修改下参数和贴图可以实现岩浆效果(右图)。有要思路是1,uv按时间去做正弦波移动2,在1的基础上加个凹凸图混合uv3,在1、2的基础上加个水流方向4,加上对雾效的支持,如没必要请自行删除雾效代码(把包含fog的几行代码删除)S..._unity 岩浆shader

广义线性模型——Logistic回归模型(1)_广义线性回归模型-程序员宅基地

文章浏览阅读5k次。广义线性模型是线性模型的扩展,它通过连接函数建立响应变量的数学期望值与线性组合的预测变量之间的关系。广义线性模型拟合的形式为:其中g(μY)是条件均值的函数(称为连接函数)。另外,你可放松Y为正态分布的假设,改为Y 服从指数分布族中的一种分布即可。设定好连接函数和概率分布后,便可以通过最大似然估计的多次迭代推导出各参数值。在大部分情况下,线性模型就可以通过一系列连续型或类别型预测变量来预测正态分布的响应变量的工作。但是,有时候我们要进行非正态因变量的分析,例如:(1)类别型.._广义线性回归模型

HTML+CSS大作业 环境网页设计与实现(垃圾分类) web前端开发技术 web课程设计 网页规划与设计_垃圾分类网页设计目标怎么写-程序员宅基地

文章浏览阅读69次。环境保护、 保护地球、 校园环保、垃圾分类、绿色家园、等网站的设计与制作。 总结了一些学生网页制作的经验:一般的网页需要融入以下知识点:div+css布局、浮动、定位、高级css、表格、表单及验证、js轮播图、音频 视频 Flash的应用、ul li、下拉导航栏、鼠标划过效果等知识点,网页的风格主题也很全面:如爱好、风景、校园、美食、动漫、游戏、咖啡、音乐、家乡、电影、名人、商城以及个人主页等主题,学生、新手可参考下方页面的布局和设计和HTML源码(有用点赞△) 一套A+的网_垃圾分类网页设计目标怎么写

C# .Net 发布后,把dll全部放在一个文件夹中,让软件目录更整洁_.net dll 全局目录-程序员宅基地

文章浏览阅读614次,点赞7次,收藏11次。之前找到一个修改 exe 中 DLL地址 的方法, 不太好使,虽然能正确启动, 但无法改变 exe 的工作目录,这就影响了.Net 中很多获取 exe 执行目录来拼接的地址 ( 相对路径 ),比如 wwwroot 和 代码中相对目录还有一些复制到目录的普通文件 等等,它们的地址都会指向原来 exe 的目录, 而不是自定义的 “lib” 目录,根本原因就是没有修改 exe 的工作目录这次来搞一个启动程序,把 .net 的所有东西都放在一个文件夹,在文件夹同级的目录制作一个 exe._.net dll 全局目录

BRIEF特征点描述算法_breif description calculation 特征点-程序员宅基地

文章浏览阅读1.5k次。本文为转载,原博客地址:http://blog.csdn.net/hujingshuang/article/details/46910259简介 BRIEF是2010年的一篇名为《BRIEF:Binary Robust Independent Elementary Features》的文章中提出,BRIEF是对已检测到的特征点进行描述,它是一种二进制编码的描述子,摈弃了利用区域灰度..._breif description calculation 特征点

房屋租赁管理系统的设计和实现,SpringBoot计算机毕业设计论文_基于spring boot的房屋租赁系统论文-程序员宅基地

文章浏览阅读4.1k次,点赞21次,收藏79次。本文是《基于SpringBoot的房屋租赁管理系统》的配套原创说明文档,可以给应届毕业生提供格式撰写参考,也可以给开发类似系统的朋友们提供功能业务设计思路。_基于spring boot的房屋租赁系统论文