earc/router

eArc - 显式架构框架 - 路由组件

4.0 2021-05-24 15:21 UTC

This package is auto-updated.

Last update: 2024-09-24 22:57:27 UTC


README

这是 earc 框架的路由组件,它也可以在其他框架中使用或作为独立组件使用。

eArc 路由器不使用任何配置的路由 - 它们通过文件系统表达,文件系统被转换为观察者树。

鉴于 URL 和目录结构之间的直接映射,理解应用的路由过程就像在路由目录的根部输入 tree 命令一样简单。

目录

安装

$ composer require earc/router

引导

将以下代码片段放置在您的脚本/框架引导部分。

1. 使用 composer 命名空间驱动的自动加载。

require_once '/path/to/your/vendor/autoload.php';

2. 然后引导 earc/di 进行依赖注入和 earc/core 进行配置文件。

use eArc\Core\Configuration;
use eArc\DI\DI;

DI::init();
Configuration::build();

3. 配置路由(见 配置)。

4. 然后派发路由事件以调用相应的控制器。

use eArc\Router\RouterEvent;

$event = new RouterEvent();
$event->dispatch();

配置

earc/router 使用 earc/event-tree 将路由事件传递给目录结构表示的观察者。您需要在您的命名空间中创建一个文件夹,作为事件树的根目录。

将参数放在位于 vendor 目录下的名为 .earc-config.php 的文件中。

<?php #.earc-config.php

return ['earc' => [
    'is_production_environment' => false,
    'event_tree' => [
        'directories' => [
            'earc/router/earc-event-tree' => 'eArc\\RouterEventTreeRoot',
            // your configuration part:
            '../path/to/your/eventTree/root/folder' => 'NamespaceOfYour\\EventTreeRoot',
        ]   
    ]
]];

事件树根目录的路径必须是绝对路径或相对于您的项目 vendor 目录的相对路径。

当然,您可以使用 YAML 文件来定义配置数组。

<?php #.earc-config.php

return Yaml::parse(file_get_contents('/path/to/your/config.yml'));

基本用法

由于我们使用现代操作系统的本地树数据结构来组织我们的代码,定义我们的路由和目标控制器只需一小步。

这真是太简单了。

  1. 转到事件树根目录并创建一个新的子目录 routing
  2. 对于每个路由,为固定部分创建子目录,并在末尾放置一个扩展 eArc\Router\AbstractController 的类。
  3. 使用 process() 方法和传递的 RouterEvent 来挂钩您的控制器逻辑。

控制器

假设您计划使用以下 URL /admin/user/admin/user/edit/{id}/admin/user/add/admin/user/delete 来实现它们明显的作用,您有两种选择

  1. 要么在 routing/admin/user 目录中放置一个控制器(没有名为 的子目录名为 editadddelete)。然后所有用户管理逻辑都必须在这个控制器中实现。

  2. 或者,您可以将一个控制器放置在 routing/admin/user 目录中,第二个放在 routing/admin/user/edit 目录中,第三个放在 routing/admin/user/add 目录中,最后一个放在 routing/admin/user/delete 目录中。

第二种方法是推荐的。

控制器可能看起来像这样

namespace NamespaceOfYour\EventTreeRoot\routing\admin\user\edit;

use eArc\Router\AbstractController;
use eArc\Router\Interfaces\RouterEventInterface;

class Controller extends AbstractController
{
    public function process(RouterEventInterface $event) : void
    {
        //... your controller code goes here

        //... a very basic example without form processing:

        // the parameters are the route arguments that does not match a directory        
        $id = $event->getRoute()->getParam(0);
        // if you use doctrine the next step could look like this
        $user = di_get(UserRepository::class)->find($id);
        // calling some third party rendering engine    
        di_get(EngineInterface::class)->render('templates/user/edit.html', ['user' => $user]);
    }
}

