浅析Macaw路由框架

295天前 · 代码 · 352次阅读

一直想摸一个自己的PHP框架出来,无奈技术力不足。听前辈说看源码能快速提升码力,于是去翻了一些主流框架的源码开始啃。对着优雅的Laravel,十年磨一剑的TP看了一上午,压根毛都没看懂,自身实力还是太薄弱了,妄想一步登天是不可能滴,于是就想着先把基本的思想搞明白,试着解析一些简单的路由框架试试看。这不,Macaw就蹦出来了。

Macaw is a simple, open source PHP router. It's super small (~150 LOC), fast, and has some great annotated source code. This class allows you to just throw it into your project and start using it immediately.

Macaw是一个简单,开源的PHP路由器。它炒鸡小(大概150行代码),炒鸡快还有着精心注释过的源码。这个类允许你直接扔到你的项目里,开箱即用~

Oh,这太棒了。还有什么比这个更能快速上手路由器吗?这就开始!

Rewrite

首先看一下Macaw的重写规则,与一般的框架一样,将所有非静态资源全部请求至index.php。有了这个地址重写后就方便多了。

#302重定向,保留地址栏
rewrite ^/(.*)/$ /$1 redirect;
#判断是否为非静态资源
if (!-e $request_filename){
    #若为非静态资源则指向入口文件Index.php并停止之后的重写
    rewrite ^(.*)$ /index.php break;
}

用法

再来简单看看Macaw的使用方法,从Usage开始,顺瓜摸藤。

use NoahBuscher\Macaw\Macaw;

//普通的get请求
Macaw::get('/', function() {
    echo 'JOJO思密达';
});

//普通的post请求
Macaw::post('/', function() {
    echo '就用这个砍下承太郎的脑袋';
});
//当然还有delete等等

//使用lambda URIs
Macaw::get('/(:num)', function($id){
    echo "id is ".$id;
});

Macaw::get('/(:any)', function($name){
    echo "name is ".$name;
});

//重头戏
Macaw::get('page', 'Controllers\demo@page');
Macaw::get('view/(:num)', 'Controllers\demo@view');

//执行路由
Macaw::dispatch();

可以看到Macaw支持各种请求方式,回调函数的调用也很自由。这就让我非常好奇,150行代码就能整出这么个麻雀虽小五脏俱全的路由器来了吗。

代码实现

见识到了Macaw的功能后我迫不及待地打开了源码想要一探究竟。好家伙,果真加上注释就174行代码。在仔细地学习研究之后,我大为震撼。下面开始我粗糙的分析。

类的一开头定义了一些静态的数组与变量与函数。

  • $routes用于存储路由路径(/view/(:num), /page/about...)
  • $methods用处储存路由访问方式(get, post...)
  • $callbacks用于存储回调函数,其中有的为字符串,有的则为直接的函数
  • $patterns用于储存正则规则
  • $error_callback用于存储发生错误时要调用的回调函数
  • 其他俩$halts$maps暂时不知道是要干啥的

$routes$callbacks$methods 的下标是一一对应的。非常巧妙的存储方式。

public static $halts = false;
public static $routes = array();
public static $methods = array();
public static $callbacks = array();
public static $maps = array();
public static $patterns = array(
    ':any' => '[^/]+',
    ':num' => '[0-9]+',
    ':all' => '.*'
);
public static $error_callback;

接下来精彩的地方来了。我们不难发现,我们定义路由规则时,不同的规则调用的不同的函数。例如注册get请求使用的是Macaw::get()方法,而注册post请求时使用的是Macaw::post()方法。虽然我们用的是不同的函数,但是要执行的功能都是一致的,即将两个参数(路由路径和回调函数)压入上面的$routes$callbacks里面,再将对应的请求方法压入$methods里头。

可是HTTP有那么多请求方式,难道要一个个写出来吗?No,那太noob了。作者告诉了我们一个更加聪明的办法。没错,就是__callstatic()魔术方法。这类似与C++中的重载,而PHP所提供的重载(overloading)是指动态地创建类属性和方法。更加详细的解释可以看PHP: 重载 - Manual,我这里简单介绍一下。就是当你调用了一个本不存在的静态函数时(假如get方法),会调用到这个函数来动态创建一个名为get的静态函数并执行。而参数则为那个动态创建的函数名get和原本传入参数。

