Magento 2中的控制器,就像其他MVC框架一样,是MVC流程的重要组成部分。在Magento 2中,控制器有很多变化,例如:与Magento 1相比,控制器的结构和工作方式。如果您熟悉Magento 1控制器,那么您就会知道它们可以有多个控制方法。在Magento 2控制器中只有一个方法(执行)将由前端控制器调用。

本篇博客将介绍控制器基础、匹配流程、控制器类型(后端和前端)、对现有控制器的更改、创建自定义控制器的说明以及如何创建几个控制器的示例。

 

控制器

Controller是位于模块Controller文件夹中的类,负责特定的Url或Url组。它与Magento 1中的控制器不同,因为它只有一个执行方法(在Magento 1中控制方法想要多少就有多少),所有需要的数据都是用对象管理器在DI中填充的。对于每个action,我们都有一个带有执行方法的控制器类。当路由匹配控制器action类时,调用Execute方法,它负责将响应返回给前端控制器。所有控制器继承于\Magento\Framework\App\Action\Action类,该类具有将调用控制器中的执行方法的分派方法,我们稍后会介绍流程。有两种控制器类型:前端和后端,它们具有相似的行为,但后端有额外的权限检查方法。控制器以特定方式构建,以便它们可以匹配,用于匹配的URL结构是:

  • frontName - 它在routes.xml配置中设置,并具有唯一值,将由路由器匹配
  • action path - Controller文件夹内的文件夹名称,默认为index
  • action class - 我们称之为Controller的action类,默认为索引

现在,我们将通过操作控制器方法并解释它们的作用。

执行方法

在action类下的执行方法是默认被调用的,它继承自\Magento\Framework\App\Action\Action。它由\Magento\Framework\App\Action\Action::dispatch()方法调用,在这个方法中,我们应该具有所有的控制器逻辑(当然,我们可以在附加方法中有逻辑,但是execute方法将调用它们),并且它将返回响应(主要是呈现的页面)。

 

\Magento\Framework\App\Action\Action

这是主要的Magento框架action类,每个控制器都必须继承自这个类(后端控制器继承自\Magento\Backend\App\Action,而这个类继承自\Magento\Framework\App\Action\Action)。重要的是每个控制器都要继承这个类以继承所需的方法,并允许前端控制器调用dispatch方法(它将调用execute方法)。

Dispatch

这个方法将首先被前端控制器调用(Magento\Framework\App\FrontController)

Magento\Framework\App\FrontController::dispatch() - 在我们的Action类中调用dispatch:

$result = $actionInstance->dispatch($request);

重要的是要了解我们的Action类中的dispatch方法被用于前端控制器。对于后端控制器,dispatch方法在\Magento\Backend\App\Action中被重写,因此它可以检查用户是否允许访问。让我们来说明一下dispatch方法:

/**
     * Dispatch request
     *
     * @param RequestInterface $request
     * @return ResponseInterface
     * @throws NotFoundException
     */
    public function dispatch(RequestInterface $request)
    {
        $this->_request = $request;
        $profilerKey = 'CONTROLLER_ACTION:' . $request->getFullActionName();
        $eventParameters = ['controller_action' => $this, 'request' => $request];
        $this->_eventManager->dispatch('controller_action_predispatch', $eventParameters);
        $this->_eventManager->dispatch('controller_action_predispatch_' . $request->getRouteName(), $eventParameters);
        $this->_eventManager->dispatch(
            'controller_action_predispatch_' . $request->getFullActionName(),
            $eventParameters
        );
        \Magento\Framework\Profiler::start($profilerKey);
 
        $result = null;
        if ($request->isDispatched() && !$this->_actionFlag->get('', self::FLAG_NO_DISPATCH)) {
            \Magento\Framework\Profiler::start('action_body');
            $result = $this->execute();
            \Magento\Framework\Profiler::start('postdispatch');
            if (!$this->_actionFlag->get('', self::FLAG_NO_POST_DISPATCH)) {
                $this->_eventManager->dispatch(
                    'controller_action_postdispatch_' . $request->getFullActionName(),
                    $eventParameters
                );
                $this->_eventManager->dispatch(
                    'controller_action_postdispatch_' . $request->getRouteName(),
                    $eventParameters
                );
                $this->_eventManager->dispatch('controller_action_postdispatch', $eventParameters);
            }
            \Magento\Framework\Profiler::stop('postdispatch');
            \Magento\Framework\Profiler::stop('action_body');
        }
        \Magento\Framework\Profiler::stop($profilerKey);
        return $result ?: $this->_response;
    }