每个控制器中的动作比传统的方法调用参数化方式更为重要

  1. 这迫使程序员将业务逻辑从控制器中移出。
  2. 每个动作都暴露于预处理和后处理。您可以在不触碰文件系统的情况下添加逻辑。(有关详细信息,请参阅通过附加到路由的监听器实现的预处理和后处理via listeners attached to the route。)

如果您想坚持传统方式,您可以在扩展 AbstractController 的抽象 BaseController 中实现逻辑。或者使用 earc 路由器的 生命周期钩子(推荐)。对于参数化方法调用逻辑本身,您最多需要三行代码。

由于您的控制器都具有不同的命名空间,您可以将它们全部命名为 Controller。但建议使用更明确的方式来命名它们。

响应控制器

AbstractController 是一个传统的 earc/event-tree 监听器,AbstractResponseController 则更接近于路由的需求,它从事件处理中迈出一步。它自2.1版本开始提供,支持参数注入和转换(通过 earc/parameter-transformer)。

提示:选择哪种控制器类型是一个架构决策,这取决于您的项目。传统的Web应用应使用 AbstractResponseController,而事件驱动的设计可能更倾向于使用 AbstractController

这种类型的控制器可能看起来像这样

namespace NamespaceOfYour\EventTreeRoot\routing\admin\user\edit;

use eArc\Router\AbstractResponseController;
use eArc\Router\Interfaces\ResponseInterface;
use eArc\Router\Response;

class Controller extends AbstractResponseController
{
    public function respond(?User $userObject) : ResponseInterface
    {
        //... your controller code goes here

        //... a very basic example without form processing:

        // calling some third party rendering engine    
        return new Response(di_get(EngineInterface::class)->render('templates/user/edit.html', ['user' => $userObject]));
    }
}

提示:可以为空类型将字符串参数 'null' 转换为 null 值。这使得将 null 值作为 URL 的一部分发送成为可能。

控制器代码看起来更简洁,节省了视图行。earc 路由器知道 PHP 内置的原始类型,通过 earc/data 定义的实体,通过 earc/di 可以构建的所有服务,以及它附带的所有接口(RouterEventInterfaceRouteInformationInterfaceRequestInformationInterface)。

您必须扩展现有逻辑以支持其他类型提示。这可以通过两种不同的方式完成。(示例使用众所周知的 doctrine orm。)

实现类特定类型提示支持

要支持单个类的类型提示,实现 ParameterTransformerFactoryInterface

   use eArc\ParameterTransformer\Interfaces\ParameterTransformerFactoryInterface;
   
   class User implements ParameterTransformerFactoryInterface
   {
       //...
   
       public static function buildFromParameter($parameter): static
       {
           return di_get(EntityManagerInterface::class)
               ->getRepository(User::class)
               ->find($parameter);
       }
   }

现在,如果 RequestuserObject 参数包含有效的用户 ID,则将 User 对象注入到 respond 方法中。

实现一类类类型提示支持

要支持更广泛的类,让一个服务实现 ParameterTransformerFactoryServiceInterface 并进行标记。

use eArc\ParameterTransformer\Interfaces\ParameterTransformerFactoryServiceInterface;

class ParameterTransformerExtension implements ParameterTransformerFactoryServiceInterface
{
   public function buildFromParameter(string $fQCN, $parameter) : object|null
   {
       return di_get(EntityManagerInterface::class)
           ->getRepository($fQCN)
           ?->find($parameter);
   }
}

// ...

di_tag(ParameterTransformerFactoryServiceInterface::class, ParameterTransformerExtension::class);

您可以将这两种方法混合使用。使用 ParameterTransformerFactoryServiceInterface 接口的方法在代码效率上更高,但 ParameterTransformerFactoryInterface 在运行时上略胜一筹。

类型提示请求键映射

由于 Route 参数通过数字访问,您不能将 respond 方法的参数命名为相同的名称。

对于所有情况,参数名称不能与您可以定义的输入变量匹配,您可以定义一个映射。只需覆盖 AbstractResponseControllergetInputKeyMapping 方法即可。

namespace NamespaceOfYour\EventTreeRoot\routing\admin\user\edit;

use eArc\Router\AbstractResponseController;
use eArc\Router\Interfaces\ResponseInterface;
use eArc\Router\Interfaces\RouterEventInterface;

class Controller extends AbstractResponseController
{
    public function respond(?User $userObject, ?string $name) : ResponseInterface
    {
        // ...
    }
    
    protected function getInputKeyMapping(RouterEventInterface $event): array
    {
        return [
            'userObject' => 0,
            'name' => 'new_name',
        ];
    }
}

预定义类型提示

响应控制器有三个预定义的类型提示

  1. RouterEventInterface
  2. RouteInformationInterface
  3. RequestInformationInterface 您可以通过类型提示直接访问相关对象。要扩展预定义的类型提示,请扩展 AbstractResponseController 并重写 getPredefinedTypeHints 方法。
namespace NamespaceOfYour\EventTreeRoot\routing\admin\user\edit;

use eArc\Router\AbstractResponseController;
use eArc\Router\Interfaces\RequestInformationInterface;
use eArc\Router\Interfaces\ResponseInterface;
use eArc\Router\Interfaces\RouteInformationInterface;
use eArc\Router\Interfaces\RouterEventInterface;

class Controller extends AbstractResponseController
{
    public function respond(MyControllerService $service, ?User $userObject, ?string $name = 'Default Name') : ResponseInterface
    {
        // ...
    }
    
    protected function getPredefinedTypeHints(RouterEventInterface $event): array
    {
        return [
            MyControllerService::class => di_get(MyControllerServiceFactory::class)->build($event),
            RouterEventInterface::class => $event,
            RouteInformationInterface::class => $event->getResponse(),
            RequestInformationInterface::class => $event->getRequest(),
        ];
    }
}

类型提示默认值

如果转换结果为 null 值,则使用默认参数值。如果没有提示默认值,则使用 null 值。

提示:关于转换过程的详细文档可以在 earc/parameter-transformer 页面上找到。

路由事件

路由事件总是使用 URL、请求方法和变量来构建。每个路由事件实例总是绑定到有效的请求,并且如果需要,可以轻松序列化和保存以供以后使用。通过向事件传递额外的参数,您可以覆盖 URL、请求方法和变量。

use eArc\Router\RouterEvent;

$event = new RouterEvent(
    $url,
    $requestMethod,
    $requestVariables
);

$event->dispatch();

这为您提供了进行任何必要的预处理、重建保存的请求或模拟某些集成测试的能力。

如果没有提供或以 null 值传递,则 URL 设置为从 $_SERVER['REQUEST_URI'] 提取的路径。请求方法的默认值是 SERVER['REQUEST_METHOD'] 的值。如果未传递给构造函数,则通过自动导入 'INPUT_*' 变量来设置请求变量。

每个路由事件都携带有关请求和路由的信息。它们被保存在不可变的请求(通过 $event->getRequest() 访问)和不可变的路由(通过 $event->getRoute() 访问)。有关详细信息,请参阅 eArc\Router\Interface\RouteInformationInterfaceeArc\Router\Interface\RequestInformationInterface

特殊字符路由

对命名空间的字符约束(特别是不允许 -.~)限制了可用的路由名称。最快的解决方案是使用 earc/event-tree 的 .redirect 指令。在父目录中放置一个名为 .redirect 的纯文本文件。假设您想使用 URL /spe~ci-al.chars 但您使用命名空间兼容的替代方案 /special_characters。然后在 routing 目录中放置以下文件

spe~ci-al.chars ~/special_characters
special_characters

现在路由 /spe~ci-al.chars 由目录 routing/special_characters 表示。

.redirect 指令的详细说明请参阅 The redirect directive 部分。

高级用法

预处理和后处理

