Magento 2: Создание грида в adminhtml

  • Tutorial

Краткая памятка по созданию гридов в админке Magento 2. В качестве примера я взял простой грид из трех колонок, данные для которого (коды стран по ISO 3166) поставляются из прописанного в коде массива. Для того, чтобы сфокусироваться на основных аспектах построения грида я отбросил из дескриптора UI-компонента максимум возможного (дополнительные кнопки, фильтры, сортировка, bookmarks, ...) и часть настроек перенес в конструктор провайдера данных для грида. Если можно сделать еще короче без потери читабельности — с максимальным удовлетворением внесу соответствующие правки (UPD: спасибо коллеге Oxidant за контроллер). Код примера на github'е.





ACL


Создаем запись в ACL (./etc/acl.xml) для контроля доступа к гриду:


<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd">
    <acl>
        <resources>
            <resource id="Magento_Backend::admin">
                <resource id="Flancer32_Sample::sample" title="Samples" sortOrder="10">
                    <resource id="Flancer32_Sample::sample_grid" title="Grid" sortOrder="100"/>
                </resource>
            </resource>
        </resources>
    </acl>
</config>


Добавляем в меню админки (./etc/adminhtml/menu.xml) дополнительные пункты:


<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">
    <menu>
        <add id="Flancer32_Sample::sample"
                title="Sample" translate="title" module="Flancer32_Sample"
                sortOrder="15"
                resource="Flancer32_Sample::sample"/>
        <add id="Flancer32_Sample::sample_grid"
                title="Grid" translate="title" module="Flancer32_Sample"
                sortOrder="100" parent="Flancer32_Sample::sample"
                action="sample/grid"
                resource="Flancer32_Sample::sample_grid"/>
    </menu>
</config>

Адрес перенаправления определяется в action="...", доступ к пунктам меню — в resource="...".


Routes


В файле ./etc/adminhtml/routes.xml регистрируем маршурт fl32_sample_route (внутренний идентификатор) с именем sample (видимый идентификатор, часть URL'а):


<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="admin">
        <route id="fl32_sample_route" frontName="sample">
            <module name="Flancer32_Sample"/>
        </route>
    </router>
</config>

Controller


Обработчики запросов по адресу .../index.php/admin/sample/grid/* размещаем в каталоге ./src/Controller/Adminhtml/Grid/. Обработчик по-умолчанию: Index.php:


namespace Flancer32\Sample\Controller\Adminhtml\Grid;

class Index
    extends \Magento\Backend\App\Action
{
    const ACL_RESOURCE = 'Flancer32_Sample::sample_grid';
    const MENU_ITEM = 'Flancer32_Sample::sample_grid';
    const TITLE = 'Sample Grid';

    protected function _isAllowed()
    {
        $result = parent::_isAllowed();
        $result = $result && $this->_authorization->isAllowed(self::ACL_RESOURCE);
        return $result;
    }

    public function execute()
    {
        /** @var \Magento\Backend\Model\View\Result\Page $resultPage */
        $resultPage = $this->resultFactory->create(\Magento\Framework\Controller\ResultFactory::TYPE_PAGE);
        $resultPage->setActiveMenu(self::MENU_ITEM);
        $resultPage->getConfig()->getTitle()->prepend(__(self::TITLE));
        return $resultPage;
    }
}

Все, что делает обработчик — проверяет права пользователя на доступ к гриду и формирует страницу в соответствии с заданным для данного маршрута laout'ом.


Layout


Описание layout'а находится в каталоге ./src/view/adminhtml/layout/ в файле fl32_sample_route_grid_index, название которого состоит из трех частей:


  • fl32_sample_route: внтуренний идентификатор маршрута (см. route.id в ./etc/adminhtml/routes.xml);
  • grid: вторая часть адреса перенаправления (см. action в ./etc/adminhtml/menu.xml);
  • index: имя обработчика по-умолчанию для запросов по адресу .../index.php/admin/sample/grid/ (см. ./src/Controller/Adminhtml/Grid/Index.php);

<?xml version="1.0"?>
<page
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="content">
            <uiComponent name="sample_grid"/>
        </referenceContainer>
    </body>
</page>

В описании задано, что в качестве контента на странице нужно вывести UI-компонент с именем sample_grid.