public static function __callStatic($method,$arg){  
    echo '你试图调用'.$method.'方法,可惜它不存在';
    echo '你还穿了个参数';
    var_dump($arg);
} 

有了这个,我们就可以很轻松地将多个HTTP请求方式写进一个函数里头

  /**
   * Defines a route w/ callback and method
   */
  public static function __callstatic($method, $params) {

    if ($method == 'map') {
        $maps = array_map('strtoupper', $params[0]);
        $uri = strpos($params[1], '/') === 0 ? $params[1] : '/' . $params[1];
        $callback = $params[2];
    } else {
        $maps = null;
        $uri = strpos($params[0], '/') === 0 ? $params[0] : '/' . $params[0];
        $callback = $params[1];
    }
    
    //将自定义的规则压入数组储存
    array_push(self::$maps, $maps);
    array_push(self::$routes, $uri);
    array_push(self::$methods, strtoupper($method));
    array_push(self::$callbacks, $callback);
  }

最后来看看最为关键的dispatch()方法。首先是一系列的准备工作,将各类参数抽丝剥茧取出来

$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$method = $_SERVER['REQUEST_METHOD'];

$searches = array_keys(static::$patterns);
$replaces = array_values(static::$patterns);

$found_route = false;

self::$routes = preg_replace('/\/+/', '/', self::$routes);

$uri记录了当前访问的路径,例如/user/1/setting$method则顾名思义记录了请求的HTTP方式。同时,还取了$patterns的键值和值作为$searches$replaces数组。再标记一下是否找到路由规则$found_route为否。为了方便之后的比对,将$routes数组中多个/同时出现的地方替换为单/

其次,判断是否能够直接在不考虑正则的情况下在已经注册的$routes中找到匹配的规则。如果找到了,那么就取出他的下标(们)来遍历。

    // Check if route is defined without regex
    if (in_array($uri, self::$routes)) {
      $route_pos = array_keys(self::$routes, $uri);
      foreach ($route_pos as $route) {

        // Using an ANY option to match both GET and POST requests
        if (self$maps[$route]) && in_array($method, self::$maps[$route]))) {
          $found_route = true;

          // If route is not an object
          if (!is_object(self::$callbacks[$route])) {

            // Grab all parts based on a / separator
            $parts = explode('/',self::$callbacks[$route]);

            // Collect the last index of the array
            $last = end($parts);

            // Grab the controller name and method call
            $segments = explode('@',$last);

            // Instanitate controller
            $controller = new $segments[0]();

            // Call method
            $controller->{$segments[1]}();

            if (self::$halts) return;
          } else {
            // Call closure
            call_user_func(self::$callbacks[$route]);

            if (self::$halts) return;
          }
        }
      }
    }

如果当前下标所对应的路由规则的方法与当前请求对应,或是规则中是ANY任意方法的话,就说明找到了匹配的路由,将$found_route标记为true,并开始尝试调用回调函数。如果回调函数数组$callbacks中对应的值就是一个函数时,直接调用那个就好。如果不是,而是一个字符串时,就分割字符串,实例化对象再调用回调函数。

正则模式下也是大同小异了。这就是Macaw运行的基本流程。

总结

前辈诚不欺我,阅读别人的代码果然可以快速增长自己的码力。Macaw简洁而高效的代码打开了我新世界的大门。这些函数,魔术方法都是学过的,可如何运用于实战,融会贯通却是一门学问。而在这之上,体现的则是设计模式的思维。看来要走的路还有很长啊。

👍 9

PHP 路由 Macaw

最后修改于295天前

评论

贴吧 狗头 原神 小黄脸
收起

贴吧

狗头

原神

小黄脸

  1. Kiosr 284天前

    学到了

    1. 季悠然 283天前

      ohh,是Kiosr

      1. Kiosr 280天前

        嘿嘿, 我又有时间折腾了

  2. SomeBottle 294天前

    有趣,__callstatic这种方法之前几乎没怎么用过,想不到能这样
    PHP的情况果然是入门简单,但是玩精不简单,因为他太方便了。
    有时候确实不能一味埋着头写代码,得看看别人的手笔...
    这样易懂的学习笔记摩多摩多

    1. 季悠然 294天前

      oh些瓶,给我摩多摩多

目录

avatar

季悠然

寻找有趣的灵魂

127

文章数

1954

评论数

3

分类

好热啊

arknights!

不敢打开信封啊。因为,打开了就结束了啊。

三千院凪

107