有很多例子需要在控制器逻辑之前或之后执行逻辑。它们可以分为三种情况。

  1. 逻辑是针对特定路由/控制器的。
  2. 逻辑是针对子路由/一组控制器的。
  3. 逻辑对所有或几乎所有控制器都很有用。

通过附加到路由的监听器

前两种情况可以利用 earc/router 使用 earc/event-tree 包的事实。路由事件从 routing 文件夹到目标控制器传递,可以通过监听器进行拦截。路由事件触发实现 eArc\Router\Interface\RouterListenerInterface 的所有监听器。

让我们先从第二种情况开始。

假设您想检查以 /admin 开头的所有路由的管理员权限。只需在 routing/admin 文件夹中放置一个实现 RouterListenerInterface 的类,并像控制器一样处理事件。您甚至可以终止事件,使其无法到达任何控制器。(有关详细信息,请参阅 earc/event-tree 的文档。)当然,监听器可以分派一个针对 /login/error-pages/access-denied 的新路由事件。

namespace NamespaceOfYour\EventTreeRoot\routing\admin\user\edit;

use eArc\Router\Interfaces\RouterListenerInterface;
use eArc\Router\RouterEvent;
use eArc\Router\Interfaces\RouterEventInterface;

class Listener implements RouterListenerInterface
{
    public function process(RouterEventInterface $event) : void
    {
        //... your listener code goes here

        //... 
        // the user is not logged in
        $event->getHandler()->kill();
        (new RouterEvent('/login'))->dispatch();
        // ...
        // the user has no admin privileges
        $event->getHandler()->kill();
        (new RouterEvent('/error-pages/access-denied', 'GET'))->dispatch();
        //...
    }
}

请注意,新的路由事件不会重定向浏览器客户端。它只是改变了应用程序请求处理流程。要实现重定向,将路径从 /login 更改为 /redirect/login,并在 routing/redirect 文件夹中放置一个监听器或控制器以执行重定向。

您可能已经注意到了,您可以使用路由事件以非常透明的方式解耦您的逻辑。

现在让我们看看第二个案例。

您只需在控制器所在的同一目录中放置一个监听器,它就会被调用。为了告诉路由器在控制器之前或之后调用它,它必须实现eArc\EventTree\Interfaces\SortableListenerInterface接口。如果getPatience()返回一个正浮点数,它将在控制器之后被调用,反之亦然,因为控制器有一个耐心值为0。如果您在同一个目录中有多个监听器,您也可以通过SortableListenerInterface指定一个顺序。如果您想使其可配置,请在返回语句中使用earc/di包的di_param函数。

如果监听器仅在路由针对控制器时才调用,则监听器必须实现eArc\EventTree\Interfaces\PhaseSpecificListenerInterface接口并返回ObserverTree::PHASE_DESTINATION常量。

namespace NamespaceOfYour\EventTreeRoot\routing\some\route;

use eArc\EventTree\Transformation\ObserverTree;
use eArc\EventTree\Interfaces\PhaseSpecificListenerInterface;
use eArc\Router\Interfaces\RouterListenerInterface;
use eArc\EventTree\Interfaces\SortableListenerInterface;
use eArc\Router\Interfaces\RouterEventInterface;

class Listener implements RouterListenerInterface, SortableListenerInterface, PhaseSpecificListenerInterface
{
    public function process(RouterEventInterface $event) : void
    {
        //...
    }
                          
    public static function getPatience() : float
    {
        // it has a negative patience and is hence called before the controller
        // who has a patience of 0.
        return -1;
    }

    public static function getPhase(): int
    {
        // listener with phase destination are only called if the route matches
        return ObserverTree::PHASE_DESTINATION;
    }
}

要了解更多关于监听器的patience和事件的phases的信息,请参阅earc/event-tree的文档。

通过生命周期钩子

实现第三个案例并将钩子插入所有控制器的生命周期最简单的方法是从您自己的基控制器扩展您的控制器。您可以进行预处理和后处理,记录异常或实现旧式的动作处理 - 在一个控制器中拥有许多动作。