UI Component


Дескриптор компонента находится в файле ./src/view/adminhtml/ui_component/sample_grid.xml


<?xml version="1.0" encoding="UTF-8"?>
<listing
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <argument name="data" xsi:type="array">
        <item name="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">sample_grid.sample_grid_data_source</item>
            <item name="deps" xsi:type="string">sample_grid.sample_grid_data_source</item>
        </item>
        <item name="spinner" xsi:type="string">sample_grid_columns</item>
    </argument>
    <dataSource name="sample_grid_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Flancer32\Sample\Ui\Component\DataProvider\Grid</argument>
            <argument name="name" xsi:type="string">sample_grid_data_source</argument>
        </argument>
    </dataSource>
    <columns name="sample_grid_columns">
        <column name="code2">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="sorting" xsi:type="string">asc</item>
                    <item name="label" xsi:type="string" translate="true">Alpha-2</item>
                </item>
            </argument>
        </column>
        <column name="code3">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="label" xsi:type="string" translate="true">Alpha-3</item>
                </item>
            </argument>
        </column>
        <column name="code_num">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="label" xsi:type="string" translate="true">Numeric</item>
                </item>
            </argument>
        </column>
    </columns>
</listing>

Я постарался сделать код компонента как можно меньше (сравните с аналогичным для CMS Pages).


На что следует обратить внимание:


  • имя компонента `sample_grid` совпадает с именем файла и используется в описании `js_config` (provider & deps);
  • в настройках spinner'а указывается имя `columns`-компонента, после заполнения данными которого spinner скрывается;
  • настройки data source'а спрятаны в конструкторе класса `Flancer32\Sample\Ui\Component\DataProvider\Grid`;
  • имена столбцов грида совпадают с именами полей в данных;
  • без указания настроек сортировки (`sorting`) хотя бы для одного столбца грид не загружается;

DataProvider


За поставку данных отвечает класс \Flancer32\Sample\Ui\Component\DataProvider\Grid. Конструктор принимает из дескриптора компонента только один параметр (name), все остальные либо инжектятся Object Manager'ом при создании провайдера данных, либо создаются в нем же. Данные не зависят от фильтров/сортировки и всегда возвращаются одни и те же (захардкожены в самом провайдере).


namespace Flancer32\Sample\Ui\Component\DataProvider;

class Grid
    extends \Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider
{
    public function __construct(
        $name,
        \Magento\Framework\Api\Search\ReportingInterface $reporting,
        \Magento\Framework\Api\Search\SearchCriteriaBuilder $searchCriteriaBuilder,
        \Magento\Framework\App\RequestInterface $request,
        \Magento\Framework\Api\FilterBuilder $filterBuilder,
        \Magento\Framework\UrlInterface $url
    ) {
        $primaryFieldName = 'id';
        $requestFieldName = 'id';
        $meta = [];
        $updateUrl = $url->getUrl('mui/index/render');
        $data = [
            'config' => [
                'component' => 'Magento_Ui/js/grid/provider',
                'update_url' => $updateUrl
            ]
        ];
        parent::__construct($name, $primaryFieldName, $requestFieldName, $reporting, $searchCriteriaBuilder, $request,
            $filterBuilder, $meta, $data);
    }

    public function getData()
    {
        $result = [
            'items' => [
                ['code2' => 'AU', 'code3' => 'AUS', 'code_num' => '036'],
                ['code2' => 'AT', 'code3' => 'AUT', 'code_num' => '040'],
                ['code2' => 'AZ', 'code3' => 'AZE', 'code_num' => '031']
            ],
            'totalRecords' => 3
        ];
        return $result;
    }

}

Резюме


Создание гридов в Magento 2 — это увлекательное занятие, которому можно посвятить не только свободные часы, но дни, а может быть даже и недели. Конечно, со временем оно станет менее увлекательным и более обыденным, но пока все еще остается возможность добавить в админку свой собственный грид не в пару кликов, а путем вдумчивого и кропотливого изменения если и не десятка файлов, то около того (если бы я трусливо не захаркодил данные в провайдере — точно достиг бы этого уровня, а может быть даже и превысил). Возможно кто-то окажется более смелым и захочет использовать встроенный \Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider и зарегистрировать для него в ./src/etc/di.xml соответствующую коллекцию:


<type name="Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory">
    <arguments>
        <argument name="collections" xsi:type="array">
            <item name="sample_grid_data_source" xsi:type="string">Vendor\Module\Model\ResourceModel\Grid\Collection</item>
        </argument>
    </arguments>
</type>

Пожелаю ему в этом удачи. Я, к сожалению, на данный момент свой лимит увлекательности исчерпал.


Всем счастливого Magento 2 coding'а!

  • +14
  • 7,6k
  • 7
Поделиться публикацией
Комментарии 7
    +1
    Статья понравилась. Но я заметил что все, включая М2 разработчиков, добавляют в зависимости своего класса редирект или резалт фектори вроде
    protected $_resultPageFactory;
    
    public function __construct(
            \Magento\Backend\App\Action\Context $context,
            \Magento\Framework\View\Result\PageFactory $resultPageFactory
    ) {
            parent::__construct($context);
            $this->_resultPageFactory = $resultPageFactory;
    }
    


    Все контроллеры наследуют свойство $this->resultFactory в котором хранится Magento\Framework\Controller\ResultFactory, т.е. достаточно сделать
    public function execute()
    {
        return $this->resultFactory->create(ResultFactory::TYPE_PAGE);
    }
    

    и тем самым избавиться от необходимости в некоторых случаях пустого конструктора ради 1 лишней зависимости.
      0

      Спасибо, коллега. Внес изменения в код примера.

      +1
      После такого рода публикаций складывается впечатление, что создание интернет-магазинов на магенто по трудоазтратам аналогично их написанию на каком-нибудь фреймворке. Так в чем же его профит?
        +1
        best practice и support
          0
          Да. Смахивает на то :)
          Но все же как не крути получается быстрее — не на много но быстрее. Ко второй магенте стоит относится как к навороченому фреймворку на базе которого собран конечный продукт из кучи модулей.
          Так же можно было б сказать и про первую (или любую другую похожую модульную систему) — но все же в м2 это поярче выражено.

          И да, профит в даном случае огромный есть только тогда когда систему заведомо будут поддерживать очень долгое время и большое количество разных людей.
          В других случаях, затраты на разработку с лихвой сжирают возможный профит.

          М2 для девелопера с чуством перфикционизма и желанием сделать все как по книжке будет очень уютным местечком. Но проблема в том что eCommerce в большенстве случаев не нужно качество, ей нужна быстрота внедрения всего нового что может принести выгоду. К сожалению это не про вторую магенту — как не крути но времени нужно потратить больше на разработку, по сравнению с первой магентой, почти во всем.

          Хотя, она все же чертовски интересная:)
            0

            К сожалению перфекционистам в M2 опасно — она еще очень сырая, неоднородная по реализации система с незавершенными архитектурными решениями (те же репозитории, все еще полностью не заменившие ресурс-модели). Любой владелец магазина на М2 — доброневольный бета-тестер. Работа над выправлением этой ситуации идет, но сможет ли команда разработчиков ее выправить — вопрос открытый. Я бы поставил на них, если бы они отказались от обратной совместимости (которой и так нет) и провели генеральную чистку и реструктуризацию кода и данных, но это стало бы уже М3. Таких маневров community может и не выдержать, а без своих расширений Magento превратится в тот же Shopify, только еще и на своем "железе". Ну а пока разработчики борются со сложностью и, судя по тому, что тесты на Travis'е у них не проходят уже около месяца, борьба идет ожесточенная.


            Хотя, она все же чертовски интересная:)

            А вот тут полностью согласен :) Весьма любопытный пример распределенной разработки приложений.

            0

            Богатая функциональность, получаемая "из-коробки" — это раз. Большое кол-во расширений основного функционала, созданных сторонними разработчиками — это два. Все еще значительное community (best practice & support, как отметил коллега Sergiy) — это три. И плюс куча времени и средств закопанного в нее как разработчиками (основного функционала и расширений), так и владельцами магазинов.


            И да, профит в даном случае огромный есть только тогда когда систему заведомо будут поддерживать очень долгое время и большое количество разных людей.

            © vpodorozh

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое