调度组件¶
概览¶
The Phalcon\Mvc\Dispatcher是一个负责在MVC应用程序中实例化控制器并执行所需操作的组件。调度是指接收请求对象,从中提取模块名称、控制器名称、动作名称和可选参数,然后实例化控制器并调用该控制器的动作。
<?php
use Phalcon\Di\Di;
use Phalcon\Mvc\Dispatcher;
$container = new Di();
$dispatcher = new Dispatcher();
$dispatcher->setDI($container);
$dispatcher->setControllerName("posts");
$dispatcher->setActionName("index");
$dispatcher->setParams([]);
$controller = $dispatcher->dispatch();
方法¶
使用处理程序和参数调用一个动作方法 通过调用适当的控制器动作(包括任何路由数据或注入的参数)来处理路由器的结果。返回已调度的处理程序类(对于MVC调度是Controller,对于CLI调度是Task),或者false
如果发生异常并且操作被异常处理器中的返回值停止false
。如果在调度过程中发生了未被捕获或未处理的异常,则抛出异常。 将执行流程转发到另一个控制器/动作。 <?php
use Phalcon\Events\Event;
use Phalcon\Mvc\Dispatcher;
use App\Back\Bootstrap as Back;
use App\Front\Bootstrap as Front;
$modules = [
"frontend" => [
"className" => Front::class,
"path" => __DIR__ . "/app/Modules/Front/Bootstrap.php",
"metadata" => [
"controllersNamespace" => "App\Front\Controllers",
],
],
"backend" => [
"className" => Back::class,
"path" => __DIR__ . "/app/Modules/Back/Bootstrap.php",
"metadata" => [
"controllersNamespace" => "App\Back\Controllers",
],
],
];
$application->registerModules($modules);
$eventsManager = $container->getShared("eventsManager");
$eventsManager->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"]
);
}
);
// Forward
$this->dispatcher->forward(
[
"module" => "backend",
"controller" => "posts",
"action" => "index",
]
);
<?php
use Phalcon\Mvc\Dispatcher;
/**
* @property Dispatcher $dispatcher
*/
class InvoicesController extends Controller
{
public function viewAction(Invoices $invoice)
{
$boundModels = $this
->dispatcher
->getBoundModels()
;
}
}
public function getParam(
mixed $param,
string | array $filters = null,
mixed $defaultValue = null
): mixed
public function setModelBinder(
BinderInterface $modelBinder,
mixed $cache = null
): DispatcherInterface
$container->set(
'dispatcher',
function() {
$dispatcher = new Dispatcher();
$dispatcher->setModelBinder(
new Binder(),
'cache'
);
return $dispatcher;
}
);
分发循环¶
这是一个与MVC流程本身特别是控制器部分密切相关的关键过程。工作发生在控制器调度器内。控制器文件被读取、加载和实例化。接着执行所需的动作。如果某个动作将流程转发到其他控制器/动作,控制器调度器会重新开始。为了更好地说明这一点,以下示例近似展示了内部进行的过程:Phalcon\Mvc\Dispatcher组件定义的。
<?php
$finished = false;
while (true !== $finished) {
$finished = true;
$controllerClass = $controllerName . 'Controller';
$controller = new $controllerClass();
call_user_func_array(
[
$controller,
$actionName . 'Action',
],
$params
);
$finished = true;
}
转发¶
调度循环允许你将执行流转发到另一个控制器/动作。这在检查用户是否有权限访问某些区域时非常有用,如果没有权限可以转发到其他控制器和动作,从而允许你重用代码。
<?php
use Phalcon\Mvc\Controller;
use Phalcon\Mvc\Dispatcher;
/**
* @property Dispatcher $dispatcher
*/
class InvoicesController extends Controller
{
public function saveAction($year, $postTitle)
{
// ...
$this->dispatcher->forward(
[
'controller' => 'invoices',
'action' => 'list',
]
);
}
}
注意
请注意,forward
的行为不同于HTTP重定向。尽管它们会产生相同的结果,forward
不会重新加载当前页面,而HTTP重定向需要两次请求才能完成整个过程。
示例:
将流程转发到当前控制器中的另一动作 将流程转发到当前控制器中的另一动作,并传递参数A forward
动作接受以下参数:
参数 | 描述 |
---|---|
controller | 要转发的有效控制器名称。 |
action | 要转发的有效动作名称。 |
params | 动作的参数数组。 |
namespace | 控制器所属的有效命名空间名称。 |
参数¶
准备¶
通过使用Phalcon\Mvc\Dispatcher提供的事件或钩子点,你可以轻松调整你的应用程序以接受适合你的应用的任何URL模式。当你升级应用程序并希望转换一些遗留的URL时,这尤其有用。例如,你可能希望你的URL如下所示:
因为参数是按照它们在URL中定义的顺序传给动作的,所以你可以将它们转换为你期望的模式:
<?php
use Phalcon\Dispatcher;
use Phalcon\Mvc\Dispatcher as MvcDispatcher;
use Phalcon\Events\Event;
use Phalcon\Events\Manager;
$container->set(
'dispatcher',
function () {
$eventsManager = new Manager();
$eventsManager->attach(
'dispatch:beforeDispatchLoop',
function (Event $event, $dispatcher) {
$params = $dispatcher->getParams();
$keyParams = [];
foreach ($params as $index => $value) {
if ($index & 1) {
$key = $params[$index - 1];
$keyParams[$key] = $value;
}
}
$dispatcher->setParams($keyParams);
}
);
$dispatcher = new MvcDispatcher();
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
}
);
如果期望的模式是:
你可以使用以下代码:
<?php
use Phalcon\Dispatcher;
use Phalcon\Events\Event;
use Phalcon\Events\Manager;
use Phalcon\Mvc\Dispatcher as MvcDispatcher;
$container->set(
'dispatcher',
function () {
$eventsManager = new Manager();
$eventsManager->attach(
'dispatch:beforeDispatchLoop',
function (Event $event, $dispatcher) {
$params = $dispatcher->getParams();
$keyParams = [];
foreach ($params as $param) {
$parts = explode(':', $param);
$key = $parts[0];
$value = $parts[1];
$keyParams[$key] = $value;
}
$dispatcher->setParams($keyParams);
}
);
$dispatcher = new MvcDispatcher();
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
}
);
获取¶
当路由提供命名参数时,你可以在控制器、视图或任何继承自Phalcon\Di\Injectable
<?php
use Phalcon\Mvc\Controller;
use Phalcon\Mvc\Dispatcher;
/**
* @property Dispatcher $dispatcher
*/
class InvoicesController extends Controller
{
public function viewAction()
{
$invoiceId = $this
->dispatcher
->getParam('invoiceId', 'int')
;
$filter = $this
->dispatcher
->getParam('filter', 'string')
;
// ...
}
}
invoiceId
作为传入的第一个参数,并自动将其清理为integer
类型。第二个参数是filter
,它被清理为string
动作¶
类型。你还可以为动作定义任意的模式before
调度循环被调用时。
驼峰命名¶
如果原始URL是
例如你想要驼峰化show-unpaid
到ShowUnpaid
,beforeDispatchLoop
可以用来实现这一点。
<?php
use Phalcon\Mvc\Dispatcher as MvcDispatcher;
use Phalcon\Events\Event;
use Phalcon\Events\Manager as Manager;
$helper = $container->getShared('helper');
$container->set(
'dispatcher',
function () use ($helper) {
$eventsManager = new Manager();
$eventsManager->attach(
'dispatch:beforeDispatchLoop',
function (Event $event, $dispatcher) {
$dispatcher->setActionName(
$helper->camelize(
$dispatcher->getActionName()
)
);
}
);
$dispatcher = new MvcDispatcher();
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
}
);
过滤文件扩展名¶
如果原始URL始终包含一个.php
扩展名:
你可以在调度控制器/动作组合之前将其移除:
<?php
use Phalcon\Mvc\Dispatcher as MvcDispatcher;
use Phalcon\Events\Event;
use Phalcon\Events\Manager;
$container->set(
'dispatcher',
function () {
$eventsManager = new Manager();
$eventsManager->attach(
'dispatch:beforeDispatchLoop',
function (Event $event, $dispatcher) {
$action = $dispatcher->getActionName();
$action = preg_replace('/\.php$/', '', $action);
$dispatcher->setActionName($action);
}
);
$dispatcher = new MvcDispatcher();
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
}
);
注意
上述代码可以直接使用或根据需要调整,有助于遗留URL的转换或其他需要操作动作名称的使用场景。
模型注入¶
有时候你可能希望将那些与URL中传递的参数匹配的模型实例自动注入。
我们的控制器是:
<?php
use Phalcon\Mvc\Controller;
use Phalcon\Mvc\View;
/**
* @property View $view
*/
class InvoicesController extends Controller
{
public function viewAction(Invoices $invoice)
{
$this->view->invoice = $invoice;
}
}
viewAction
接收一个模型实例Invoices
。如果你尝试不经过任何检查和操作就执行此方法,调用将会失败。但是你可以在调度循环之前检查传入的参数,并相应地操作这些参数。 <?php
use \Exception;
use Phalcon\Events\Event;
use Phalcon\Events\Manager;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Dispatcher as MvcDispatcher;
use \ReflectionMethod;
$container->set(
'dispatcher',
function () {
$eventsManager = new Manager();
$eventsManager->attach(
'dispatch:beforeDispatchLoop',
function (Event $event, $dispatcher) {
$controllerName = $dispatcher->getControllerClass();
$actionName = $dispatcher->getActiveMethod();
try {
$reflection = new ReflectionMethod(
$controllerName,
$actionName
);
$parameters = $reflection->getParameters();
foreach ($parameters as $parameter) {
$className = $parameter->getClass()->name;
if (is_subclass_of($className, Model::class)) {
$model = $className::findFirstById(
$dispatcher->getParams()[0]
);
$dispatcher->setParams(
[
$model,
]
);
}
}
} catch (Exception $e) {
}
}
);
$dispatcher = new MvcDispatcher();
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
}
);
上面的示例已被简化。您可以根据需要进行调整,并在执行之前将任何类型的依赖项或模型注入到操作中。
调度器还提供了一个选项,可通过使用Phalcon\Mvc\Model\Binder对象中的相关方法手动
<?php
use Phalcon\Mvc\Dispatcher;
use Phalcon\Mvc\Model\Binder;
$dispatcher = new Dispatcher();
$dispatcher->setModelBinder(
new Binder()
);
return $dispatcher;
注意
The Phalcon\Mvc\Model\Binder该组件在内部使用了PHP的反射API,会消耗额外的处理周期。因此,它有能力使用一个cache
实例或者缓存服务名称。要使用此功能,您可以在setModelBinder()
方法中将缓存服务名称或实例作为第二个参数传递,或者直接在Binder
构造函数中传递缓存实例。
此外,通过在控制器中使用Phalcon\Mvc\Model\Binder\BindableInterface接口,您可以在基类控制器中定义模型绑定。
在下面的示例中,我们有一个基类控制器CrudController
它InvoicesController
继承自。
<?php
use Phalcon\Mvc\Controller;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\View;
/**
* @property View $view
*/
class CrudController extends Controller
{
public function viewAction(Model $model)
{
$this->view->model = $model;
}
}
在InvoicesController
我们将定义该控制器关联的模型。这可以通过实现Phalcon\Mvc\Model\Binder\BindableInterface接口来完成,这样就会使getModelName()
方法可用。这个方法用来返回模型名称。它可以返回一个仅包含一个模型名称的字符串,或者返回一个键为参数名的关联数组。
<?php
use Phalcon\Mvc\Model\Binder\BindableInterface;
class InvoicesController extends CrudController implements BindableInterface
{
public function getModelName()
{
return Invoices::class;
}
}
通过声明与InvoicesController
绑定器可以检查控制器中的getModelName()
方法,然后将定义好的模型传入父类view
动作。
如果您的项目结构不使用任何基类控制器,当然您仍然可以直接将模型绑定到控制器操作中:
<?php
use Phalcon\Mvc\Controller;
use Phalcon\Mvc\View;
/**
* @property View $view
*/
class InvoicesController extends Controller
{
public function showAction(Invoices $invoice)
{
$this->view->invoice = $invoice;
}
}
注意
目前,绑定器只会使用模型的主键来执行一个findFirst()
。上面的示例路由可能是这样的/posts/show/{1}
未找到(404)¶
如果已经定义了事件管理器,则可以使用它拦截找不到控制器/操作对时抛出的异常。
<?php
use Exception;
use Phalcon\Dispatcher;
use Phalcon\Mvc\Dispatcher as MvcDispatcher;
use Phalcon\Events\Event;
use Phalcon\Events\Manager;
use Phalcon\Mvc\Dispatcher\Exception as DispatchException;
$container->setShared(
'dispatcher',
function () {
$eventsManager = new Manager();
$eventsManager->attach(
'dispatch:beforeException',
function (
Event $event,
$dispatcher,
Exception $exception
) {
// 404
if ($exception instanceof DispatchException) {
$dispatcher->forward(
[
'controller' => 'index',
'action' => 'fourOhFour',
]
);
return false;
}
}
);
$dispatcher = new MvcDispatcher();
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
}
);
或者使用替代语法检查异常。
<?php
use Exception;
use Phalcon\Dispatcher\Exception as DispatcherException;
use Phalcon\Mvc\Dispatcher as MvcDispatcher;
use Phalcon\Events\Event;
use Phalcon\Events\Manager;
$container->setShared(
'dispatcher',
function () {
$eventsManager = new Manager();
$eventsManager->attach(
'dispatch:beforeException',
function (
Event $event,
$dispatcher,
Exception $exception
) {
switch ($exception->getCode()) {
case DispatcherException::EXCEPTION_HANDLER_NOT_FOUND:
case DispatcherException::EXCEPTION_ACTION_NOT_FOUND:
// 404
$dispatcher->forward(
[
'controller' => 'index',
'action' => 'fourOhFour',
]
);
return false;
}
}
);
$dispatcher = new MvcDispatcher();
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
}
);
我们可以将此方法移至插件类中:
<?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
) {
$action = 'fiveOhThree';
if ($exception instanceof DispatchException) {
$action = 'fourOhFour';
}
$dispatcher->forward(
[
'controller' => 'index',
'action' => $action,
]
);
return false;
}
}
注意
只有由调度器产生的异常和在执行操作时产生的异常才会通知beforeException
事件。在监听器或控制器事件中产生的异常会被重定向到最近的try/catch块中。
事件¶
Phalcon\Mvc\Dispatcher能够发送事件给管理器如果其存在的话。事件使用类型dispatch
来触发。一些事件在返回布尔值时false
可以停止当前操作。支持以下事件:
事件名称 | 触发时机 | 可以停止 |
---|---|---|
afterBinding | 在模型绑定后但在执行路由之前。 | 是 |
afterDispatch | 在执行完控制器/操作方法之后。 | 是 |
afterDispatchLoop | 在退出调度循环后 | 否 |
afterExecuteRoute | 在执行完控制器/操作方法之后。 | 否 |
afterInitialize | 允许在整个请求中初始化控制器 | 否 |
beforeDispatch | 在进入调度循环之后。调度器只知道路由器传递的信息。 | 是 |
beforeDispatchLoop | 在进入调度循环之前。调度器只知道路由器传递的信息。 | 是 |
beforeException | 在调度器抛出任何异常之前 | 是 |
beforeExecuteRoute | 在执行控制器/操作方法之前。调度器已经初始化了控制器,并知道该操作是否存在。 | 是 |
beforeForward | 在转发到控制器/操作方法之前。(MVC调度器) | 否 |
beforeNotFoundAction | 当控制器中找不到操作时 | 是 |
The INVO示例应用程序展示了您可以如何利用调度事件并使用Acl
下面的示例演示了如何将监听器附加到此组件上:
<?php
use Phalcon\Mvc\Dispatcher as MvcDispatcher;
use Phalcon\Events\Event;
use Phalcon\Events\Manager;
$container->set(
'dispatcher',
function () {
$eventsManager = new Manager();
$eventsManager->attach(
'dispatch',
function (Event $event, $dispatcher) {
// ...
}
);
$dispatcher = new MvcDispatcher();
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
},
true
);
初始化后的控制器自动充当调度事件的监听器,因此您可以将方法实现作为回调:
<?php
use Phalcon\Mvc\Controller;
use Phalcon\Mvc\Dispatcher;
class InvoicesController extends Controller
{
public function beforeExecuteRoute(
Dispatcher $dispatcher
) {
// ...
}
public function afterExecuteRoute(
Dispatcher $dispatcher
) {
// ...
}
}
注意
事件监听器上的方法接受一个Phalcon\Events\Event对象作为第一个参数 - 控制器中的方法则没有。
事件管理器¶
您可以使用dispatcher::beforeForward
事件以便更轻松地更改模块和执行重定向。
<?php
use App\Back\Bootstrap;
use Phalcon\Di\Di;
use Phalcon\Events\Manager;
use Phalcon\Mvc\Dispatcher;
use Phalcon\Events\Event;
$container = new Di();
$modules = [
'backend' => [
'className' => Bootstrap::class,
'path' => '/app/Modules/Back/Bootstrap.php',
'metadata' => [
'controllersNamespace' => 'App\Back\Controllers',
],
],
];
$manager = new Manager();
$manager->attach(
'dispatch:beforeForward',
function (
Event $event,
Dispatcher $dispatcher,
array $forward
) use ($modules) {
$moduleName = $forward['module'];
$metadata = $modules[$moduleName]['metadata'];
$dispatcher->setModuleName($moduleName);
$dispatcher->setNamespaceName(
$metadata['controllersNamespace']
);
}
);
$dispatcher = new Dispatcher();
$dispatcher->setDI($container);
dispatcher->setManager($manager);
$container->set('dispatcher', $dispatcher);
$dispatcher->forward(
[
'module' => 'backend',
'controller' => 'invoices',
'action' => 'index',
]
);
echo $dispatcher->getModuleName();
自定义¶
The Phalcon\Mvc\DispatcherInterface接口必须被实现以创建自己的调度器。
<?php
namespace MyApp\Mvc
use Phalcon\Mvc\DispatcherInterface;
class MyDispatcher implements DispatcherInterface
{
/**
* Dispatches a handle action taking into account the routing parameters
*/
public function dispatch(): object | bool;
/**
* Forwards the execution flow to another controller/action
*/
public function forward(array $forward): void;
/**
* Gets last dispatched action name
*/
public function getActionName(): string;
/**
* Gets the default action suffix
*/
public function getActionSuffix(): string;
/**
* Returns the active controller in the dispatcher
*/
public function getActiveController(): ControllerInterface;
/**
* Gets last dispatched controller name
*/
public function getControllerName(): string;
/**
* Gets the default handler suffix
*/
public function getHandlerSuffix(): string;
/**
* Returns the latest dispatched controller
*/
public function getLastController(): ControllerInterface;
/**
* Gets a param by its name or numeric index
*
* @param string|array filters
*/
public function getParam($param, $filters = null);
/**
* Gets action params
*/
public function getParams(): array;
/**
* Returns value returned by the latest dispatched action
*/
public function getReturnedValue();
/**
* Check if a param exists
*/
public function hasParam($param): bool;
/**
* Checks if the dispatch loop is finished or has more pending
* controllers/tasks to dispatch
*/
public function isFinished(): bool;
/**
* Sets the action name to be dispatched
*/
public function setActionName(string $actionName): void;
/**
* Sets the default action suffix
*/
public function setActionSuffix(string $actionSuffix): void;
/**
* Sets the default controller suffix
*/
public function setControllerSuffix(string $controllerSuffix);
/**
* Sets the controller name to be dispatched
*/
public function setControllerName(string $controllerName);
/**
* Sets the default action name
*/
public function setDefaultAction(string $actionName): void;
/**
* Sets the default controller name
*/
public function setDefaultController(string $controllerName);
/**
* Sets the default namespace
*/
public function setDefaultNamespace(string $defaultNamespace): void;
/**
* Sets the default suffix for the handler
*/
public function setHandlerSuffix(string $handlerSuffix): void;
/**
* Sets the module name which the application belongs to
*/
public function setModuleName(string $moduleName): void;
/**
* Sets the namespace to which the controller belongs to
*/
public function setNamespaceName(string $namespaceName): void;
/**
* Set a param by its name or numeric index
*
* @param mixed value
*/
public function setParam($param, $value): void;
/**
* Sets action params to be dispatched
*/
public function setParams(array $params): void;
}