namespace NamespaceOfYour\EventTreeRoot\routing\admin\user\edit;

use eArc\Router\AbstractController;
use eArc\Router\Interfaces\RouterEventInterface;

abstract class AbstractDeprecatedSyntaxController extends AbstractController
{
    public function process(RouterEventInterface $event) : void
    {
        $this->preProcessing($event);

        try {
            $actionId = $event->getRoute()->getParam(0);
            $methodName = $actionId.'Action';
            $this->$methodName($event);
        } catch (\Exception $exception) {
            $this->logException($exception);       
        }

        $this->postProcessing($event);
    }
    
    protected function logException(\Exception $exception) {/*...*/}

    protected function preProcessing(RouterEventInterface $event) {/*...*/}

    protected function postProcessing(RouterEventInterface $event) {/*...*/}
}
  • 优点:简单快捷。
  • 缺点:不够灵活。例如,如果您有一个核心应用程序和许多扩展核心应用程序的客户应用程序,客户无法更改流程。即使像装饰或黑名单这样的基本覆盖技术也无法应用于基控制器,除非为每个控制器进行装饰。

也许您已经听说过开放-封闭原则(OCP)。如您所见,上面继承不适合大规模遵循OCP。程序流程不再开放以进行修改。为了克服这一点,earc/router在事件树上公开了监听器/控制器的调用。

通过添加名为earcearc/lifecycleearc/lifecycle/router的文件夹扩展事件树根。如果您在最后一个文件夹中放置一个实现eArc\EventTree\Interfaces\ListenerInterface的类,您就可以拦截eArc\Router\LifeCycle\RouterLifeCycleEvent

让我们再次使用事件树的力量来做上面的例子。

我们需要将三个类放入earc/lifecycle/router文件夹中。

namespace NamespaceOfYour\EventTreeRoot\earc\lifecycle\router;

use eArc\Observer\Interfaces\ListenerInterface;
use eArc\EventTree\Interfaces\SortableListenerInterface;
use eArc\Observer\Interfaces\EventInterface;
use eArc\Router\AbstractController;
use eArc\Router\LifeCycle\RouterLifeCycleEvent;
use eArc\Router\Interfaces\RouterEventInterface;

class PreProcessingListener implements ListenerInterface, SortableListenerInterface
{
    public function process(EventInterface $event) : void
    {
        if ($event instanceof RouterLifeCycleEvent) {
            $this->preProcessing($event->routerEvent);
        }
    }

    protected function preProcessing(RouterEventInterface $event) {/*...*/}
        
    public static function getPatience() : float
    {
        return -1;
    }
}

class PostProcessingListener implements ListenerInterface, SortableListenerInterface
{
    public function process(EventInterface $event) : void
    {
        if ($event instanceof RouterLifeCycleEvent) {
            $this->postProcessing($event->routerEvent);
        }
    }
      
    protected function postProcessing(RouterEventInterface $event) {/*...*/}

    public static function getPatience() : float
    {
      return 1;
    }
}

class ExecuteCallListener implements ListenerInterface
{
    public function process(EventInterface $event) : void
    {
        if ($event instanceof RouterLifeCycleEvent) {
            try {
                $listener = $event->listenerCallable[0];
                if (!$listener instanceof AbstractController) {
                    call_user_func($event->listenerCallable, $event->routerEvent);
                
                    return;
                }

                $actionId = $event->routerEvent->getRoute()->getParam(0);
                $methodName = $actionId.'Action';
                $listener->$methodName($event->routerEvent);
            } catch (\Exception $exception) {
                $this->logException($exception);       
            }
        }
    }
    
    protected function logException(\Exception $exception) {/*...*/}
}

最后但同样重要的是,在配置部分黑名单原始的ExecuteCallListener。因为控制器不应该被调用两次。

