aura/dispatcher

从工厂创建对象并使用命名参数调用方法;还提供了一种特质,用于使用命名参数调用闭包和对象方法。

2.0.4 2016-10-03 19:45 UTC

This package is auto-updated.

Last update: 2024-09-11 04:32:10 UTC


README

提供将任意名称映射到可调度对象,然后使用命名参数调度到这些对象的工具。这对于根据路径信息参数或命令行参数调用控制器和命令对象、调度到基于闭包的控制器以及从工厂构建可调度对象非常有用。

前言

安装

此库需要 PHP 5.4 或更高版本;我们原则上推荐使用最新版本的 PHP。它没有用户空间依赖。

它可以通过 Composer 以 aura/dispatcher 的方式安装和自动加载。

或者,下载一个版本 或克隆此存储库,然后要求或包含其 autoload.php 文件。

质量

Scrutinizer Code Quality Code Coverage Build Status

要运行命令行中的单元测试,在包根目录中发出 phpunit 命令。(这需要 PHPUnit 作为 phpunit 可用。)

此库试图遵守 PSR-1PSR-2PSR-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"
?>