URL路由器中的组和中间件

时间:2015-09-03 08:29:10

标签: php url-routing

我正在构建自定义PHP框架。目标是让这段代码有效:

$app = new Router();

$app->group("/admin", function($app) {

    $app->group("/pages", function($app) {

        $app->get("/home", "AdminPages@home")
            ->before("before_home1")
            ->before("before_home2")
            ->after("after_home1")
            ->after("after_home2");


    })->before("before_pages1")->before("before_pages2")->after("after_pages1")->after("after_pages2");

})->before("before_admin1")->before("before_admin2")->after("after_admin1")->after("after_admin2");

目前,Router::get()会返回Route个对象,因此我可以添加前置和后置中间件(before()after(),并将其保存到Route::$before[]Route::$after[]),但我不知道如何继续。

问题是函数的顺序(在两个数组中)应该是

before_admin1
before_admin2
before_pages1
before_pages2
before_home1
before_home2
AdminPages@home
after_home1
after_home2
after_pages1
after_pages2
after_admin1
after_admin2

但是上面代码中的执行顺序是

before_home1
before_home2
before_pages1
before_pages2
before_admin1
before_admin2
AdminPages@home
after_home1
after_home2
after_pages1
after_pages2
after_admin1
after_admin2

将中间件调用按此顺序放入的最简单方法是什么? Router::group()应该返回什么?也许是另一个Router

1 个答案:

答案 0 :(得分:1)

如果您想拥有这种链接行为,则必须分两步执行路由:路由定义阶段,您可以在其中构建路径对象,以及路由评估阶段,您可以在其中查看每个匹配项,然后查看哪个匹配项。

实现对无限嵌套的支持的最直接的方法可能是构建嵌套的Route对象,并让递归整理所有“执行顺序”问题。

这应该让你开始:

class Route
{
    protected $method;
    protected $pattern;
    protected $controller;

    protected $parent = null;

    protected $before = array();
    protected $after = array();

    public function __construct($method, $pattern, $controller)
    {
        $this->method = $method;
        $this->pattern = $pattern;
        $this->controller = $controller;
    }

    public function setParent($parent)
    {
        $this->parent = $parent;
    }

    public function before($controller)
    {
        $this->before[] = $controller;
    }

    public function after($controller)
    {
        $this->after[] = $controller;
    }

    /* Returns itself if the provided method and URI match this route,
       otherwise returns null */
    public function match($method, $uri)
    {
        /* Match on simple equality for the sake of simplicity */
        return $uri === $this->getFullPattern() && $method === $this->method ?
            $this : null;
    }

    protected function getFullPattern()
    {
        /* Recursively concatenate all parent patterns */
        return is_null($this->parent) ?
            $this->pattern :
            $this->parent->getFullPattern() . $this->pattern;
    }

    public function dispatch()
    {
        $this->runBefore();

        /* Call controller function */

        $this->runAfter();
    }

    public function runBefore()
    {
        /* Run the before filters on the parent first */
        if(!is_null($this->parent))
        {
            $this->parent->runBefore();
        }

        foreach($this->before as $controller)
        {
            /* Execute before filter */
        }
    }

    public function runAfter()
    {
        foreach($this->after as $controller)
        {
            /* Execute after filter */
        }

        /* Run the after filters on the parent next */
        if(!is_null($this->parent))
        {
            $this->parent->runAfter();
        }
    }
}

/* A router is considered a special "group" route */
class Router extends Route
{
    protected $routes = array();

    public function __construct($pattern = "")
    {
        parent::__construct(null, $pattern, null);
    }

    public function addChild($route)
    {
        $this->routes[] = $route;
        $route->setParent($this);
        return $route;
    }

    public function group($pattern, $func)
    {
        $child = new Router($pattern);
        $this->addChild($child);
        call_user_func($func, $child);
        return $child;
    }

    public function get($pattern, $controller)
    {
        return $this->addChild(new Route("GET", $pattern, $controller));
    }

    /* And the same goes for POST, PUT, DELETE, etc. */

    /* Returns the child route that matches the provided parameters,
       or null if there is no match; since we are calling 'match' on
       each child, we perform a recursive matching */
    public function match($method, $uri)
    {
        foreach($this->routes as $route)
        {
            $result = $route->match($method, $uri);
            if($result instanceof Route)
            {
                return $result;
            }
        }
        return null;
    }

    public function dispatch()
    {
        throw new Exception("Group routes cannot be dispatched.");
    }
}

我根本没有测试此代码,所以请谨慎行事。