start_php_framework - 基础篇 - 1.4 Implementation of router

这篇博客介绍了如何在PHP框架中实现简单的路由器。通过创建新的命名空间、定义项目结构,然后设置路由表,根据请求信息匹配并调用相应类和方法。作者提供了创建路由更新脚本和读取路由表的代码示例,并展示了如何处理不同URL路径以实现基本的路由功能。项目结构和源代码链接也一并给出。
摘要由CSDN通过智能技术生成
1.4 Implementation of router

下一步我们实现简单的路由,来进行controller的调用

  • Step1:在start_php_framework根文件夹下新建application文件夹,然后修改composer.json,新增一个命名空间app。修改之后重新在cmd中进入start_php_framework根文件夹,然后运行composer install,新命名空间会自动生效。修改的内容如下:
{
    ...
    "autoload": {
        "psr-4": {
            "core\\": "core",
            "app\\": "application"
        }
    }
    ...
}
  • Step2:在application文件夹下新建home文件夹,用于表示默认模块。再在home文件夹下新建controller文件夹,这就是接下来的主战场了。
  • Step3:在上述controller文件夹下新建Index.php,内容如下:
<?php

namespace app\home\controller;

class Index
{
    public function index()
    {
        // 当前目标是刷新浏览器后能输出如下字符串
        echo 'Current location: start_php_framework\application\home\controller\Index\index';
    }
}
  • Step4:实现路由的大体思路是:设置路由表,获取到URI信息后与路由表进行对比,如果URI合法,就调用相应的类和方法,否则抛错,找不到页面。路由表中设置两种方式,一种是设置合法的字符串,URI与字符串进行对比;还有一种是设定合法命名空间,接收到的URI去判定命名空间下的类及方法是否存在。为了加快判定速度,路由文件设置好之后我们运行一个启动脚本,刷新路由表,直接存储到文件中。当路由配置更新后重新运行脚本,更新文件中的路由信息。于是问题在此退化为:根据路由配置编写脚本,生成路由文件。附一个config/router.php的内容:
<?php

/**
 * 路由表,两种配置方式:
 *   第一种:进行字符串的拼接,对比pathinfo
 *   第二种:使用命名空间进行配置,那么需要查看类内的方法是否存在
 */
return [
    'path' => [
        ['/', '\app\home\controller\Index\index', 'get'],
        ['/home/index/index', '\app\home\controller\Index\index', 'get'],
        ['/home/index/index2', '\app\home\controller\Index\index2', 'get'],
    ],
    'namespace' => [
        // 模块级别
        '\app\home\controller' => [
            // 模块内控制器
            'Index' => [
                ['index', 'get'],
            ],
        ],
    ],
];
  • Step5:编写路由更新脚本文件,命名为script_update_router.php,放置在start_php_framework/web目录下,其内容如下(编写之后进入start_php_framework/web目录下执行php script_update_router.php来生成路由表,执行之后会自动在根目录创建runtime文件夹及json格式的路由表):
<?php

require_once '../vendor/autoload.php';

class Router
{
    public static function updateRouter()
    {
        $routerConfig = include_once '../config/router.php';

        $router = [];

        // 处理字符串类型的路由配置
        if (!empty($routerConfig['path'])) {
            $router = array_merge($router, self::processPathRouter($routerConfig['path']));
        }

        // 处理字符串类型的路由配置
        if (!empty($routerConfig['namespace'])) {
            $router = array_merge($router, self::processNamespaceRouter($routerConfig['namespace']));
        }

        // var_dump($router);
        self::saveRouterToFile($router);
    }

    /**
     * 处理命名空间类型的路由配置
     */
    private static function processPathRouter($pathRouter = [])
    {
        if (empty($pathRouter)) {
            return [];
        }

        $router = [];
        foreach ($pathRouter as $kRouter => $vRouter) {
            $router[strtolower($vRouter[0])] = $vRouter;
        }

        return $router;
    }

    /**
     * 处理命名空间类型的路由配置
     */
    private static function processNamespaceRouter($nsRouter = [])
    {
        if (empty($nsRouter)) {
            return [];
        }

        $router = [];
        foreach ($nsRouter as $kNs => $vNs) {
            foreach ($vNs as $kRouter => $vRouter) {
                list($empty, $app, $moduleName) = explode('\\', $kNs);
                if ($vRouter[0] == '*') {
                    // 如果是通配符,就反射获取类下面的public方法,统统放到路由表中
                    $reflection = new ReflectionClass($kNs . '\\' . $kRouter);
                    $methods = $reflection->getMethods(ReflectionMethod::IS_PUBLIC);
                    foreach ($methods as $kName => $vName) {
                        $router[strtolower('/' . $moduleName . '/' . $kRouter . '/' . $vName->name)] = ['/' . $moduleName . '/' . $kRouter . '/' . strtolower($vName->name), $kNs . '\\' . $kRouter . '\\' . strtolower($vName->name), '*'];
                    }
                } else {
                    foreach ($vRouter as $kName => $vName) {
                        $router[strtolower('/' . $moduleName . '/' . $kRouter . '/' . $vName[0])] = ['/' . $moduleName . '/' . $kRouter . '/' . strtolower($vName[0]), $kNs . '\\' . $kRouter . '\\' . strtolower($vName[0]), $vName[1]];
                    }
                }
            }
        }

        return $router;
    }

