aura / dispatcher
从工厂创建对象并使用命名参数调用方法;还提供了一种特质,用于使用命名参数调用闭包和对象方法。
Requires
- php: >=5.4.0
README
提供将任意名称映射到可调度对象,然后使用命名参数调度到这些对象的工具。这对于根据路径信息参数或命令行参数调用控制器和命令对象、调度到基于闭包的控制器以及从工厂构建可调度对象非常有用。
前言
安装
此库需要 PHP 5.4 或更高版本;我们原则上推荐使用最新版本的 PHP。它没有用户空间依赖。
它可以通过 Composer 以 aura/dispatcher 的方式安装和自动加载。
或者,下载一个版本 或克隆此存储库,然后要求或包含其 autoload.php 文件。
质量
要运行命令行中的单元测试,在包根目录中发出 phpunit
命令。(这需要 PHPUnit 作为 phpunit
可用。)
此库试图遵守 PSR-1、PSR-2 和 PSR-4。如果您注意到遵守方面的疏忽,请通过拉取请求发送补丁。
社区
要提问、提供反馈或与其他 Aura 社区成员交流,请加入我们的 Google Group、关注 @auraphp on Twitter 或在 Freenode 上与我们聊天 #auraphp。
入门
概述
首先,外部路由机制(如 Aura.Router 或微框架路由器)创建一个参数数组。(或者,参数可能是一个实现 ArrayAccess 的对象。)
然后将这些参数传递给 Dispatcher。它检查它们,并选择一个对象来调用这些参数,可选地使用参数确定的方法。
然后 Dispatcher 检查第一次调用的返回结果。如果结果是另一个可调度对象,则 Dispatcher 将递归地调度该结果,直到返回一个不是可调度对象的结果。
当返回非可调度结果时,Dispatcher 停止递归并返回非可调度结果。
闭包和可调用对象
首先,我们告诉 Dispatcher 检查 controller
参数以找到要调度的对象名称
<?php use Aura\Dispatcher\Dispatcher; $dispatcher = new Dispatcher; $dispatcher->setObjectParam('controller'); ?>
接下来,我们使用 setObject()
将闭包对象设置到 Dispatcher 中
<?php $dispatcher->setObject('blog', function ($id) { return "Read blog entry $id"; }); ?>
现在我们可以通过使用 controller
参数的值作为名称来调度到该闭包
<?php $params = [ 'controller' => 'blog', 'id' => 88, ]; $result = $dispatcher($params); // or call __invoke() directly echo $result; // "Read blog entry 88" ?>
对于可调用对象也是如此。首先,定义一个具有 __invoke()
方法的类
<?php class InvokableBlog { public function __invoke($id) { return "Read blog entry $id"; } } ?>
然后,将对象实例设置到 Dispatcher 中
<?php $dispatcher->setObject('blog', new InvokableBlog); ?>
最后,调度到可调用对象(参数和逻辑与上面相同)
<?php $params = [ 'controller' => 'blog', 'id' => 88, ]; $result = $dispatcher($params); // or call __invoke() directly echo $result; // "Read blog entry 88" ?>
对象方法
我们可以告诉分发器检查方法参数,找到要在对象上调用的方法。如果存在这样的方法,该方法将优先于对象上的__invoke()
方法。
首先,告诉分发器检查action
参数的值,以找到它应该调用的方法名称。
<?php $dispatcher->setMethodParam('action'); ?>
接下来,定义我们将要分发的对象;注意,方法为read()
而不是__invoke()
。
<?php class Blog { public function read($id) { return "Read blog entry $id"; } } ?>
然后,我们将对象设置为分发器...
<?php $dispatcher->setObject('blog', new Blog); ?>
...最后,我们调用分发器;我们已添加一个action
参数,其中包含要调用的方法名称。
<?php $params = [ 'controller' => 'blog', 'action' => 'read', 'id' => 88, ]; $result = $dispatcher($params); // or call __invoke() directly echo $result; // "Read blog entry 88" ?>
在参数中嵌入对象
如果您愿意,可以直接将可分发的对象放在参数中。(这通常是微框架路由器的工作方式。)例如,让我们将一个闭包放入controller
参数中;当我们调用分发器时,它将调用该闭包。
<?php $params = [ 'controller' => function ($id) { return "Read blog entry $id"; }, 'id' => 88, ]; $result = $dispatcher($params); // or call __invoke() directly echo $result; // "Read blog entry 88" ?>
对于可调用对象也是如此...
<?php $params = [ 'controller' => new InvokableBlog, 'id' => 88, ]; $result = $dispatcher($params); // or call __invoke() directly echo $result; // "Read blog entry 88" ?>
...以及对象方法
<?php $params = [ 'controller' => new Blog, 'action' => 'read', 'id' => 88, ]; $result = $dispatcher($params); // or call __invoke() directly echo $result; // "Read blog entry 88" ?>
递归和延迟加载
分发器是递归的。在将分发到第一个对象之后,如果该对象返回一个可分发对象,则分发器将重新分发到该对象。它将继续这样做,直到返回的结果不再是可分发对象。
让我们将分发器中的可调用对象示例转换为延迟加载的实例化。我们只需要将实例化包装在另一个可分发对象中(在本例中为闭包)。这样做的优点是我们可以将尽可能多的对象放入分发器中,它们将在分发器调用它们之前不会实例化。
<?php $dispatcher->setObject('blog', function () { return new Blog; }); ?>
然后我们使用与之前相同的参数调用分发器。
<?php $params = [ 'controller' => 'blog', 'action' => 'read', 'id' => 88, ]; $result = $dispatcher($params); // or call __invoke() directly echo $result; // "Read blog entry 88" ?>
发生的情况是这样的
-
分发器找到'blog'可分发对象,看到它是一个闭包,并用参数调用它。
-
分发器检查结果,看到结果是可分发对象,并用参数调用它。
-
分发器检查那个结果,看到它不是可调用的对象,并返回结果。
直接发送参数数组
有时您可能希望直接将整个参数数组发送给对象方法或闭包,而不是匹配参数键与函数参数名称。要做到这一点,为将接收它们的参数名称命名一个键在参数数组中,然后使用该名称将参数数组设置为自身。根据您的需求,这可能更容易通过引用或复制来完成。
<?php // a dispatchable closure that takes an array of params directly, // not the individual params by keys matching argument names $dispatcher->setObject('blog', function ($params) { return "Read blog entry {$params['id']}" }); // the initial params $params = [ 'controller' => 'blog', 'action' => 'read', 'id' => 88, ]; // set a params reference into itself; this corresponds with the // 'params' closure argument $params['params'] =& $params; // dispatch $result = $dispatcher($params); // or call __invoke() directly echo $result; // "Read blog entry 88" ?>
重构以适应架构变化
分发器的设计理念是,一些开发者可能从微框架架构开始,随着时间的推移逐步发展到全栈架构。
最初,开发者使用嵌入在参数中的闭包
<?php $dispatcher->setObjectParam('controller'); $params = [ 'controller' => function ($id) { return "Read blog entry $id"; }, 'id' => 88, ]; $result = $dispatcher($params); // or call __invoke() directly echo $result; // "Read blog entry 88" ?>
在添加了几个控制器后,开发者很可能希望将路由配置与控制器操作分开。此时,开发者可能开始将控制器操作放入分发器中
<?php $dispatcher->setObject('blog', function ($id) { return "Read blog entry $id!"; }); $params = [ 'controller' => 'blog', 'id' => 88, ]; $result = $dispatcher($params); // or call __invoke() directly echo $result; // "Read blog entry 88" ?>
随着控制器数量和复杂性的持续增长,开发者可能希望将控制器放入它们自己的类中,并在途中进行延迟加载
<?php class Blog { public function __invoke($id) { return "Read blog entry $id"; } } $dispatcher->setObject('blog', function () { return new Blog; }); $params = [ 'controller' => 'blog', 'id' => 88, ]; $result = $dispatcher($params); // or call __invoke() directly echo $result; // "Read blog entry 88" ?>
最后,开发者可能将多个操作收集到一个控制器中,将相关的功能保存在同一个类中。此时,开发者应调用setMethodParam()
来告诉分发器在可分发对象上调用方法的位置。
<?php class Blog { public function browse() { // ... } public function read($id) { return "Read blog entry $id"; } public function edit($id) { // ... } public function add() { // ... } public function delete($id) { // ... } } $dispatcher->setMethodParam('action'); $dispatcher->setObject('blog', function () { return new Blog; }); $params = [ 'controller' => 'blog', 'action' => 'read', 'id' => 88, ]; $result = $dispatcher($params); // or call __invoke() directly echo $result; // "Read blog entry 88" ?>
基于构造的配置
您可以在构造时设置所有可分发对象、对象参数名称和方法参数名称。这使得在单个调用中配置分发器对象更容易。
<?php $object_param = 'controller'; $method_param = 'action'; $objects = [ 'blog' => function () { return new BlogController; }, 'wiki' => function () { return new WikiController; }, 'forum' => function () { return new ForumController; }, ]; $dispatcher = new Dispatcher($objects, $object_param, $method_param); ?>
中介分发方法
有时,您的类将有一个中介方法来选择要运行的动作,这个动作可以是作用于自身,也可以作用于另一个对象。本包提供了一个 InvokeMethodTrait,用于通过命名参数在对象上调用方法。(InvokeMethodTrait 遵守受保护和私有作用域。)
<?php use Aura\Dispatcher\InvokeMethodTrait; class Blog { use InvokeMethodTrait; public function __invoke(array $params) { $action = isset($params['action']) ? $params['action'] : 'index'; $method = 'action' . ucfirst($action); return $this->invokeMethod($this, $method, $params); } protected function actionRead($id = null) { return "Read blog entry $id"; } } ?>
然后您可以像往常一样向对象分发,它将决定自己的逻辑流程。
<?php $dispatcher->setObject('blog', function () { return new Blog; }); $params = [ 'controller' => 'blog', 'action' => 'read', 'id' => 88, ]; $result = $dispatcher($params); // or call __invoke() directly echo $result; // "Read blog entry 88" ?>