浅析Macaw路由框架

493天前 · 代码 · 578次阅读

一直想摸一个自己的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:$methods[$route] == 'ANY' || (!empty(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

最后修改于493天前

评论

贴吧 狗头 原神 小黄脸
收起

贴吧

  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡
  • 贴吧泡泡

狗头

  • 狗头
  • 狗头
  • 狗头
  • 狗头
  • 狗头
  • 狗头
  • 狗头
  • 狗头
  • 狗头
  • 狗头
  • 狗头
  • 狗头

原神

  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神
  • 原神

小黄脸

  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  • 小黄脸
  1. Kiosr 482天前

    学到了

    1. 季悠然 481天前

      ohh,是Kiosr chaiquan_love

      1. Kiosr 477天前

        嘿嘿, 我又有时间折腾了 huaji_pc

  2. SomeBottle 492天前

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

    1. 季悠然 492天前

      oh些瓶,给我摩多摩多 huaji_pc

目录

avatar

季悠然

寻找有趣的灵魂

135

文章数

2020

评论数

3

分类

好热啊

arknights!

如果不能忠于自己的心,胜负又有什么价值呢?

1092