awesomite/chariot

这只是另一个路由库

v0.5.0 2018-03-28 21:45 UTC

This package is auto-updated.

Last update: 2024-09-13 22:14:07 UTC


README

Codacy Badge Build Status Coverage Status

这只是另一个路由库。它使URL易于人类使用,同时代码易于程序员使用。使用树来获得最佳性能。

github.com/awesomite/chariot

为什么?

为了简化创建易于人类使用的URL。

<?php

/** @var Awesomite\Chariot\RouterInterface $router */
echo $router->linkTo('showArticle')->withParam('id', 5);

目录

它是如何工作的?

模式

模式旨在最大程度地简化在您的应用程序中创建路由。模式可以包含用双大括号 {{ }} 包裹的参数。

参数

参数包含三个值,由一个或多个空白字符分隔。第二个和第三个值是可选的。第一个值仅是名称。第二个值是一个正则表达式或已注册的正则表达式的名称,默认值为 [^/]+。第三个值包含参数的默认值(用于生成链接)。

示例

我相信最好的文档来自现实世界的示例。以下模式应该有助于您了解它是如何工作的。

  • /page/{{ page :uint }}
  • /page/{{page:uint}} (第一个参数和第二个参数之间不需要空格,如果第二个参数以 : 开头)
  • /page/{{page \d+ 1}}
  • /categories/{{ name [a-zA-Z0-9-]+ }}
  • /categories/{{ categoryName }}/item-{{ itemId :uint }}

路由

<?php

use Awesomite\Chariot\Pattern\PatternRouter;
use Awesomite\Chariot\HttpMethods;
use Awesomite\Chariot\Exceptions\HttpException;

$router = PatternRouter::createDefault();
$router->addRoute(HttpMethods::METHOD_GET, '/', 'home');

$method = 'GET';
$path = '/';

try {
    $route = $router->match($method, $path);
    $handler = $route->getHandler();
    echo $handler, "\n";
} catch (HttpException $exception) {
    echo $exception->getMessage(), "\n";
    
    // code can be equal to 404 or 405
    if ($exception->getCode() === HttpException::HTTP_METHOD_NOT_ALLOWED) {
        echo 'Allow: ', implode(', ', $router->getAllowedMethods($path)), "\n";   
    }
}

生成链接

<?php

use Awesomite\Chariot\Pattern\PatternRouter;
use Awesomite\Chariot\HttpMethods;

$router = PatternRouter::createDefault();
$router->addRoute(HttpMethods::METHOD_GET, '/category-{{ category :int }}', 'showCategory');

echo $router->linkTo('showCategory')->withParam('category', 5), "\n";
/*
 * Output:
 * /category-5 
 */

隐藏参数

隐藏参数是一个非常有用的机制。使用它们允许您在不更改大量代码的情况下完全更改应用程序中的路由。让我们看看以下场景

  1. 您网站中有一个分类页面。
  2. 分类页面的路径模式等于 /category-{{ id :uint }}
  3. 在您的代码中许多地方生成了分类页面的链接。让我们说 100
  4. 您想改变方法。链接中不再期望有分类的id。您想要有人类友好的链接,例如 /books 而不是 /category-15
  5. 使用 老式 的生成链接方法迫使您在 100 个地方重写代码。您必须花费时间来重写代码。错误的风险很高。

而不是单调地重写代码,您只需更改路由中的一个地方。这种方法可以帮助您节省时间并保护您的代码免受错误的影响。以下代码示例应该有助于您了解如何重写路由。

旧代码

$router->get('/category-{{ id :uint }}', 'showCategory');

新代码

$router
    ->get('/fantasy', 'showCategory', ['id' => 1])
    ->get('/comedy', 'showCategory', ['id' => 2]);

注意:在这种情况下,少量分类(例如100个)不会引起性能问题。但请记住 - 大量路由分配给一个处理程序可能会减慢生成链接的速度。我鼓励您在您的机器上执行性能测试。此存储库中附带的示例测试,执行以下命令以执行它

git clone --depth 1 git@github.com:awesomite/chariot.git
cd chariot
composer update
php speedtest/console.php test-links

更大的示例

<?php

use Awesomite\Chariot\Pattern\PatternRouter;

$router = PatternRouter::createDefault();
$router->get('/show-first-page', 'showPage', [
    'page' => 1,
]);
$router->get('/page-{{ page :uint }}', 'showPage');

$route = $router->match('GET', '/show-first-page');
echo $route->getHandler(), "\n";
var_dump($route->getParams());
/*
 * Output:
 * showPage
 * array(1) {
 *   'page' =>
 *   int(1)
 * }
 */

echo $router->linkTo('showPage')->withParam('page', 1), "\n"; // /show-first-page
echo $router->linkTo('showPage')->withParam('page', 2), "\n"; // /page-2
/*
 * Output:
 * /show-first-page
 * /page-2
 */

缓存

<?php

use Awesomite\Chariot\Pattern\PatternRouter;

class RouterFactory
{
    private $cacheFile;

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

    public function rebuildRouter()
    {
        $router = $this->createRouter();
        file_put_contents($this->cacheFile, '<?php return ' . $router->exportToExecutable() . ';');
    }

    public function getRouter(): PatternRouter
    {
        return require $this->cacheFile;
    }

    private function createRouter(): PatternRouter
    {
        return PatternRouter::createDefault()
            ->get('/', 'showHome')
            ->get('/news', 'showNews', ['page' => 1])
            ->get('/news/{{ page :int }}', 'showNews');
    }
}