正如我们看到的,我们的dispatch方法将返回来自上下文的结果或响应。它将检查是否分派了请求,而不是在 \Magento\Framework\App\Action\Action类中调用execute方法:

$result = $this→execute();

我们的action类中还有两个更重要的方法:_forward和_redirect,让我们来说明一下这两个方法:

Forward method

这个受保护的方法将控制转移到另一个action控制器、控制器路径和模块。这不是重定向,它将运行另一个路由循环,将控制传递给另一个控制器action。

Redirect method

它将通过设置响应头和重定向Url来重定向新Url上的用户。

 

控制器action匹配流程

FrontController::dispatch() → Router::match() → Controller::dispatch() -> 

Controller::execute()

在上面你可以看到高水平流量,但由于流量比这更复杂,让我们快速通过更详细的水平:

在上面可以看到高级流程,但是由于流程比这稍微复杂一些,让我们在更详细的层面上快速进行:

FrontController::dispatch()

while (!$request->isDispatched() && $routingCycleCounter++ < 100) {
            /** @var \Magento\Framework\App\RouterInterface $router */
            foreach ($this->_routerList as $router) {
                try {
                    $actionInstance = $router->match($request);

它将首先匹配路由器,如上面的代码所示,路由匹配将返回action类(\Magento\Framework\App\ActionFactory)实例。之后,前端控制器将在action类实例上调用dispatch方法:

$result = $actionInstance->dispatch($request);

由于我们已经介绍了dispatch方法,它将调用action类的execute方法:

$result = $this->execute();

这是应用程序流程在action类execute方法中的简单方式。

 

后端与前端控制器的区别

这两个控制器之间的主要区别在于后端控制器中的附加检查和其他方法。两个控制器最终都继承自\Magento\Framework\App\Action\Action类,但后端控制器继承了继承自\Magento\Framework\App\Action\Action的\Magento\Backend\App\Action class类。在后端控制器dispatch中,redirect 和 rewrite 方法以提供用于检查ACL(访问控制列表)的逻辑。

 

后端控制器

它继承自\Magento\Backend\App\Action类,并具有检查访问控制的_isAllowed方法。在dispatch方法中,它将检查是否允许用户访问当前Url并且它将重定向到登录(如果不允许用户)或者它将设置响应状态403(禁止):

public function dispatch(\Magento\Framework\App\RequestInterface $request)
    {
        if (!$this->_processUrlKeys()) {
            return parent::dispatch($request);
        }
 
        if ($request->isDispatched() && $request->getActionName() !== 'denied' && !$this->_isAllowed()) {
            $this->_response->setStatusHeader(403, '1.1', 'Forbidden');
            if (!$this->_auth->isLoggedIn()) {
                return $this->_redirect('*/auth/login');
            }
            $this->_view->loadLayout(['default', 'adminhtml_denied'], true, true, false);
            $this->_view->renderLayout();
            $this->_request->setDispatched(true);
            return $this->_response;
        }
 
        if ($this->_isUrlChecked()) {
            $this->_actionFlag->set('', self::FLAG_IS_URLS_CHECKED, true);
        }
 
        $this->_processLocaleSettings();
 
        return parent::dispatch($request);
    }

如果要创建后端控制器并想要添加一些自定义权限,则需要在_isAllowed方法中添加访问权限检查,例如:

protected function _isAllowed()
{
     return $this->_authorization->isAllowed('Magento_EncryptionKey::crypt_key');
}

 

对现有控制器的更改

对于更改现有控制器,有两种方法可以更改它们。你可以通过喜好,插件或像Magento 1之前/之后的“旧”风格来执行此操作。首选项将用您的控制器代码完全更改控制器(我们可以称之为完全重写)。插件将只改变所需的控制方法。最后,after和before将更改自定义前端名称的控制器的位置。例如,如何在管理区域添加新的控制器:

<router id="admin">
    <route id="catalog" frontName="catalog">
        <module name="Magento_Catalog" before="Magento_Backend" />
    </route>
</router>

 

Action wrapper类

Action wrapper类是Controller文件夹中的一个类,它继承自Magento\Framework\App\Action\Action,然后我们的action类扩展了该action wrapper。如果您有多个action类的通用逻辑,那么我们将在action wrapper类中写入逻辑,并在我们需要的每个action类上使用它。

让我们看看,例如Magento\Catalog\Controller\Product是action wrapper类,它在Catalog\Controller\*文件夹中的许多action类中使用(action classes: Magento\Catalog\Controller\Product\CompareMagento\Catalog\Controller\Product\Gallery 等),所有这些都使用_initProduct,它通过helper加载产品。

/**
 * Product controller.
 *
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Magento\Catalog\Controller;
 
use Magento\Catalog\Controller\Product\View\ViewInterface;
use Magento\Catalog\Model\Product as ModelProduct;
 
abstract class Product extends \Magento\Framework\App\Action\Action implements ViewInterface
{
    /**
     * Initialize requested product object
     *
     * @return ModelProduct
     */
    protected function _initProduct()
    {
        $categoryId = (int)$this->getRequest()->getParam('category', false);
        $productId = (int)$this->getRequest()->getParam('id');
 
        $params = new \Magento\Framework\DataObject();
        $params->setCategoryId($categoryId);
 
        /** @var \Magento\Catalog\Helper\Product $product */
        $product = $this->_objectManager->get('Magento\Catalog\Helper\Product');
        return $product->initProduct($productId, $this, $params);
    }
}

 

如何创建自定义控制器

  1. 在 etc/frontend 或 etc/adminhtml 文件夹中创建routes.xml(第一个用于前端,第二个用于后端)。
  2. 在routes.xml中为控制器添加自定义配置,例如:
    - router:id - standard (frontend)/admin
    - route:id - 您的唯一路由ID 
    - route:frontName - url中的唯一名称,这是基础路由url的第一部分
    - 模块名称 - 您的模块名称
  3. 按照上面的url结构创建action类:
    Controller/Actionpath/Actionclass.php

示例 - 后端和前端控制器

我们将创建用于自定义控制器演示的示例模块。首先,我们创建一个模块:

etc/module.xml:

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2015 Mageased.o.o.
 * created by Zoran Salamun(zoran.salamun@magease.net)
 * Module is created for Custom Controllers demonstration
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd">
    <module name="Magease_CustomControllers" setup_version="2.0.0"></module>
</config>

etc/frontend/routes.xml - 前端的路由配置; 为了演示,我们将匹配“mageasefronttest”作为前缀

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2015 Magease d.o.o.
 * created by Zoran Salamun(zoran.salamun@magease.net)
 * Module is created for Custom Controllers demonstration
 */
-->
<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="mageasetestfrontend" frontName="mageasefronttest">
            <module name="Magease_CustomControllers" />
        </route>
    </router>
</config>

etc/adminhtml/routes.xml - 后端的路由配置,为了演示我们将匹配“mageaseadmintest”作为frontname( /admin/之后的url的一部分)

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2015 Magease d.o.o.
 * created by Zoran Salamun(zoran.salamun@magease.net)
 * Module is created for Custom Controllers demonstration
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../lib/internal/Magento/Framework/App/etc/routes.xsd">
    <router id="admin">
        <route id="mageaseadmintest" frontName="mageaseadmintest">
            <module name="Magease_CustomControllers" before="Magento_Backend" />
        </route>
    </router>
</config>

现在我们将为两个控制器配置创建action类。在前端,让我们为url siteurl/mageasefronttest/demonstration/sayhello/创建action路径和action类。您可以在Controller文件夹中看到我们需要Demonstration文件夹(这称为action路径),在该文件夹中我们需要Sayhello.php控制器action类:

Controller/Demonstration/Sayhello.php:

<?php
/**
 * Copyright © 2015 Magease d.o.o.
 * created by Zoran Salamun(zoran.salamun@magease.net)
 */
namespace Magease\CustomControllers\Controller\Demonstration;
 
class Sayhello extends \Magento\Framework\App\Action\Action
{
    /**
     * say hello text
     */
    public function execute()
    {
        die("Hello ???? - Magease\\CustomControllers\\Controller\\Demonstration\\Sayhello - execute() method");
    }
}

对于后端控制器,让我们匹配url siteurl/admin/mageaseadmintest/演示/sayadmin/。为此,我们需要Controller/Adminhtml中的Demonstration文件夹,在该action类中我们需要创建Sayadmin.php

Controller/Adminhtml/Demonstration/Sayadmin.php

<?php
/**
 * Copyright © 2015 Magease d.o.o.
 * created by Zoran Salamun(zoran.salamun@magease.net)
 */
namespace Magease\CustomControllers\Controller\Adminhtml\Demonstration;
 
class Sayadmin extends \Magento\Backend\App\Action
{
    /**
     * say admin text
     */
    public function execute()
    {
        die("Admin ???? - Magease\\CustomControllers\\Controller\\Adminhtml\\Demonstration\\Sayadmin - execute() method");
    }
}

注意,默认情况下_isAllowed将返回true,为了创建自定义权限,您必须在该方法下添加check。为了演示,我们将保留默认值,以便用户可以访问该控制器。

注意:添加新控制器后,从Magento root中的控制台刷新所有内容:

php bin/magento setup:upgrade

我希望这篇博客能帮助您理解Magento 2中的控制器。