[TOC]
# 调度控制器
`Phalcon\Mvc\Dispatcher` 是负责实例化控制器并在MVC应用程序中对它们执行所需操作的组件。了解其操作和功能有助于我们从框架提供的服务中获得更多。
## 调度循环
这是一个重要的过程,与MVC流本身有很大关系,特别是与控制器部分有关。工作发生在控制器调度程序中。读取,加载和实例化控制器文件。然后执行所需的操作。如果操作将流转发到另一个控制器/操作,则控制器调度程序再次启动。为了更好地说明这一点,以下示例显示了`Phalcon\Mvc\Dispatcher`中执行的大致过程:
```php
<?php
// Dispatch loop
while (!$finished) {
$finished = true;
$controllerClass = $controllerName . 'Controller';
// Instantiating the controller class via autoloaders
$controller = new $controllerClass();
// Execute the action
call_user_func_array(
[
$controller,
$actionName . 'Action'
],
$params
);
// '$finished' should be reloaded to check if the flow was forwarded to another controller
$finished = true;
}
```
上面的代码缺少验证,过滤器和其他检查,但它演示了调度程序中的正常操作流程。
### 分发循环事件
`Phalcon\Mvc\Dispatcher` 能够将事件发送到EventsManager(如果存在)。使用类型`dispatch`触发事件。返回布尔值`false`时的某些事件可能会停止活动操作。支持以下事件:
| 事件名称 | 触发 | Can stop operation? | Triggered on |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | --------------------- |
| beforeDispatchLoop | 在进入调度循环之前触发。此时,`Dispatcher`不知道控制器或要执行的动作是否存在。`Dispatcher`只知道路由器传递的信息。 | Yes | Listeners |
| beforeDispatch | 进入调度循环后触发。此时,`Dispatcher`不知道控制器或要执行的动作是否存在。`Dispatcher`只知道路由器传递的信息 | Yes | Listeners |
| beforeExecuteRoute | 在执行控制器/操作方法之前触发。此时,调度程序已初始化控制器并知道操作是否存在。 | Yes | Listeners/Controllers |
| initialize | 在执行控制器/操作方法之前触发。此时,调度程序已初始化控制器并知道操作是否存在。 | No | Controllers |
| afterExecuteRoute | 执行控制器/操作方法后触发。由于无法停止操作,因此仅在执行操作后使用此事件进行清理 | No | Listeners/Controllers |
| beforeNotFoundAction |在控制器中找不到操作时触发 | Yes | Listeners |
| beforeException | 在调度程序抛出任何异常之前触发 | Yes | Listeners |
| afterDispatch |执行控制器/操作方法后触发。由于无法停止操作,因此仅在执行操作后使用此事件进行清理 | Yes | Listeners |
| afterDispatchLoop | 退出调度循环后触发 | No | Listeners |
| afterBinding | 模型绑定后但在执行路径之前触发 | Yes | Listeners/Controllers |
`INVO`教程展示了如何利用使用`Acl`实现安全过滤器的调度事件。
以下示例演示如何将侦听器附加到此组件:
```php
<?php
use Phalcon\Mvc\Dispatcher as MvcDispatcher;
use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;
$di->set(
'dispatcher',
function () {
// Create an event manager
$eventsManager = new EventsManager();
// Attach a listener for type 'dispatch'
$eventsManager->attach(
'dispatch',
function (Event $event, $dispatcher) {
// ...
}
);
$dispatcher = new MvcDispatcher();
// Bind the eventsManager to the view component
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
},
true
);
```
实例化的控制器自动充当调度事件的侦听器,因此您可以将方法实现为回调:
```php
<?php
use Phalcon\Mvc\Controller;
use Phalcon\Mvc\Dispatcher;
class PostsController extends Controller
{
public function beforeExecuteRoute(Dispatcher $dispatcher)
{
// Executed before every found action
}
public function afterExecuteRoute(Dispatcher $dispatcher)
{
// Executed after every found action
}
}
```
>[warning] 事件侦听器上的方法接受`Phalcon\Events\Event`对象作为它们的第一个参数 - 控制器中的方法不接受。
## 转发到其他动作
调度循环允许我们将执行流转发到另一个控制器/动作。这对于检查用户是否可以访问某些选项,将用户重定向到其他屏幕或仅重用代码非常有用。
```php
<?php
use Phalcon\Mvc\Controller;
class PostsController extends Controller
{
public function indexAction()
{
}
public function saveAction($year, $postTitle)
{
// ... Store some product and forward the user
// Forward flow to the index action
$this->dispatcher->forward(
[
'controller' => 'posts',
'action' => 'index',
]
);
}
}
```
请记住,进行`forward`与进行HTTP重定向不同。虽然他们显然得到了相同的结果。`forward`不重新加载当前页面,所有重定向都发生在单个请求中,而HTTP重定向需要两个请求才能完成该过程。
更多转发示例:
```php
<?php
// Forward flow to another action in the current controller
$this->dispatcher->forward(
[
'action' => 'search'
]
);
// Forward flow to another action in the current controller
// passing parameters
$this->dispatcher->forward(
[
'action' => 'search',
'params' => [1, 2, 3]
]
);
```
`forward` 操作接受以下参数:
| 参数 | 描述 |
| ------------ | ------------------------------------------------------- |
| `controller` | 要转发的有效控制器名称。 |
| `action` | 要转发到的有效操作名称。 |
| `params` | 操作的参数数组。 |
| `namespace` | 控制器所属的有效命名空间名称。 |
### 使用事件管理器
您可以使用`dispatcher::beforeForward`事件来更改模块比重定向更容易和“更清洁”:
```php
<?php
use Phalcon\Di;
use Phalcon\Events\Manager;
use Phalcon\Mvc\Dispatcher;
use Phalcon\Events\Event;
$di = new Di();
$modules = [
'backend' => [
'className' => 'App\Backend\Bootstrap',
'path' => '/app/Modules/Backend/Bootstrap.php',
'metadata' => [
'controllersNamespace' => 'App\Backend\Controllers',
],
],
];
$manager = new Manager();
$manager->attach(
'dispatch:beforeForward',
function (Event $event, Dispatcher $dispatcher, array $forward) use ($modules) {
$metadata = $modules[$forward['module']]['metadata'];
$dispatcher->setModuleName($forward['module']);
$dispatcher->setNamespaceName($metadata['controllersNamespace']);
}
);
$dispatcher = new Dispatcher();
$dispatcher->setDI($di);
$dispatcher->setEventsManager($manager);
$di->set('dispatcher', $dispatcher);
$dispatcher->forward(
[
'module' => 'backend',
'controller' => 'posts',
'action' => 'index',
]
);
echo $dispatcher->getModuleName(); // will display properly 'backend'
```
## 准备参数
感谢`Phalcon\Mvc\Dispatcher`提供的挂钩点,您可以轻松地将您的应用程序调整为任何URL架构;即您可能希望您的网址如下所示:`http://example.com/controller/key1/value1/key2/value`。由于参数是按照在操作的URL中定义的顺序传递的,因此您可以将它们转换为采用所需的模式:
```php
<?php
use Phalcon\Dispatcher;
use Phalcon\Mvc\Dispatcher as MvcDispatcher;
use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;
$di->set(
'dispatcher',
function () {
// Create an EventsManager
$eventsManager = new EventsManager();
// Attach a listener
$eventsManager->attach(
'dispatch:beforeDispatchLoop',
function (Event $event, $dispatcher) {
$params = $dispatcher->getParams();
$keyParams = [];
// Use odd parameters as keys and even as values
foreach ($params as $i => $value) {
if ($i & 1) {
// Previous param
$key = $params[$i - 1];
$keyParams[$key] = $value;
}
}
// Override parameters
$dispatcher->setParams($keyParams);
}
);
$dispatcher = new MvcDispatcher();
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
}
);
```
如果所需的架构是:`http://example.com/controller/key1:value1/key2:value`,则需要以下代码:
```php
<?php
use Phalcon\Dispatcher;
use Phalcon\Mvc\Dispatcher as MvcDispatcher;
use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;
$di->set(
'dispatcher',
function () {
// Create an EventsManager
$eventsManager = new EventsManager();
// Attach a listener
$eventsManager->attach(
'dispatch:beforeDispatchLoop',
function (Event $event, $dispatcher) {
$params = $dispatcher->getParams();
$keyParams = [];
// Explode each parameter as key,value pairs
foreach ($params as $number => $value) {
$parts = explode(':', $value);
$keyParams[$parts[0]] = $parts[1];
}
// Override parameters
$dispatcher->setParams($keyParams);
}
);
$dispatcher = new MvcDispatcher();
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
}
);
```
## 获取参数
当路由提供命名参数时,您可以在控制器,视图或任何其他继承`Phalcon\Di\Injectable`的组件中接收它们。
```php
<?php
use Phalcon\Mvc\Controller;
class PostsController extends Controller
{
public function indexAction()
{
}
public function saveAction()
{
// Get the post's title passed in the URL as parameter
// or prepared in an event
$title = $this->dispatcher->getParam('title');
// Get the post's year passed in the URL as parameter
// or prepared in an event also filtering it
$year = $this->dispatcher->getParam('year', 'int');
// ...
}
}
```
## 准备动作
您还可以在调度循环之`前`为操作定义任意模式。
### 操作名称驼峰化
如果原始网址为:`http://example.com/admin/products/show-latest-products`,例如,您希望将`show-latest-products`传递给`ShowLatestProducts`,则需要以下代码:
```php
<?php
use Phalcon\Text;
use Phalcon\Mvc\Dispatcher as MvcDispatcher;
use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;
$di->set(
'dispatcher',
function () {
// Create an EventsManager
$eventsManager = new EventsManager();
// Camelize actions
$eventsManager->attach(
'dispatch:beforeDispatchLoop',
function (Event $event, $dispatcher) {
$dispatcher->setActionName(
Text::camelize($dispatcher->getActionName())
);
}
);
$dispatcher = new MvcDispatcher();
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
}
);
```
### 删除遗留的扩展名
如果原始URL始终包含`.php`扩展名:
```php
http://example.com/admin/products/show-latest-products.php
http://example.com/admin/products/index.php
```
您可以在调度控制器/操作组合之前将其删除:
```php
<?php
use Phalcon\Mvc\Dispatcher as MvcDispatcher;
use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;
$di->set(
'dispatcher',
function () {
// Create an EventsManager
$eventsManager = new EventsManager();
// Remove extension before dispatch
$eventsManager->attach(
'dispatch:beforeDispatchLoop',
function (Event $event, $dispatcher) {
$action = $dispatcher->getActionName();
// Remove extension
$action = preg_replace('/\.php$/', '', $action);
// Override action
$dispatcher->setActionName($action);
}
);
$dispatcher = new MvcDispatcher();
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
}
);
```
### 注入模型实例
在此示例中,开发人员希望检查操作将接收的参数,以便动态注入模型实例。
控制器看起来像:
```php
<?php
use Phalcon\Mvc\Controller;
class PostsController extends Controller
{
/**
* Shows posts
*
* @param \Posts $post
*/
public function showAction(Posts $post)
{
$this->view->post = $post;
}
}
```
方法`showAction`接收模型`\Posts`的一个实例,开发人员可以在调度准备参数的动作之前检查这个:
```php
<?php
use \Exception;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Dispatcher as MvcDispatcher;
use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;
use \ReflectionMethod;
$di->set(
'dispatcher',
function () {
// Create an EventsManager
$eventsManager = new EventsManager();
$eventsManager->attach(
'dispatch:beforeDispatchLoop',
function (Event $event, $dispatcher) {
// Possible controller class name
$controllerName = $dispatcher->getControllerClass();
// Possible method name
$actionName = $dispatcher->getActiveMethod();
try {
// Get the reflection for the method to be executed
$reflection = new ReflectionMethod($controllerName, $actionName);
$parameters = $reflection->getParameters();
// Check parameters
foreach ($parameters as $parameter) {
// Get the expected model name
$className = $parameter->getClass()->name;
// Check if the parameter expects a model instance
if (is_subclass_of($className, Model::class)) {
$model = $className::findFirstById($dispatcher->getParams()[0]);
// Override the parameters by the model instance
$dispatcher->setParams([$model]);
}
}
} catch (Exception $e) {
// An exception has occurred, maybe the class or action does not exist?
}
}
);
$dispatcher = new MvcDispatcher();
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
}
);
```
上面的例子已经简化了。开发人员可以改进它,以便在执行之前在操作中注入任何类型的依赖关系或模型。
从3.1.x开始,调度程序还提供了一个选项,可以通过使用`Phalcon\Mvc\Model\Binder`在内部处理传递到控制器操作的所有模型。
```php
use Phalcon\Mvc\Dispatcher;
use Phalcon\Mvc\Model\Binder;
$dispatcher = new Dispatcher();
$dispatcher->setModelBinder(new Binder());
return $dispatcher;
```
>[warning] 由于Binder对象使用内部可能很重的Reflection Api,因此可以设置缓存。这可以通过在`setModelBinder()`中使用第二个参数来完成,该参数也可以接受服务名称或仅通过将缓存实例传递给Binder构造函数。
它还引入了一个新接口`Phalcon\Mvc\Model\Binder\BindableInterface` ,它允许您定义控制器关联模型,以允许模型在基本控制器中绑定。
例如,你有一个 `PostsController`继承于基础`CrudController`。你的`CrudController`看起来像这样:
```php
use Phalcon\Mvc\Controller;
use Phalcon\Mvc\Model;
class CrudController extends Controller
{
/**
* Show action
*
* @param Model $model
*/
public function showAction(Model $model)
{
$this->view->model = $model;
}
}
```
在`PostsController`中,您需要定义控制器与哪个模型相关联。这是通过实现`Phalcon\Mvc\Model\Binder\BindableInterface`来完成的,它将添加`getModelName()`方法,您可以从中返回模型名称。它只返回一个模型名称或关联数组的字符串,其中key是参数名称。
```php
use Phalcon\Mvc\Model\Binder\BindableInterface;
use Models\Posts;
class PostsController extends CrudController implements BindableInterface
{
public static function getModelName()
{
return Posts::class;
}
}
```
通过声明与`PostsController`关联的模型,绑定器可以在将定义的模型传递到父显示操作之前检查控制器的`getModelName()`方法。
如果您的项目结构不使用任何父控制器,您当然仍然可以将模型直接绑定到控制器操作中:
```php
use Phalcon\Mvc\Controller;
use Models\Posts;
class PostsController extends Controller
{
/**
* Shows posts
*
* @param Posts $post
*/
public function showAction(Posts $post)
{
$this->view->post = $post;
}
}
```
>[warning] 目前,活页夹仅使用模型主键来执行`findFirst()`。以上的示例路线是`/posts/show/{1}`
## 处理 Not-Found 异常
使用EventsManager,可以在调度程序在未找到控制器/操作组合时抛出异常时插入挂钩点:
```php
<?php
use Exception;
use Phalcon\Dispatcher;
use Phalcon\Mvc\Dispatcher as MvcDispatcher;
use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;
use Phalcon\Mvc\Dispatcher\Exception as DispatchException;
$di->setShared(
'dispatcher',
function () {
// Create an EventsManager
$eventsManager = new EventsManager();
// Attach a listener
$eventsManager->attach(
'dispatch:beforeException',
function (Event $event, $dispatcher, Exception $exception) {
// Handle 404 exceptions
if ($exception instanceof DispatchException) {
$dispatcher->forward(
[
'controller' => 'index',
'action' => 'show404',
]
);
return false;
}
// Alternative way, controller or action doesn't exist
switch ($exception->getCode()) {
case Dispatcher::EXCEPTION_HANDLER_NOT_FOUND:
case Dispatcher::EXCEPTION_ACTION_NOT_FOUND:
$dispatcher->forward(
[
'controller' => 'index',
'action' => 'show404',
]
);
return false;
}
}
);
$dispatcher = new MvcDispatcher();
// Bind the EventsManager to the dispatcher
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
}
);
```
当然,这个方法可以移动到独立的插件类上,允许多个类在调度循环中产生异常时采取操作:
```php
<?php
use Exception;
use Phalcon\Events\Event;
use Phalcon\Mvc\Dispatcher;
use Phalcon\Mvc\Dispatcher\Exception as DispatchException;
class ExceptionsPlugin
{
public function beforeException(Event $event, Dispatcher $dispatcher, Exception $exception)
{
// Default error action
$action = 'show503';
// Handle 404 exceptions
if ($exception instanceof DispatchException) {
$action = 'show404';
}
$dispatcher->forward(
[
'controller' => 'index',
'action' => $action,
]
);
return false;
}
}
```
>[danger] 只有调度程序生成的异常和执行的操作中产生的异常才会在`beforeException`事件中得到通知。在侦听器或控制器事件中生成的异常将重定向到最新的try/catch。
## 实现自己的Dispatcher
必须实现 `Phalcon\Mvc\DispatcherInterface` 接口才能创建自己的调度程序,替换Phalcon提供的调度程序。
- 常规
- Welcome
- 贡献
- 生成回溯
- 测试重现
- 单元测试
- 入门
- 安装
- Web服务器设置
- WAMP
- XAMPP
- 教程
- 基础教程
- 教程:创建一个简单的REST API
- 教程:Vökuró
- 提升性能
- 教程:INVO
- 开发环境
- Phalcon Compose (Docker)
- Nanobox
- Phalcon Box (Vagrant)
- 开发工具
- Phalcon开发者工具的安装
- Phalcon开发者工具的使用
- 调试应用程序
- 核心
- MVC应用
- 微应用
- 创建命令行(CLI)应用程序
- 依赖注入与服务定位
- MVC架构
- 服务
- 使用缓存提高性能
- 读取配置
- 上下文转义
- 类加载器
- 使用命名空间
- 日志
- 队列
- 数据库
- 数据库抽象层
- Phalcon查询语言(PHQL)
- ODM(对象文档映射器)
- 使用模型
- 模型行为
- ORM缓存
- 模型事件
- 模型元数据
- 模型关系
- 模型事务
- 验证模型
- 数据库迁移
- 分页
- 前端
- Assets管理
- 闪存消息
- 表单
- 图像
- 视图助手(标签)
- 使用视图
- Volt:模板引擎
- 业务逻辑
- 访问控制列表(ACL)
- 注解解析器
- 控制器
- 调度控制器
- 事件管理器
- 过滤与清理
- 路由
- 在session中存储数据
- 生成URL和路径
- 验证
- HTTP
- Cookies管理
- 请求环境
- 返回响应
- 安全
- 加密/解密
- 安全
- 国际化
- 国际化
- 多语言支持