$factory = new RouterFactory(__DIR__ . DIRECTORY_SEPARATOR . 'router.cache');
// Executing this function once is enough, e.g. during warmup
$factory->rebuildRouter();
$router = $factory->getRouter();
// decorators are not cacheable, you must add them each time
// $router->addParamDecorator(new MyParamDecorator());

定义自定义模式

<?php

use Awesomite\Chariot\Pattern\PatternRouter;
use Awesomite\Chariot\Pattern\Patterns;

$categories = [
    'action',
    'adventure',
    'comedy',
];

$router = new PatternRouter(new Patterns());
$router->getPatterns()
    ->addPattern(':date', '[0-9]{4}-[0-9]{2}-[0-9]{2}')
    ->addEnumPattern(':category', $categories);

$router->get('/day-{{ date :date }}', 'showDay');
$route = $router->match('GET', '/day-2017-01-01');
echo $route->getParams()['date'], "\n"; // 2017-01-01

$router->get('/category-{{ category :category }}', 'showCategory');
$route = $router->match('GET', '/category-comedy');
echo $route->getParams()['category'], "\n"; // comedy

验证

Chariot 检查传入(路由)和传出(生成链接)的值是否正确。

注意:方法 PatternRouter->linkTo() 返回 LinkInterface 实例。请阅读说明以了解 toString()__toString() 方法之间的区别。

<?php

use Awesomite\Chariot\Pattern\PatternRouter;
use Awesomite\Chariot\Exceptions\HttpException;
use Awesomite\Chariot\Exceptions\CannotGenerateLinkException;

$router = PatternRouter::createDefault();
$router->get('/category-{{ categoryId :int }}', 'showCategory');

/*
 * The following code displays "Error 404"
 */
try {
    $route = $router->match('GET', '/category-books');
    echo "Handler:\n", $route->getHandler(), "\n";
    echo "Params:\n";
    var_dump($route->getParams());
} catch (HttpException $exception) {
    echo 'Error ', $exception->getCode(), "\n";
}

/*
 * The following code displays "Cannot generate link"
 */
try {
    echo $router->linkTo('showCategory')->withParam('categoryId', 'books')->toString(), "\n";
} catch (CannotGenerateLinkException $exception) {
    echo "Cannot generate link\n";
}

默认参数

<?php

use Awesomite\Chariot\Pattern\PatternRouter;

$router = PatternRouter::createDefault();
$router->get('/articles/{{ page :uint 1 }}', 'articles');
echo $router->linkTo('articles'), "\n";
echo $router->linkTo('articles')->withParam('page', 2), "\n";

/*
 * Output:
 * /articles/1
 * /articles/2
 */

转换参数

路由器可以转换从URL提取的参数(以及传递到URL的参数)。传递给方法addPattern()的对象必须实现PatternInterface接口。@参见 PatternInterface

<?php

use Awesomite\Chariot\Pattern\PatternInterface;
use Awesomite\Chariot\Pattern\PatternRouter;
use Awesomite\Chariot\Pattern\Patterns;
use Awesomite\Chariot\Pattern\StdPatterns\DatePattern;

$router = new PatternRouter(new Patterns());
/*
 * Passed object to method addPattern() must implement interface PatternInterface
 */
$router->getPatterns()->addPattern(':date', new DatePattern());
$router->get('/day/{{ day :date }}', 'showDay');
echo $router->linkTo('showDay')->withParam('day', new \DateTime('2017-07-07')), "\n";

/*
 * Output:
 * /day/2017-07-07
 */

提供者/装饰器

让我们想象以下场景

  1. 你已经准备了一个大型的Web应用程序。
  2. 应用程序使用以下模式来显示项目 /items/{{ id :int }}
  3. 下一个目标是添加项目的标题到URL(/items/{{ id :int }}-{{ title }})。

你只需更改URL模式,并在应用程序生成项目URL的任何地方添加代码 withParam('title', $title)。Chariot可以更好地解决此类问题并更快地解决。以下代码解释了如何使用提供者。参见

<?php

use Awesomite\Chariot\ParamDecorators\ParamDecoratorInterface;
use Awesomite\Chariot\Pattern\PatternRouter;
use Awesomite\Chariot\ParamDecorators\ContextInterface;

class TitleProvider implements ParamDecoratorInterface
{
    private $mapping;
    
    public function __construct(array $mapping)
    {
        $this->mapping = $mapping;
    }

    public function decorate(ContextInterface $context)
    {
        if ('showItem' !== $context->getHandler()) {
            return;
        }
        
        $id = $context->getParams()['id'] ?? null;
        $title = $this->mapping[$id] ?? null;
        
        if (null !== $title) {
            $context->setParam('title', $title);
        }
    }
}

$titleMapping = [
    1 => 'my-first-item',
    2 => 'my-second-item',
    3 => 'my-third-item',
];
$router = PatternRouter::createDefault();
$router->get('/items/{{ id :int }}-{{ title }}', 'showItem');

/*
 * Error, because title is not defined
 */
echo 'Without provider: ';
echo $router->linkTo('showItem')->withParam('id', 1), PHP_EOL;

/*
 * Valid URL, because title will be provided automatically
 */
$router->addParamDecorator(new TitleProvider($titleMapping));
echo 'With provider: ';
echo $router->linkTo('showItem')->withParam('id', 1), PHP_EOL;

/*
 * Output:
 * 
 * Without provider: __ERROR_CANNOT_GENERATE_LINK
 * With provider: /items/1-my-first-item
 */

默认模式

方法Awesomite\Chariot\Pattern\Patterns::createDefault()返回一个带有标准模式的Awesomite\Chariot\Pattern\Patterns实例

更多示例

许可

MIT - 阅读许可证

版本

版本号遵循语义版本控制2.0.0方案。