17 май 2017
Автоматизация затрагивает всё: расчет стоимости доставки, создание и отправка накладной, получение PDF этикетки, отслеживание статусов и многое другое.
Выводить всю информацию (кнопки, статусы, формы отправки) на базовом виде заказа - не удобно, можно вынести всё в дополнительную вкладку.
Пример вкладки для Magento 1:

Опираясь на опыт с Magento 1, сделаем аналогичный функционал для Magento 2.
Создание модуля
Создадим базу модуля, подключив его через composer к нашему магазину. В нашем примере — модуль разрабатывается как готовый пакет (package) для Magento 2. В тег name вписывается название пакета, по которому модуль будет искаться в репозиториях. M2 работает с autoload (PSR-4), что упрощает работу с различными внешними модулями.
composer.json
{ "name": "vendor/module", "type": "magento2-module", "license": "proprietary", "homepage": "http://www.example.ru/magento2-module.html", "description": "Модуль доставки", "keywords": [ "magento", "module", "delivery", "vendor" ], "authors": [ { "name": "Vendor", "email": "[email protected]", "homepage": "http://www.vendor.ru/" } ], "require": { "php": "~5.5.0|~5.6.0|~7.0.0", "magento/framework": "*" }, "repositories": [ { "type": "composer", "url": "https://repo.magento.com" } ], "autoload": { "files": [ "registration.php" ], "psr-4": { "Vendor\\Module\\": "" } } }
Для работы модуля нужно задать его зарегистрировать в системе как компонент.
registration.php
<?php \Magento\Framework\Component\ComponentRegistrar::register( \Magento\Framework\Component\ComponentRegistrar::Module, 'Vendor_Module', __DIR__ );
и etc/module.xml
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> <module name="Vendor_Module" setup_version="2.0.0"> </module> </config>
Вторым шагом добавляем layout
Добавляем блок в нужную секцию layout.
view/adminhtml/layout/sales_order_view.xml
<?xml version="1.0"?> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="admin-2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="left"> <referenceBlock name="sales_order_tabs"> <action method="addTab"> <argument name="name" xsi:type="string">Vendor Module</argument> <argument name="block" xsi:type="string">Vendor\Module\Block\Adminhtml\Order\View\Tab\Module</argument> </action> </referenceBlock> </referenceContainer> </body> </page>
В теге argument мы передаем название блока, который будет подключен в качестве дополнительной вкладки на странице заказов
Определим сам блок:
Block/Adminhtml/Order/View/Tab/Module.php
<?phpnamespace Vendor\Module\Block\Adminhtml\Order\View\Tab;
class Module extends \Magento\Backend\Block\Template implements \Magento\Backend\Block\Widget\Tab\TabInterface {
<span style="color: #008800; font-weight: bold">protected</span> <span style="color: #996633">$_template</span> <span style="color: #333333">=</span> <span style="background-color: #fff0f0">'order/view/tab/module.phtml'</span>; <span style="color: #DD4422">/**</span>
* * @var \Magento\Framework\Registry */ protected $coreRegistry = null;
<span style="color: #DD4422">/**</span>
* @param \Magento\Backend\Block\Template\Context $context * @param \Magento\Framework\Registry $registry * @param array $data */ public function __construct( \Magento\Backend\Block\Template\Context $context, \Magento\Framework\Registry $registry, array $data = [] ) { $this->coreRegistry = $registry; parent::__construct($context, $data); }
<span style="color: #008800; font-weight: bold">public</span> <span style="color: #008800; font-weight: bold">function</span> <span style="color: #0066BB; font-weight: bold">getOrder</span>() { <span style="color: #008800; font-weight: bold">return</span> <span style="color: #996633">$this</span><span style="color: #333333">-></span><span style="color: #0000CC">coreRegistry</span><span style="color: #333333">-></span><span style="color: #0000CC">registry</span>(<span style="background-color: #fff0f0">'current_order'</span>); } <span style="color: #DD4422">/**</span>
* {@inheritdoc} */ public function getTabLabel() { return __(‘Module’); }
<span style="color: #DD4422">/**</span>
* {@inheritdoc} */ public function getTabTitle() { return __(‘Module’); }
<span style="color: #DD4422">/**</span>
* {@inheritdoc} */ public function canShowTab() { return true; }
<span style="color: #DD4422">/**</span>
* {@inheritdoc} */ public function isHidden() { return false; }
<span style="color: #DD4422">/**</span>
* * @return string */ public function getTabClass() { return ‘ajax only’; }
<span style="color: #DD4422">/**</span>
* * @return string */ public function getClass() { return $this->getTabClass(); }
<span style="color: #DD4422">/**</span>
* * @return string / public function getTabUrl() { return $this->getUrl(‘vendor_module//module’, [‘_current’ => true]); }
}
В функции $this->getUrl для получения url мы передаем параметры: адрес нужного нам роута 'vendor_module/*/module' и дополнительный массив параметров ['_current' => true].
Символ * в адресе роута 'vendor_module/*/module означает передачу текущего используемого роута. То есть, если мы находимся на контроллере sales, то вместо * в итоговом адресе будет тот же контроллер.
Параметр '_current' => true отвечает за передачу таких же дополнительных параметрах к основному адресу при получении ссылки. К примеру, если мы находимся на странице с параметрами order_id/12, то эти же параметры будут переданы при генерации url. Если мы не указываем нужный нам путь, то будет использован тот же модуль, контроллер и action.
Функцию canShowTab можно использовать для того, чтобы в нее передавать условия показа вкладки. К примеру - проверять, ваш ли метод доставки используется в этом заказе.
К примеру, определить принадлежность по $order в carrier можно так:
/** * * @param \Magento\Sales\Model\Order $order * @return boolean */ public function isShippedBy(\Magento\Sales\Model\Order $order) { if (strpos($order->getShippingMethod(), $this->_code . '_') !== false) { return true; } return false; }
Подошли к важному отличию Magento 2 от Magento 1 - в панели администрирования, данные по вкладкам грузятся ajax'ом, поэтому для модуля нужно создать роут (route)
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"> <router id="admin"> <route id="vendor_module" frontName="vendor_module"> <module name="Vendor_Module"/> </route> </router> </config>
Создадим контроллер, который будет отвечать за вкладку:
Controller/Adminhtml/Order/CustomTab.php
<?phpnamespace Vendor\Module\Controller\Adminhtml\Order;
use Magento\Backend\App\Action; use Magento\Sales\Api\OrderManagementInterface; use Magento\Sales\Api\OrderRepositoryInterface; use Psr\Log\LoggerInterface;
class Module extends \Magento\Sales\Controller\Adminhtml\Order { /** * @var \Magento\Framework\View\LayoutFactory */ protected $layoutFactory;
<span style="color: #DD4422">/**</span>
* @param Action\Context $context * @param \Magento\Framework\Registry $coreRegistry * @param \Magento\Framework\App\Response\Http\FileFactory $fileFactory * @param \Magento\Framework\Translate\InlineInterface $translateInline * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory * @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory * @param \Magento\Framework\View\Result\LayoutFactory $resultLayoutFactory * @param \Magento\Framework\Controller\Result\RawFactory $resultRawFactory * @param OrderManagementInterface $orderManagement * @param OrderRepositoryInterface $orderRepository * @param LoggerInterface $logger * @param \Magento\Framework\View\LayoutFactory $layoutFactory * * @SuppressWarnings(PHPMD.ExcessiveParameterList) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ public function __construct( Action\Context $context, \Magento\Framework\Registry $coreRegistry, \Magento\Framework\App\Response\Http\FileFactory $fileFactory, \Magento\Framework\Translate\InlineInterface $translateInline, \Magento\Framework\View\Result\PageFactory $resultPageFactory, \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory, \Magento\Framework\View\Result\LayoutFactory $resultLayoutFactory, \Magento\Framework\Controller\Result\RawFactory $resultRawFactory, OrderManagementInterface $orderManagement, OrderRepositoryInterface $orderRepository, LoggerInterface $logger, \Magento\Framework\View\LayoutFactory $layoutFactory ) { $this->layoutFactory = $layoutFactory; parent::__construct( $context, $coreRegistry, $fileFactory, $translateInline, $resultPageFactory, $resultJsonFactory, $resultLayoutFactory, $resultRawFactory, $orderManagement, $orderRepository, $logger ); }
<span style="color: #008800; font-weight: bold">public</span> <span style="color: #008800; font-weight: bold">function</span> <span style="color: #0066BB; font-weight: bold">execute</span>() { <span style="color: #996633">$this</span><span style="color: #333333">-></span><span style="color: #0000CC">_initOrder</span>(); <span style="color: #996633">$layout</span> <span style="color: #333333">=</span> <span style="color: #996633">$this</span><span style="color: #333333">-></span><span style="color: #0000CC">layoutFactory</span><span style="color: #333333">-></span><span style="color: #0000CC">create</span>(); <span style="color: #996633">$html</span> <span style="color: #333333">=</span> <span style="color: #996633">$layout</span><span style="color: #333333">-></span><span style="color: #0000CC">createBlock</span>(<span style="background-color: #fff0f0">'Vendor\Module\Block\Adminhtml\Order\View\Tab\Module'</span>) <span style="color: #333333">-></span><span style="color: #0000CC">toHtml</span>(); <span style="color: #996633">$this</span><span style="color: #333333">-></span><span style="color: #0000CC">_translateInline</span><span style="color: #333333">-></span><span style="color: #0000CC">processResponseBody</span>(<span style="color: #996633">$html</span>); <span style="color: #DD4422">/** @var \Magento\Framework\Controller\Result\Raw $resultRaw */</span> <span style="color: #996633">$resultRaw</span> <span style="color: #333333">=</span> <span style="color: #996633">$this</span><span style="color: #333333">-></span><span style="color: #0000CC">resultRawFactory</span><span style="color: #333333">-></span><span style="color: #0000CC">create</span>(); <span style="color: #996633">$resultRaw</span><span style="color: #333333">-></span><span style="color: #0000CC">setContents</span>(<span style="color: #996633">$html</span>); <span style="color: #008800; font-weight: bold">return</span> <span style="color: #996633">$resultRaw</span>; }
}
В данном контроллере функция execute() непосредственно отвечает за выполнение action.
Отметим еще отличие Magento 2 от Magento 1: в M2 каждый action на контроллер делается как отдельный файл. В M1 контроллер мог включать в себя несколько action сразу.
Создадим шаблон вывода:
order/view/tab/module.phtml
<section class="admin__page-section module-tab-content"> <h1>Привет из Санкт-Петербурга</h1> </section>
Делаем очистку кеша
php bin/magento cache:clean
Заходим в заказ и проверяем:

Кликаем:

Conclusion
Готово! Надеемся, что данная статья расширила ваши представления о принципах разработки на Magento 2.