调度控制器
最后更新于:2022-04-02 05:15:13
[TOC]
# 调度控制器
`Phalcon\Mvc\Dispatcher` 是负责实例化控制器并在MVC应用程序中对它们执行所需操作的组件。了解其操作和功能有助于我们从框架提供的服务中获得更多。
## 调度循环
这是一个重要的过程,与MVC流本身有很大关系,特别是与控制器部分有关。工作发生在控制器调度程序中。读取,加载和实例化控制器文件。然后执行所需的操作。如果操作将流转发到另一个控制器/操作,则控制器调度程序再次启动。为了更好地说明这一点,以下示例显示了`Phalcon\Mvc\Dispatcher`中执行的大致过程:
```php
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
[warning] 事件侦听器上的方法接受`Phalcon\Events\Event`对象作为它们的第一个参数 - 控制器中的方法不接受。
## 转发到其他动作
调度循环允许我们将执行流转发到另一个控制器/动作。这对于检查用户是否可以访问某些选项,将用户重定向到其他屏幕或仅重用代码非常有用。
```php
dispatcher->forward(
[
'controller' => 'posts',
'action' => 'index',
]
);
}
}
```
请记住,进行`forward`与进行HTTP重定向不同。虽然他们显然得到了相同的结果。`forward`不重新加载当前页面,所有重定向都发生在单个请求中,而HTTP重定向需要两个请求才能完成该过程。
更多转发示例:
```php
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
[
'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
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
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
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
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
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
view->post = $post;
}
}
```
方法`showAction`接收模型`\Posts`的一个实例,开发人员可以在调度准备参数的动作之前检查这个:
```php
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
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
forward(
[
'controller' => 'index',
'action' => $action,
]
);
return false;
}
}
```
>[danger] 只有调度程序生成的异常和执行的操作中产生的异常才会在`beforeException`事件中得到通知。在侦听器或控制器事件中生成的异常将重定向到最新的try/catch。
## 实现自己的Dispatcher
必须实现 `Phalcon\Mvc\DispatcherInterface` 接口才能创建自己的调度程序,替换Phalcon提供的调度程序。
';