[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管理
- 請求環境
- 返回響應
- 安全
- 加密/解密
- 安全
- 國際化
- 國際化
- 多語言支持