本文讨论一下路由系统(Routing)。路由定义会指定模块名称,可用于根据URL参数找到对应模块并执行控制器方法
本文内容目录:
- Magento 2 处理请求流程
- 在前端或后台添加自定义路由
- 前端路由
- routes.xml
- 后台路由
- 使用路由重写控制器
- 前端路由
Magento 2 请求处理流程
如下URL
http://example.com/index.php/front_name/controller/action
front_name
可用于查找对应的模块。每个模块的路由定义这个名称参数是使用 routes.xml 配置文件。后面会详细说明
Magento 2 接收到请求时为了找到对应控制器/动作的流程是:index.php -> HTTP app -> FrontController -> Routing -> Controller -> processing -> etc
Http 类会调用 FrontControler 再通过请求的路由找到匹配的 控制器动作
文件: vendor/magento/framework/App/FrontControler.php
public function dispatch(RequestInterface $request)
{
\Magento\Framework\Profiler::start('routes_match');
$routingCycleCounter = 0;
$result = null;
while (!$request->isDispatched() && $routingCycleCounter++ < 100) {
/** @var \Magento\Framework\App\RouterInterface $router */
foreach($this->_routerList as $router) {
try{
$actionInstance = $router->match($request);
if($actionInstance) {
$request->setDispatched(true);
$this->response->setNoCacheHeaders();
if ($actionInstance instanceof \Magento\Framework\App\Action\AbstractAction) {
$result = $actionInstance->dispatch($request);
} else {
$result = $actionInstance->execute();
}
break;
}catch (\Magento\Framework\Exception\NotFoundException $e) {
$request->initForward();
$request->setActionName('noroute');
$request->setDispatched(false);
break;
}
}
}
}
}
如上代码的 dispatch() 方法中,会循环路由列表来查找匹配当前请求的路由。如果找到当前匹配的控制器动作就会调用执行。
在前端或后台创建自定义路由
继续使用 HelloWorld 模块演示代码。
接下来演示如何在前端或后台添加路由以及如何使用路由改写控制器
前端路由
前端路由使用配置文件 routes.xml
路径: app/code/Aqrun/HelloWorld/etc/frontend/routeds.xml
<?xml version="1.0" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<!-- 前台路由必须定义在 'standard' 路由下面 -->
<router id="standard">
<!--定义路由并指定 id 和 frontname -->
<route frontName="helloworld" id="helloworld">
<!-- 路由匹配的模块 -->
<module name="Aqrun_HelloWorld"/>
</route>
</router>
</config>
如上代码注册路由非常简单。前台路由必须使用 standard 路由,在这个路由下面再添加子项,指定模块及两个属性:
- id 属性是路由的唯一标识,在给模块添加布局句柄文件时也会用到这个ID
- frontName 属性值也是唯一的字符串,会显示在URL请求的链接中。比如如下路由:
<route frontName="helloworld" id="helloworld">
则当前模块的URL就是:
http://example.xom/index.php/helloworld/controller/action
对应控制器动作的布局句柄文件就是: helloworld_controller_action.xml
, 根据示例代码中的路径创建对应的动作类: {namespace}/{module}/Controller/{Controller}/{Action}.php
后台路由
后台路由配置文件和上面的前台路由一样,但文件夹是 adminhtml 及 router 节点的ID必须是 admin
文件: app/code/Aqrun/HelloWorld/etc/adminhtml/routes.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<!-- 后台使用 admin 路由 -->
<router id="admin">
<!-- 定义路由及两个属性 -->
<route id="aqrun_helloworld" frontName="aqrun_helloworld">
<!-- 匹配的模块 -->
<module name="Aqrun_HelloWorld"/>
</route>
</router>
</config>
后面页面URL和前台页面一样,但会在路由名称前添加后台范围名来标识这是后台的路由。如下URL
http://example.xom/index.php/admin/aqrun_helloworld/controller/action
后台页面的控制器动作的目录是 Controller/Adminhtml
。上面的URL动作文件就是:
{namespace}/{module}/Controller/Adminhtml/{Controller}/{Action}.php
使用路由重写控制器
接下来研究一下如何使用路由实现控制器重写。根据上面的内容,可以看出每个路由都有ID属性标识。那如果我们使用一样的ID属性定义两个路由会发生什么呢?
答案就是匹配两个模块中的控制器动作。Magento 系统提供了 before/after 属性来配置模块的排序,用来指定哪个模块的控制器先被发现。这就是控制器重写的逻辑。
比如我们要重写控制器 customer/account/login
,我们会在 routes.xml 如下定义
文件: app/code/Aqrun/HelloWorld/etc/frontend/routnes.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<!-- 前台使用 'standard'-->
<router id="standard">
<!--定义路由-->
<route frontName="helloworld" id="helloworld">
<!--指定模块-->
<module name="Aqrun_HelloWorld"/>
</route>
<route id="customer">
<module name="Aqrun_HelloWorld" before="Magento_Customer" />
</route>
</router>
</config>
对应的控制器文件: app/code/Aqrun/HelloWorld/Controler/Account/Login.php
因此 frontControler 类会先匹配到我们的模块中的 Login 动作,如果匹配到了就会执行我们的动作而 Magneto_Customer 模块的动作不会再执行。这样我们就完成了控制的重写。
你也可以在模块中使用和其它模块一样的路由相当于配置了另一个模块。比如上面的配置文件,你可以使用 customer 路由调用你的控制器和动作。如果控制器是 'Blog' 动作是 'Index.php', 那访问的URL就是:
http://example.xom/customer/blog/index