magento2中的路由
我们可以说路由,是Magento中最重要的部分。Magento2中完整的进程流取决于URL进程请求和路由器类(用来响应匹配和执行请求)。这篇文章包含了Magento2中路由器流并分析一些默认的路由器。当然,这里也会演示怎么去常见一个自定义的路由器,提到路由器是如何匹配到方法(控制器类),更多关于控制器的信息将会在其它文章中提到。
路由流
首先,我们需要分析完整的路由流,这样我们就能为后面的部分了解更多的细节。我们之前就知道,Magento2在请求启动的流类(launch method)里创建HTTP进程。我们的流以创建前端控制器开始。
$frontController = $this->_objectManager->get('Magento\Framework\App\FrontControllerInterface');
前端控制器对循环槽中所有可用的路由器和当前请求匹配的路由进行响应。稍后我们将详细讲解前端路由。现在,还是先理解整个流程,其中进程如何匹配路由器是重点。路由器列表被创建在RouterList(在前端控制器路由器循环里被调用)类里,位于Magento\Framework\App中。这个类用来排列和迭代路由器列表。路由器类负责匹配响应当前请求的路由器。让我们看看Magento2的流程。
index.php (runs bootstrap and create HTTP application) → HTTP app → FrontController → Routing → Controller processing → etc
现在我们将分析路由流程的每个部分以便更好地理解Magento2的路由。
前端控制器
和Magento1中一样,这是路由入口,被HTTP启动进程(launch method)调用。负责对当前请求路由进行匹配。它位于lib/internal/Magento/Framework/App/FrontController.php。你可以在HTTP启动方法FrontControllerInterface里找到。让我们看看这段代码吧。
class Http implements \Magento\Framework\AppInterface
{
public function launch()
{
...
//Here Application is calling front controller and it's dispatch method
$frontController = $this->_objectManager->get('Magento\Framework\App\FrontControllerInterface');
$result = $frontController->dispatch($this->_request);
...
}
}
现在,我们知道了前端控制器何时、如何被调用,让我们看看前端控制器类(Front controller class)和它的调度方法: lib/internal/Magento/Framework/App/FrontController.php
class FrontController implements FrontControllerInterface
{
public function dispatch(RequestInterface $request)
{
\Magento\Framework\Profiler::start('routers_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);
$actionInstance-->getResponse()-->setNoCacheHeaders();
$result = $actionInstance-->dispatch($request);
break;
}
} catch (\Magento\Framework\Exception\NotFoundException $e) {
$request-->initForward();
$request-->setActionName('noroute');
$request-->setDispatched(false);
break;
}
}
}
\Magento\Framework\Profiler::stop('routers_match');
if ($routingCycleCounter > 100) {
throw new \LogicException('Front controller reached 100 router match iterations');
}
return $result;
}
}
如你所见,调度方法将循环所有的路由器(可用的)直到一个路由被匹配,需求被调度($request→setDispatched(true);) ,或者路由计数器执行到100。路由器可以被匹配,但是如果没有调度,就会重复路由器循环。此外,路由器可以被重定向调度,或者它可以被匹配和处理。路由器列表类在请求流中被解析。现在,我们再看看路由器是如何匹配(匹配方法)的以及究竟什么是路由器。
路由器
简单来说,路由器就是负责匹配执行URL请求的PHP类。默认的Magento框架和核心里有些Base、DefaultRouter、Cms和UrlRewrite这样的路由器。所有这些我们都将讨论,解释它们的目标和如何工作的。路由器正在执行RouterInterface。现在让我们看看默认的路由器流:
Base Router → CMS Router → UrlRewrite Router → Default Router (这是路由器环路—— FrontController::dispatch())
Base Router
Base Router位于lib/internal/Magento/Framework/App/Router/Base.php,它是环路中的第一个。如果你是magento1开发者的话,你知道它作为标准路由器。匹配方法是parseRequest和matchAction,在第二个方法中会设置前端模块名,控制器路径名,动作名,控制器模和路由名。在基础路由器中标准Magento URL(front name/action path/action/param 1/etc params/) 被匹配。
CMS Router
CMS Router位于app/code/Magento/Cms/Controller/Router.php,用于处理CMS页面。它设置cms模块名(模块前端名),page控制器名(控制器路径名)和view(app/code/Magento/Cms/Controller/Page/View.php)动作名。为Base controller设置好格式后,它将设置页面ID并转发这个ID,但不会调度它。转发意味着它会破坏现有的路由器循环,再次开始循环(最多可执行100次)。这个路由器循环将匹配激活View controller(Cms/Controller/Page)的基础路由器,显示被存储的页面ID(根据url找到的页面ID)。
UrlRewrite Router
在Magento2中,UrlRewrite有了它自己的路由器。熟悉Magento1的朋友知道UrlRewrite是Standard路由器的一部分。它位于app/code/Magento/UrlRewrite/Controller/Router.php使用Url Finder获取数据库中匹配的url重写。
$rewrite = $this->urlFinder->findOneByData(
[
UrlRewrite::ENTITY_TYPE => $oldRewrite->getEntityType(),
UrlRewrite::ENTITY_ID => $oldRewrite->getEntityId(),
UrlRewrite::STORE_ID => $this->storeManager->getStore()->getId(),
UrlRewrite::IS_AUTOGENERATED => 1,
]
);
它会转向CMS router这样的动作。
Default Router
它位于lib/internal/Magento/Framework/App/Router/DefaultRouter.php是路由器循环的最后一个。当其它路由都不匹配的时候才使用它。在Magento2中,我们可以为"404找不到"页面创建一个自定义的句柄来展示自定义内容。下面是DefaultRouter中没有路由处理情况下的循环。
foreach ($this->noRouteHandlerList->getHandlers() as $noRouteHandler) {
if ($noRouteHandler->process($request)) {
break;
}
}
Custom Router (附带示例)
前端控制器会浏览所有路由器列表中的路由(由routes.xml配置创建),所以我们需要将di.xml模块中新加路由器的配置添加到lib/internal/Magento/Framework/App/RouterList.php,由此来增加自定义路由器。我们将创建一个新的模块(例:Alwayly/CustomRouter),之后我们添加心得路由器到路由器列表,最后创建路由器类。
自定义路由器只是一个例子,在这个例子中你能了解Base router是如何匹配和跳转需求的。首先,我们需要在app/code/Alwayly/CustomRouter中为我们的模块创建一个文件夹结构,接着我们将在etc文件夹中创建modules.xml,在模块根目录下创建composer.json(带有模块信息)。现在,我们可以通过添加配置到di.xml(etc/frontend)来创建自定义路由器,这里我们的路由器只针对与前端。最后,我们将在Controller文件夹里创建Router.php来编写匹配路由器的逻辑。我们将扫描URL,确认是否有特定字,然后根据这些字简历前端模块名、控制器路径和给默认控制器的跳转请求。作为示例,我们要搜索的两个字是"examplerouter"和"exampletocms"。当搜索到"examplerouter"时,我们将跳转到Base router匹配格式(这里,我们设置前端模块名为"alwaylytest",控制器路径名为"test",动作名为"test"),当搜索到"exampletocms"时,我们通过Base router跳转到About us页面。
di.xml(位于etc/frontend)
< ?xml version="1.0"?>
< config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
< type name="Magento\Framework\App\RouterList">
< arguments>
< argument name="routerList" xsi:type="array">
< item name="alwaylycustomrouter" xsi:type="array">
< item name="class" xsi:type="string"> Alwayly\CustomRouter\Controller\Router< /item>
< item name="disable" xsi:type="boolean"> false< /item>
< item name="sortOrder" xsi:type="string"> 22< /item>
< /item>
< /argument>
< /arguments>
< /type>
< /config>
Router.php (位于Controller文件夹)
<?php
namespace Alwayly\CustomRouter\Controller;
class Router implements \Magento\Framework\App\RouterInterface
{
/**
* @var \Magento\Framework\App\ActionFactory
*/
protected $actionFactory;
/**
* Response
*
* @var \Magento\Framework\App\ResponseInterface
*/
protected $_response;
/**
* @param \Magento\Framework\App\ActionFactory $actionFactory
* @param \Magento\Framework\App\ResponseInterface $response
*/
public function __construct(
\Magento\Framework\App\ActionFactory $actionFactory,
\Magento\Framework\App\ResponseInterface $response
) {
$this->actionFactory = $actionFactory;
$this->_response = $response;
}
/**
* Validate and Match
*
* @param \Magento\Framework\App\RequestInterface $request
* @return bool
*/
public function match(\Magento\Framework\App\RequestInterface $request)
{
/*
* We will search “examplerouter” and “exampletocms” words and make forward depend on word
* -examplerouter will forward to base router to match alwaylytest front name, test controller path and test controller
class
* -exampletocms will set front name to cms, controller path to page and action to view
*/
$identifier = trim($request->getPathInfo(), '/');
if(strpos($identifier, 'exampletocms') !== false) {
/*
* We must set module, controller path and action name + we will set page id 5 witch is about us page on
* default magento 2 installation with sample data.
*/
$request->setModuleName('cms')->setControllerName('page')->setActionName('view')->setParam('page_id', 5);
} else if(strpos($identifier, 'examplerouter') !== false) {
/*
* We must set module, controller path and action name for our controller class(Controller/Test/Test.php)
*/
$request->setModuleName('alwaylytest')->setControllerName('test')->setActionName('test');
} else {
//There is no match
return;
}
/*
* We have match and now we will forward action
*/
return $this->actionFactory->create(
'Magento\Framework\App\Action\Forward',
['request' => $request]
);
}
}
routes.xml (位于etc/frontend)
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../lib/internal/Magento/Framework/App/etc/routes.xsd">
< router id="standard">
<route id="alwaylytest" frontName="alwaylytest">
<module name="Alwayly_CustomRouter" />
</route>
</router>
</config>
Test.php (test controller action class)
<?php
namespace Alwayly\CustomRouter\Controller\Test;
class Test extends \Magento\Framework\App\Action\Action
{
public function execute()
{
die("Alwayly\\CustomRouter\\Controller\\Test\\Test controller execute()");
}
}