use eArc\RouterEventTreeRoot\earc\lifecycle\router\ExecuteCallListener;

di_import_param(['earc' => ['event_tree' => ['blacklist' => [
    ExecuteCallListener::class => true,
]]]]);

现在我们的逻辑在应用程序继承尺度上(对于扩展)是开放的(对于修改)。

如果您愿意,您可以用装饰来代替黑名单。

use NamespaceOfYour\App\Somewhere\OutsideTheEventTree\ExecuteCallListener;
use eArc\RouterEventTreeRoot\earc\lifecycle\router\ExecuteCallListener as OriginECL;

di_decorate(OriginECL::class, ExecuteCallListener::class);

注意

  1. 装饰可以在调用之前在代码中的任何位置进行,但黑名单必须在第一个事件分发之前进行。
  2. 在装饰的情况下,您必须将装饰的ExecuteCallListener放置在事件树之外。

路由/事件树继承

正如您可能已经注意到的,原始的ExecuteCallListener生活在您的事件树根目录之外,作为供应商目录的一部分,但它是由事件调用的。这就是所谓的事件树继承。如果您查看您的配置,您会注意到有两个earc.event_tree.directories。您的和earc/router/earc-event-tree。如果您将这两个事件树在它们的根处结合在一起,您就得到了实际使用的事件树。您可以组合尽可能多的树。如果一个叶子存在于至少一个树中,则存在一个叶子。

路由部分也进行了合并,虽然不出所料。它允许您按包定义路由。

事件树继承是一个强大的工具,但对于初学者来说可能会有些复杂。您可以使用earc/event-tree包中的view-tree命令行工具来绘制(并使用grep)实际树的结构。

自定义事件

为了保持组件解耦,事件应该是唯一保留运行时信息的地方(当监听器/控制器完成其工作后)。由于运行时信息是应用程序特定的,因此设计自己的事件是您的架构责任的一部分。

最佳实践是使用接口来描述运行时信息。遵循接口分离原则(ISP)。设计实现接口的对象并扩展eArc\Router\RouterEvent以提供这些对象。

namespace NamespaceOfYour\App\Somewhere\OutsideTheEventTree;

use eArc\Router\RouterEvent;

interface AppRuntimeInformationInterface
{
    public function getRunnerId(): int;
    public function addWarning(Exception $exception);
    public function getWarnings(): array;
    public function getSession(): SessionInterface;
    public function getCurrentUser() : ?UserInterface;
    public function setCurrentUser(?UserInterface $user);
}

class AppRuntimeInformation implements AppRuntimeInformationInterface
{
    protected $runnerId;
    protected $warnings = [];
    protected $session;
    protected $currentUser;

    //...
}

class AppRouterEvent extends RouterEvent
{
    protected $runtimeInformation;

    public function __construct(?string $uri = null,?string $requestMethod = null,?array $argv = null)
    {
        parent::__construct($uri,$requestMethod,$argv);

        $this->runtimeInformation = di_get(AppRuntimeInformation::class);              
    }

    public function getRI(): AppRuntimeInformationInterface {/*...*/}
}

不要忘记在您的引导部分替换路由事件。

$event = new AppRouterEvent();
$event->dispatch();

现在,所有需要在您的监听器/控制器之间交换的运行时信息都已公开,易于查找和理解。

子系统处理

如果您需要一个只触发子集监听器/控制器的路由事件,您可以修改由EventInterface提供的getApplicableListener()方法。它返回由事件调用的所有监听器接口数组。

例如,如果一个核心应用程序支持多个版本,您可以通过这种方式使用不同的监听器/控制器来处理不同的版本。如果控制器支持多个版本,它只需实现多个监听器接口即可。

此功能的其他用例

  • 应用程序的某些部分仅在某个国家或某些语言中可用。
  • 应用程序的某些部分仅在调试模式下有效。
  • 对于付费更多金额的高级用户,应用程序的行为会显著变化。
  • 应用程序的不同部分可以被切换。
  • 处理路由的不同阶段。