    /**
     * 将路由数组写入文件
     */
    private static function saveRouterToFile($routerArr)
    {
        $routerFile = '../runtime/router/router.json';
        if (!file_exists('../runtime/router/')) {
            $mkRes = mkdir('../runtime/router/', 0777, true);
            if (!$mkRes) {
                return '没有相对应的文件夹,并且创建失败';
            }
        }

        $fd = fopen($routerFile, 'w');
        fwrite($fd, json_encode($routerArr));
        fclose($fd);
    }
}

if (file_exists('../config/router.php')) {
    Router::updateRouter();
} else {
    echo '../config/router.php not exist';
}
  • Step6:路由表已经创建,接下来创建start_php_framework/core/Router.php对其进行读取,内容如下:
<?php

namespace core;

class Router
{
    public static function getRouterTable()
    {
        $routerFile = '../runtime/router/router.json';
        $routerTableJson = file_get_contents($routerFile);

        $routerTable = json_decode($routerTableJson, true);
        // var_dump($routerTable);
        return $routerTable;
    }
}
  • Step7:为了配合测试路由表,更新start_php_framework/application/home/controller/Index.php的内容,更新之后其内容如下:
<?php

namespace app\home\controller;

// use core\Db;

class Index
{
    public function index()
    {
        echo 'Current location: start_php_framework\application\home\controller\Index\index';
    }

    public function index2()
    {
        echo 'Current location: start_php_framework\application\home\controller\Index\index2';
    }

    private function index3()
    {
        echo 'Current location: start_php_framework\application\home\controller\Index\index3';
    }
}
  • Step8:在App.php中调用Step8中新建的Router.php
    更新后的App.php内容如下:
<?php

namespace core;

use core\Router;

class App
{
    public static $cfg;

    /**
     * 框架运行入口
     */
    public static function run()
    {

        $routerTable = Router::getRouterTable();

        $uri = $_SERVER['REQUEST_URI'];
        $uri = str_replace('/index.php', '', $uri);

        $expRes = explode('/', $uri);
        if (isset($expRes[1]) && $expRes[1] != '') {
            // 有1就一定有2和3,路由使用严格模式,不进行默认的猜测
            $module = $expRes[1];
            $controller = $expRes[2];
            $function = $expRes[3];

            $callStr = strtolower('/' . $module . '/' . $controller . '/' . $function);
            if (array_key_exists($callStr, $routerTable)) {
                $call = $routerTable[$callStr];
            } else {
                // TODO.. 后续可以替换为自定义的404页面
                http_response_code(404);
                die;
            }
        } else {
            $module = '';
            $controller = '';
            $function = '';

            // 未指定模块,调用“/”对应的路由
            $call = $routerTable['/'];
        }

        if (strtolower($_SERVER['REQUEST_METHOD']) != strtolower($call[2])) {
            return '请求方式不匹配,请检查URI的请求方式。。';
        }

        $callInfo = explode('\\', $call[1]);
        $functionName = array_pop($callInfo);
        $className = implode('\\', $callInfo);

        $class = new $className();
        $response = $class->$functionName();
    }
}
  • Step9:关闭入口文件index.php中的var_dump,理论上所有的输出以后已经转移到controller层,关闭后index.php内容如下:
<?php

// composer自动加载
require_once '../vendor/autoload.php';

// App
require_once '../core/App.php';

\core\App::run();
  • Step10:另外再实现一下隐藏url中的index.php的功能,在StartPHP/web目录下新建.htaccess文件(因为此处我使用的Apache作为Web Server,所以rewrite需要配合.htaccess文件来实现,Nginx的话此前给出的样例配置中已经实现了rewrite),.htaccess内容如下:
<IfModule mod_rewrite.c>
  Options +FollowSymlinks -Multiviews
  RewriteEngine On

  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteRule ^(.*)$ index.php?/$1 [QSA,PT,L]
</IfModule>
  • Step11:浏览器输入http://zsc.spf.com/http://zsc.spf.com/home/index/index会得到一样的输出Current location: start_php_framework\application\home\controller\Index\index。而访问http://zsc.spf.com/home/index/index2则会得到输出:Current location: start_php_framework\application\home\controller\Index\index2。访问http://zsc.spf.com/home/index/index3则会报404错误,至此,路由已经初步实现。

项目结构

此时的项目目录结构:

start_php_framework         [框架根目录]
  ├─ application            [应用运行主目录]
  │    └─ home              [默认模块]
  │         └─ controller   [home模块的控制器文件夹]
  ├─ config                 [配置文件目录]
  │    ├─ config.php        [主配置文件]
  │    ├─ db.php            [数据库配置文件]
  │    └─ router.php        [路由配置文件]
  ├─ core                   [框架核心源码目录]
  │    ├─ db                [各类数据库驱动文件存储目录]
  │    │   └─ Mysql.php     [MySQL连接驱动]
  │    ├─ App.php           [应用启动文件]
  │    ├─ Config.php        [读取配置文件]
  │    ├─ Db.php            [数据库操作文件]
  │    └─ Router.php        [获取路由表的文件]
  ├─ runtime                [未来存放路由表、运行日志等文件]
  │    └─ router            [存放路由表的文件夹]
  │        └─ router.json   [json格式的路由表]
  ├─ vendor                 [composer自有文件夹,将来存储第三方扩展]
  │    ├─ composer          [composer自有文件夹]
  │    └─ autoload.php      [自动加载关键文件,一定要在入口文件引用,且在App.php之前]
  ├─ web                    [框架入口]
  │    ├─ .htaccess         [Apache重定向描述文件]
  │    ├─ index.php         [框架入口文件]
  │    └─ script_update_router.php         [更新路由表的脚本]
  └─ composer.json          [composer描述文件]

项目地址:https://gitee.com/JiDiYanHuo/start_php_framework

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值