让 FastRoute 支持 URL 反向解析

FastRoute 是一款高性能路由组件,需要运行于 PHP 7.1 及以上版本。很多 PHP Web 框架都用它作为默认路由。然而这款路由组件不支持 URL 反向解析。

Hyperf 框架也采用了这款路由组件,以下代码可以让 Hyperf 支持 URL 反向解析:

PHP<?php
/**
 * Router.php
**/
declare(strict_types=1);

namespace App\Http;

use Hyperf\HttpServer\Router\Router as BaseRouter;

class Router extends BaseRouter
{
    /**
     * @var array
     */
    protected static $routeMethods = ['get', 'post', 'put', 'delete', 'patch', 'head'];

    /**
     * @var string
     */
    protected static $currentGroupPrefix;

    protected static function createUrl($route)
    {
        return new Url(static::$currentGroupPrefix . $route);
    }

    public static function addGroup(string $prefix, callable $callback, array $options = [])
    {
        $previousGroupPrefix = static::$currentGroupPrefix;
        static::$currentGroupPrefix = $previousGroupPrefix . $prefix;

        $router = static::$factory->getRouter(static::$serverName);
        $router->addGroup($prefix, $callback, $options);

        static::$currentGroupPrefix = $previousGroupPrefix;
    }

    public static function addRoute($httpMethod, string $route, $handler, array $options = [])
    {
        $router = static::$factory->getRouter(static::$serverName);
        $router->addRoute($httpMethod, $route, $handler, $options);

        return static::createUrl($route);
    }

    public static function __callStatic($name, $arguments)
    {
        $returnValue = parent::__callStatic($name, $arguments);

        return in_array($name, static::$routeMethods) ? static::createUrl($arguments[0]) : $returnValue;
    }
}
PHP<?php
/**
 * Url.php
**/
declare(strict_types=1);

namespace App\Http;

use FastRoute\RouteParser\Std as RouteParser;
use LogicException;

class Url
{
    /**
     * @var string
     */
    public $route;

    /**
     * @var string
     */
    public $name;

    public function __construct(string $route)
    {
        $this->route = $route;
    }

    public function name(string $name)
    {
        $this->name = $name;
        UrlManager::getManager()->addUrl($this);

        return $this;
    }

    public function parse(...$arguments)
    {
        $routeParser = new RouteParser;
        $routes = $routeParser->parse($this->route);

        foreach ($routes as $route) {
            $parsedUrl = '';
            $i = 0;
            foreach ($route as $part) {
                if (is_string($part)) {
                    $parsedUrl .= $part;
                    continue;
                }
    
                if ($i === count($arguments)) {
                    throw new LogicException('Not enough parameters given');
                }
                $parsedUrl .= $arguments[$i++];
            }
    
            if ($i === count($arguments)) {
                return $parsedUrl;
            }
        }

        throw new LogicException(
            sprintf('The arguments does not matched for the route \'%s\'.', $this->name)
        );
    }
}
PHP<?php
/**
 * UrlManager.php
**/
declare(strict_types=1);

namespace App\Http;

use RuntimeException;

class UrlManager
{
    /**
     * @var UrlManager
     */
    protected static $manager;

    /**
     * @var array
     */
    protected $urls = [];

    public function addUrl(Url $url)
    {
        if (isset($this->urls[$url->name])) {
            throw new RuntimeException(
                sprintf('Cannot add the url named \'%s\'. A duplicate name exists.', $url->name)
            );
        }

        $this->urls[$url->name] = $url;
    }

    public function reverse(string $name, ...$arguments)
    {
        if (! isset($this->urls[$name])) {
            throw new RuntimeException(
                sprintf('The url named \'%s\' does not exists.', $name)
            );
        }

        return $this->urls[$name]->parse(...$arguments);
    }

    /**
     * @return UrlManager
     */
    public static function getManager()
    {
        if (! isset(static::$manager)) {
            static::$manager = new static();
        }

        return static::$manager;
    }
}

修改项目中的 config\routes.php 配置文件:

PHP<?php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://doc.hyperf.io
 * @contact  [email protected]
 * @license  https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
 */

use App\Http\Router;

Router::get('/user/{id}', 'App\Controller\UserController@info')->name('user-info');

使用 URL 反向解析:

PHP<?php

use App\Http\UrlManager;

// 返回 '/user/123'
$url = UrlManager::getManager()->reverse('user-info', 123);