进一步解耦

您可以使用事件树和路由树(您可以将它视为事件树的一个子集)来更好地解耦您的代码。让我们再看一个例子。

几乎所有的Web应用程序都进行某种形式的渲染。渲染需要三个要素:数据、模板和引擎。数据不会因为可视化而改变,模板和引擎会改变。

控制器控制应用程序的数据生成和持久化机制,它们不应控制可视化层。这个基本原理是著名的单一职责原则(SRP)。

因此,将模板引擎注入控制器或在其内部使用模板注解是不好的做法。控制器不应了解这些内容。

路由确定控制器和与一些参数一起的数据,但在旧式框架如symfony中,它似乎也确定了模板。这是不正确的,也不应该是您编码的方式。我见过无数例子,其中两个动作/路由做同样的事情,生成相同的数据响应,只是为了获取不同的模板或返回JSON表示而不是数据模板表示。

稍加思考,您就会意识到在这些情况下,不同的路由只是不同的展示参数。旧式MVC不支持解耦,因此程序员需要以不良的方式发挥创造力。

我们如何做得更好?

每个控制器返回的数据都非常具体。你不应该将其作为数组返回,而应该作为对象返回。一旦你有一个对象,你就有了一个标识符,表示应用程序可以使用的模板集合。如果这个集合中有多个模板可用,那么表示参数就会发挥作用。记住,参数是事件的一部分,但表示参数也是返回数据的一个有效部分——可能由控制器转换。

一旦控制器处理完毕,就有数据可以选择,以及从模板集合中选择一个模板的键。所有这些都附加到事件上。这看起来需要后期处理!

实现后,我们只需更改对象类、表示参数和模板的赋值,就可以更改选定的模板。如果我们想更改模板引擎,我们只需要装饰一个监听器。我们不需要更改所有控制器的代码,也不需要遵守不适合我们新用例的引擎接口。

提示:将模板组织在同一目录结构或类似结构中,可能是一个好主意,这样就可以显著减少配置开销。请注意,这个目录结构可以与路由目录结构完全不同。

自定义路由

路由重写

改变路由的原因有很多。向后兼容性、客户请求或SEO只是其中三个。在一个没有路由树继承(路由部分的事件树继承)的应用程序中,这很容易。只需重命名和重新组织文件夹。

重定向指令

如果你需要在不同的路由下使用相同的内容,你应该使用earc/event-tree的.redirect指令。它只是一个名为.redirect的文件。你可以将其放在需要重定向的任何文件夹中。每一行都是一个重定向。在行首,你放你想重定向的子文件夹名称(它不需要存在),然后在第二个地方,通过一个空格分隔,你放目标路径,它是相对于事件树根目录的相对路径。~/是一个引用当前目录的快捷方式。

要排除现有或继承的目录,只需将目标留空。.redirect指令是事件树继承的一部分。如果存在几个具有相同路径的.redirect指令,命名相同的子文件夹,那么earc.event_tree.directories的顺序很重要。指令按照它们的目录树注册的顺序被覆盖。你可以使用目标快捷方式~来取消重定向。

lama creatures/animals/fox #redirects events targeting the lama subfolder to creatures/animals/fox
eagle ~/extinct/2351       #redirects events targeting the eagle subfolder to the extinct/2351 subfolder
maple                      #hides the maple subdirectory from the events
tiger ~                    #cancels all redirects for tiger made by the event trees inherited so far

例如,将路由重写为/imported/products/products/imported需要两个步骤(每个重写部分一个)

1 ) 在routing目录中放置.redirect指令

products routing/imported
imported

2 ) 在imported目录中放置.redirect指令

imported ~/products
products

显然,使用这会改变路由的目录参数,但它不会改变被调用的控制器或监听器的调用顺序。

要重写基本叶子节点,将.redirect指令放入事件树根。

查找指令

每个.redirect指令都会破坏事件树显式设计带来的清晰度。因此,大量使用.redirect指令是一个反模式。如果你需要重定向大量的树,最好是重写它,并使用.lookup指令来包含旧树中的监听器。

.redirect指令一样,.lookup指令也是一个纯文本文件。如果你放在那里,它就会被包含。这意味着事件树链接的叶子中的每个监听器都会被处理,就像它位于当前叶子中一样。每一行是一个单独的包含。路径必须是相对于事件树根的相对路径。

路由目录

如果你重新编写路由树,使用一个新的路由目录是一种最佳实践。如果不这样做,你将需要除了易于理解的 .lookup 指令之外,还需要更复杂的 .redirect 指令来排除不想要的路由叶节点。

要实现这一点,只需为你的路由事件配置一个新的路由目录。

use eArc\Router\Interfaces\RouterEventInterface;

di_import_param(['earc' => [
    'router' => [
        'routing_directory' => [
            RouterEventInterface::class => 'v2/routing'
        ]   
    ]
]]);

你可以为不同的事件定义不同的路由目录。事件传递的第一个键,对 instanceof 进行检查,定义了路由目录。如果没有通过,则路由目录为 routing

映射路由

在某些用例中,映射比 earc 路由的概念更优越。想想一个多国网站,使用语言键(如 /en/de/fr)前缀路由。然后你可以在路由目录下创建名为 endefr 的子目录,并在每个目录中放置 .lookup,用于设置语言区域并进行重定向。这样做没有问题,但提取语言并减少几行代码会更高效。

序列化事件

路由事件可以被序列化。

这使得检查用户访问权限并在他们登录或注册后重新路由他们变得就像喝一杯茶一样简单。

但是请注意,未序列化的事件需要再次分发。它们在路由树中的位置丢失,并从路由目录开始它们的旅程。这样,真的很难搞砸。

缓存路由树

路由树的概念深深植根于文件系统中。文件系统访问在时间和成本上并不便宜,可能成为瓶颈。如果你使用像 ACPu 这样的文件缓存,考虑将事件树结构(即使可能非常大)加载到内存中是值得考虑的。

配置步骤在 earc/event-tree 的 文档 中描述。

进一步阅读

  • earc/router 是建立在 earc/event-tree 之上的。请随时查阅 earc/event-tree 文档。

  • 为了充分利用全局容器免依赖注入系统 earc/di,阅读其文档可能是个好主意。

  • 要处理类型提示转换的边缘情况,earc/parameter-transformer 的文档提供了见解。

版本

版本 4.0

  • 仅限 PHP ^8.0
  • AbstractResponseController 的类型提示转换使用 earc/parameter-transformer
  • eArc\Router\Interfaces\ParameterFactoryInterface 被替换为 eArc\ParameterTransformer\Interfaces\ParameterTransformerFactoryInterface

版本 3.1

  • AbstractResponseController 支持 earc/data 实体的类型提示

版本 3.0

  • PHP ^7.3 || PHP ^8.0

版本 2.1

  • 响应控制器
  • 参数注入
  • 参数转换器
  • ParameterFactoryInterface
  • AbstractResponseController::USE_REQUEST_KEYS
  • 响应控制器默认值

版本 2.0

版本 1.1

  • RequestInformationInterface::getArg()RouteInformationInterface::getParam() 现在接受默认参数

版本 1.0

  • 现在,路由将匹配 earc/event-treerouting 部分。
  • 分发器现在是 earc/router 的一部分,而不是 earc/core,并分发 earc/event-tree 事件。
  • 引入了不可变对象 RouteRequest。这两个对象都作为有效负载附加到已分发的 earc/event-tree 事件。
  • 不再有访问和主控制器。控制器现在是 earc/event-tree 监听器。
  • 路由器生命周期通过事件树暴露。这使得在大规模上遵循开闭原则(open-closed-principle)的同时,实现预处理和后处理变得简单。

版本 0.1

首个官方版本。