Z tego artykułu dowiesz się między innymi jak utworzyć Ui component form, oraz jak:

  • dodać kontroler działający w obszarze panelu administracyjnego Magento 2
  • dodać link do menu kierujący do nowej strony w panelu administracyjnym
  • zapisać dane z formularza w bazie danych

Ten artykuł jest częścią praktycznego kursu dla Magento Developerów. Zachęcam do przeczytania poprzednich części, jeśli ich wcześniej nie widziałeś, aby lepiej zrozumieć to, nad czym tutaj pracuję.

Tyle tytułem wstępu, jedziemy z tematem!

Dodanie Controllera w obszarze panelu administracyjnego Magento

Na wstępie zadajmy sobie jedno ważne, ale to bardzo ważne pytanie:

Co to jest kontroler i do czego jest mi on potrzebny?

Gdy chcesz dodać nową stronę to właśnie potrzebujesz kontrolera. Każda strona jest dostępna pod określoną ścieżką. Przykład:

http://magento2.test/customer/account/login

Jak się pewnie domyślasz jest to adres strony logowania. Wyjaśnię Ci teraz co oznaczją poszczególne człony tego adresu.

http://magento2.test – jest to adres sklepu tzw base url

customerfrontName

accountcontrollerName

loginactionName

Kontroler do tej ścieżki znajdziesz w module Magento_Customer: Magento/Customer/Controller/Account/Login.php

Na podstawie lokalizacji tego pliku widać, że katalog Account to controllerName, a plik Login.php odpowiada za actionName

Skąd bierze się frontName?

Aby dodać nowy kontroler w Magento należy:

  1. Utworzyć klasę kontrolera
  2. Dodać konfigurację tego kontrolera w pliku routes.xml

Dla naszego kontrolera będzie to Magento/Customer/etc/frontend/routes.xml

Właśnie w pliku routes.xml znajdziemy frontName.

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

Jak widzisz jest tu zadeklarowane frontName=”customer” i właśnie stąd to się bierze.

Czym jest id ścieżki?

Dla każdej ścieżki definiujemy jej id i jest on później wykorzystywany przy tworzeniu layoutu. Bazując na powyższym przykładzie layout do tej strony znajdziesz tutaj: Magento/Customer/view/frontend/layout/customer_account_login.xml

Przy budowaniu layoutu dla ścieżki wzór wygląda tak:

<Vendor name>/<Module name>/<area>/<routeId>_<controllerName>_<actionName>.xml

Zapamiętaj

Layout budowany jest na podstawie Route id, a Kontroler na podstawie frontName. Wszystkie te wartości definiuje się w pliku routes.xml

Jak już wiesz, w M2 kontrolery umieszcza się w katalogu modułu w folderze Controller (swoją drogą szokujące prawda? ;-)) Kontrolery działające w obszarze panelu administacyjnego umieszcza się w podfolderze Adminhtml katalogu Controller. Analogicznie z plikami routes.xml. Te, w których chcemy zadeklarować kontrolery dla panelu administracyjnego zapisujemy w folderze etc/adminhtml/.

Tworzenie nowej strony z funkcjonalnością dodawania/edycji bannerów

Mając tak poważną wiedzę o stronach i kontrolerach, możemy zadeklarować naszą stronę, z poziomu której bedzie można dodać banner lub go edytować.

Klasa kontrollera

Postanowiłem, że actionName mojego kontrolera to będzie… Form. Początkowo chciałem zrobić dwa kontrollery: Add i Edit. Po przemyśleniu doszedłem do wniosku, że dodawanie i edycja dzięki paru sprytnym trikom może być zrobione na jednym formularzu i nie ma potrzeby definiowania dwóch kontrolerów do tego.

<?php
/**
 * @copyright Copyright (C) 2020 Marcin Kwiatkowski (https://marcin-kwiatkowski.com)
 */
declare(strict_types=1);

namespace Mkwiatkowski\CatalogBanners\Controller\Adminhtml\Banner;

use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\View\Result\Page;
use Magento\Framework\View\Result\PageFactory;

/**
 * Class Form
 * @package Mkwiatkowski\CatalogBanners\Controller\Adminhtml\Banner
 */
class Form extends Action implements HttpGetActionInterface
{
    /**
     * Authorization level of a basic admin session
     *
     * @see _isAllowed()
     */
    const ADMIN_RESOURCE = 'Mkwiatkowski_CatalogBanners::banner';

    /**
     * Menu identifier
     */
    const MENU_ID = 'Mkwiatkowski_CatalogBanners::banners_add';

    /**
     * @var PageFactory
     */
    protected $resultPageFactory;

    /**
     * Add constructor.
     *
     * @param Context $context
     * @param PageFactory $resultPageFactory
     */
    public function __construct(
        Context $context,
        PageFactory $resultPageFactory
    ) {
        parent::__construct($context);

        $this->resultPageFactory = $resultPageFactory;
    }

    /**
     * Load the page.
     *
     * @return Page
     */
    public function execute() : Page
    {
        $pageTitle = ($this->getRequest()->getParam('banner_id')) ? __('Edit banner') : __('Add new banner');
        $resultPage = $this->resultPageFactory->create();
        $resultPage->setActiveMenu(static::MENU_ID);
        $resultPage->getConfig()->getTitle()->prepend($pageTitle);

        return $resultPage;
    }
}

Każdy backendowy kontroler musi dziedziczyć po klasie Magento\Backend\App\Action, oraz implementować jeden z interfejsów HTTP (linia 19)

Admin resource

Stała ADMIN_RESOURCE (linia 26) definiuję nazwę zasobu, która jest używana w ACL do określenia, czy dana ścieżka jest dostępna dla zalogowanego administratora. O ACL (Access Control List) będzie osobny artykuł, ponieważ to dosyć rozległy temat.

Menu Id

Stała MENU_ID zdefiniowana w lini 31 jest używana później do ustawienia aktywnego linku w menu i koresponduje z id elementu w menu, który zadeklarujemy później.

Metoda execute

W tej metodzie oprócz ustawienia aktywnego stanu menu, ustawiam tytuł strony w zależności od tego, czy jesteśmy na stronie edycji, czy dodawania nowego banneru. Metoda zwraca wygenerowaną stronę.

Definicja ścieżki

Następnym krokiem jest zdefiniowanie ścieżki w pliku 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="catalogbanners" frontName="catalogbanners">
            <module name="Mkwiatkowski_CatalogBanners" />
        </route>
    </router>
</config>

Route id to catalogbanners, więc strona będzie dostępna pod adresem <base_url>/<admin_url>/catalogbanners/banner/form

Layout

Jak już dobrze wiesz nazwa layoutu budowana jest z route id, controllerName i actionName, więc w naszym przypadku będzie to plik view/adminhtml/layout/catalogbanners_banner_form.xml

<?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">
    <referenceContainer name="content">
        <uiComponent name="banner_form"/>
    </referenceContainer>
</page>

w piątej linijce umieszczamy na stronie UI Component z formularzem. Stworzeniem tego kompoenentu zajmiemy się trochę później.

Dodanie nawigacji

Mamy już nową stronę, teraz przydałoby się ją gdzieś podlinkować. Myślę, ze najlepszym miejscem będzie lokalizacja w zakładce „Catalog”. Tworzymy plik 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="Mkwiatkowski_CatalogBanners::banners" title="Catalog Banners" translate="title"
             module="Mkwiatkowski_CatalogBanners" parent="Magento_Catalog::catalog" sortOrder="50"
             resource="Mkwiatkowski_CatalogBanners::banners"/>
        <add id="Mkwiatkowski_CatalogBanners::banners_add" title="Add new banner" translate="title"
             module="Mkwiatkowski_CatalogBanners" parent="Mkwiatkowski_CatalogBanners::banners" sortOrder="10"
             action="catalogbanners/banner/form"
             resource="Mkwiatkowski_CatalogBanners::banner"/>
    </menu>
</config>

W piątej lini kodu dodajemy węzeł „Catalog Banners” jako submenu do menu Magento_Catalog::catalog. W dwunastej lini kodu dodajemy ink do submenu, a w czternastej definiujemy do jakies ścieżki ma ten link kierować. Gotowe menu wygląda tak:

Ui component

Ui component

Ui Componenty deklaruje się w plikach xml. Nazwa pliku jest później używana w layout, w miejscu gdzie chcemy umieścić dany komponent. W naszym przypadku komponent nazywa się banner_form. Plik tworze w katalogu view/adminhtml/ui_component.

<?xml version="1.0" encoding="UTF-8"?>
<form 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">banner_form.banner_form_data_source</item>
        </item>
        <item name="label" xsi:type="string" translate="true">Banner Information</item>
        <item name="template" xsi:type="string">templates/form/collapsible</item>
    </argument>
    <settings>
        <buttons>
            <button name="save" class="Mkwiatkowski\CatalogBanners\Block\Adminhtml\Edit\SaveButton"/>
        </buttons>
        <namespace>banner_form</namespace>
        <dataScope>data</dataScope>
        <deps>
            <dep>banner_form.banner_form_data_source</dep>
        </deps>
    </settings>
    <dataSource name="banner_form_data_source">
        <argument name="data" xsi:type="array">
            <item name="js_config" xsi:type="array">
                <item name="component" xsi:type="string">Magento_Ui/js/form/provider</item>
            </item>
        </argument>
        <settings>
            <submitUrl path="catalogbanners/banner/save"/>
        </settings>
        <dataProvider class="Mkwiatkowski\CatalogBanners\Model\Banner\DataProvider" name="banner_form_data_source">
            <settings>
                <requestFieldName>banner_id</requestFieldName>
                <primaryFieldName>banner_id</primaryFieldName>
            </settings>
        </dataProvider>
    </dataSource>
    <fieldset name="banner">
        <settings>
            <label/>
        </settings>
        <field name="banner_id" formElement="input">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">banner</item>
                </item>
            </argument>
            <settings>
                <dataType>text</dataType>
                <visible>false</visible>
                <dataScope>banner_id</dataScope>
            </settings>
        </field>
        <field name="is_active" formElement="checkbox">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">banner</item>
                    <item name="default" xsi:type="number">1</item>
                </item>
            </argument>
            <settings>
                <dataType>boolean</dataType>
                <visible>true</visible>
                <label translate="true">Is active</label>
                <dataScope>is_active</dataScope>
            </settings>
            <formElements>
                <checkbox>
                    <settings>
                        <description translate="true">Is active</description>
                        <valueMap>
                            <map name="false" xsi:type="string">0</map>
                            <map name="true" xsi:type="string">1</map>
                        </valueMap>
                        <prefer>toggle</prefer>
                    </settings>
                </checkbox>
            </formElements>
        </field>
        <field name="category_id" component="Magento_Catalog/js/components/new-category"
               sortOrder="20" formElement="select">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">banner</item>
                    <item name="filterOptions" xsi:type="boolean">true</item>
                    <item name="multiple" xsi:type="boolean">false</item>
                    <item name="showCheckbox" xsi:type="boolean">false</item>
                    <item name="disableLabel" xsi:type="boolean">true</item>
                    <item name="levelsVisibility" xsi:type="number">1</item>
                </item>
            </argument>
            <settings>
                <required>true</required>
                <validation>
                    <rule name="required-entry" xsi:type="boolean">true</rule>
                </validation>
                <elementTmpl>ui/grid/filters/elements/ui-select</elementTmpl>
                <label translate="true">Parent Category</label>
                <dataScope>category_id</dataScope>
                <componentType>field</componentType>
                <listens>
                    <link name="${ $.namespace }.${ $.namespace }:responseData">setParsed</link>
                </listens>
            </settings>
            <formElements>
                <select>
                    <settings>
                        <options class="Magento\Catalog\Ui\Component\Product\Form\Categories\Options"/>
                    </settings>
                </select>
            </formElements>
        </field>
        <field name="content" formElement="wysiwyg">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">banner</item>
                </item>
            </argument>
            <settings>
                <additionalClasses>
                    <class name="admin__field-wide">true</class>
                </additionalClasses>
                <validation>
                    <rule name="required-entry" xsi:type="boolean">true</rule>
                </validation>
                <label/>
                <dataScope>content</dataScope>
            </settings>
            <formElements>
                <wysiwyg>
                    <settings>
                        <wysiwyg>true</wysiwyg>
                    </settings>
                </wysiwyg>
            </formElements>
        </field>
    </fieldset>
</form>

Jak widzisz plik jest dosyć spory i wytłumaczenie wszystkiego byłoby tematem na kilka artykułów lub lekcji video. Dzisiaj zapamiętaj, trzy najważniejsze rzeczy:

  1. W węźle dataSource jest zdefiniowany DataProvider, czyli klasa, która dostarcza dane do komponentu
  2. Pola formularza definiujesz jako pola <field>
  3. w węźle <buttons> dodajemy jeden przycisk, poprzez który formularz będzie wysyłany do backendu

Utworzenie DataProvidera

Mkwiatkowski\CatalogBanners\Model\Banner\DataProvider
<?php
declare(strict_types=1);
namespace Mkwiatkowski\CatalogBanners\Model\Banner;

use Magento\Framework\Api\FilterBuilder;
use Magento\Framework\Api\Search\ReportingInterface;
use Magento\Framework\Api\Search\SearchCriteriaBuilder;
use Magento\Framework\App\Request\DataPersistorInterface;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface;
use Mkwiatkowski\CatalogBanners\Model\Banner;
use Mkwiatkowski\CatalogBanners\Model\BannerFactory;
use Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider as MagentoDataProvider;
use Mkwiatkowski\CatalogBanners\Model\ResourceModel\Banner\Collection;

/**
 * Class DataProvider
 * @package Mkwiatkowski\CatalogBanners\Model\Banner
 */
class DataProvider extends MagentoDataProvider implements DataProviderInterface
{
    /**
     * @var array
     */
    protected $loadedData;
    /**
     * @var BannerFactory
     */
    private $bannerFactory;

    /**
     * @var Collection
     */
    private $collection;

    /**
     * @var DataPersistorInterface
     */
    private $dataPersistor;

    /**
     * DataProvider constructor.
     * @param $name
     * @param $primaryFieldName
     * @param $requestFieldName
     * @param ReportingInterface $reporting
     * @param SearchCriteriaBuilder $searchCriteriaBuilder
     * @param RequestInterface $request
     * @param FilterBuilder $filterBuilder
     * @param BannerFactory $bannerFactory
     * @param Collection $collection
     * @param DataPersistorInterface $dataPersistor
     * @param array $meta
     * @param array $data
     */
    public function __construct(
        $name,
        $primaryFieldName,
        $requestFieldName,
        ReportingInterface $reporting,
        SearchCriteriaBuilder $searchCriteriaBuilder,
        RequestInterface $request,
        FilterBuilder $filterBuilder,
        BannerFactory $bannerFactory,
        Collection $collection,
        DataPersistorInterface $dataPersistor,
        array $meta = [],
        array $data = []
    ) {
        $this->bannerFactory = $bannerFactory;
        $this->collection = $collection;
        $this->dataPersistor = $dataPersistor;

        parent::__construct(
            $name,
            $primaryFieldName,
            $requestFieldName,
            $reporting,
            $searchCriteriaBuilder,
            $request,
            $filterBuilder,
            $meta,
            $data
        );
    }

    /**
     * Get data.
     *
     * @return array
     * @throws NoSuchEntityException
     */
    public function getData() : array
    {
        if (isset($this->loadedData)) {
            return $this->loadedData;
        }

        $items = $this->collection->getItems();
        /** @var Banner $banner */
        foreach ($items as $banner) {
            $this->loadedData[$banner->getId()] = $banner->getData();
        }

        $data = $this->dataPersistor->get('catalog_banner');

        if (!empty($data)) {
            $banner = $this->collection->getNewEmptyItem();
            $banner->setData($data);
            $this->loadedData[$banner->getId()] = $banner->getData();
            $this->dataPersistor->clear('catalog_banner');
        }

        return $this->loadedData;
    }
}

Metoda getData pobiera dane. Zwróć uwagę na linię 106, gdzie używany jest dataPersistor. Co to takiego? Klasa ta pobiera dane z sesji i posiada metody get, set i clear.

Klasy odpowiedzialne za przyciski

Teraz zdefiniujemy klasy odpowiedzialne za wyrenderowanie przycisków. Będzie to jedna klasa genericButton i klasy dziedziczące po niej dla każdego przycisku. Zaczniemy od przycisku Save. W pzyszłości możemy dodac przyciski takie jak Delete, Save and continue lub Duplicate.

Block/Adminhtml/Edit/GenericButton.php
<?php
declare(strict_types=1);

namespace Mkwiatkowski\CatalogBanners\Block\Adminhtml\Edit;

use Magento\Backend\Block\Widget\Context;

/**
 * Class GenericButton
 * @package Mkwiatkowski\CatalogBanners\Block\Adminhtml\Edit
 */
class GenericButton
{
    /**
     * @var Context
     */
    protected $context;

    /**
     * GenericButton constructor.
     * 
     * @param Context $context
     */
    public function __construct(
        Context $context
    ) {
        $this->context = $context;
    }

    /**
     * Generate url by route and parameters
     *
     * @param   string $route
     * @param   array $params
     * @return  string
     */
    public function getUrl(string $route = '', array $params = []) : string
    {
        return $this->context->getUrlBuilder()->getUrl($route, $params);
    }
}
Mkwiatkowski\CatalogBanners\Block\Adminhtml\Edit\SaveButton
<?php
declare(strict_types=1);

namespace Mkwiatkowski\CatalogBanners\Block\Adminhtml\Edit;

use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;

/**
 * Class SaveButton
 * @package Mkwiatkowski\CatalogBanners\Block\Adminhtml\Edit
 */
class SaveButton extends GenericButton implements ButtonProviderInterface
{

    /**
     * @return array
     */
    public function getButtonData() : array
    {
        return [
            'label' => __('Save banner'),
            'class' => 'save primary',
            'on_click' => '',
        ];
    }
}

Nasz Ui component teraz już działa i wygląda tak:

Ui component

Przesłanie formularza do backendu i zapisanie bannera w bazie danych

Aby przesłać dane do backendu należy utworzyć kontroler, który przechwyci te dane i obsłuży zapis. Kontroler ten utworzymy w katalogu Controller/Adminhtml/Banner/Save.php

<?php
declare(strict_types=1);
/**
 * @copyright Copyright (C) 2020 Marcin Kwiatkowski (https://marcin-kwiatkowski.com)
 */

namespace Mkwiatkowski\CatalogBanners\Controller\Adminhtml\Banner;

use Magento\Backend\App\Action;
use Magento\Backend\Model\View\Result\RedirectFactory;
use Magento\Backend\Model\View\Result\Redirect;
use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\App\Request\DataPersistorInterface;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Framework\Exception\NoSuchEntityException;
use Mkwiatkowski\CatalogBanners\Model\Banner;
use Mkwiatkowski\CatalogBanners\Model\BannerFactory;
use Mkwiatkowski\CatalogBanners\Model\BannerRepository;
use Psr\Log\LoggerInterface;

/**
 * Class Save
 * @package Mkwiatkowski\CatalogBanners\Controller\Adminhtml\Banner
 */
class Save extends Action implements HttpPostActionInterface
{
    /**
     * Authorization level of a basic admin session
     *
     * @see _isAllowed()
     */
    const ADMIN_RESOURCE = 'Mkwiatkowski_CatalogBanners::banner';

    /**
     * @var RedirectFactory
     */
    protected $resultRedirectFactory;

    /**
     * @var BannerFactory
     */
    private $bannerFactory;

    /**
     * @var BannerRepository
     */
    private $bannerRepository;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @var DataPersistorInterface
     */
    private $dataPersistor;

    /**
     * Save constructor.
     * @param Action\Context $context
     * @param RedirectFactory $redirectFactory
     * @param BannerFactory $bannerFactory
     * @param BannerRepository $bannerRepository
     * @param LoggerInterface $logger
     * @param DataPersistorInterface $dataPersistor
     */
    public function __construct(
        Action\Context $context,
        RedirectFactory $redirectFactory,
        BannerFactory $bannerFactory,
        BannerRepository $bannerRepository,
        LoggerInterface $logger,
        DataPersistorInterface $dataPersistor
    ) {
        $this->resultRedirectFactory = $redirectFactory;
        $this->bannerFactory = $bannerFactory;
        $this->bannerRepository = $bannerRepository;
        $this->logger = $logger;
        $this->dataPersistor = $dataPersistor;

        parent::__construct($context);
    }

    /**
     * @return Redirect
     */
    public function execute() : Redirect
    {
        $resultRedirect = $this->resultRedirectFactory->create();
        $data = $this->getRequest()->getPostValue();

        if ($data) {
            if (empty($data['banner_id'])) {
                unset($data['banner_id']);
            }

            /** @var Banner $bannerModel */
            $bannerModel = $this->bannerFactory->create();
            $bannerId = (int) $this->getRequest()->getParam('banner_id');

            if ($bannerId) {
                try {
                    $bannerModel = $this->bannerRepository->getById($bannerId);
                } catch (NoSuchEntityException $e) {
                    $this->messageManager->addErrorMessage(__('This banner no longer exists.'));
                    $this->logger->debug($e->getMessage());
                    return $resultRedirect->setPath('*/*/');
                }
            }

            $bannerModel->setData($data);

            try {
                $this->bannerRepository->save($bannerModel);
                $this->messageManager->addSuccessMessage(__('You saved the banner.'));
                $this->dataPersistor->clear('catalog_banner');
                $this->dataPersistor->set('catalog_banner', $data);
                return $resultRedirect->setPath('*/*/form', ['banner_id' => $bannerModel->getId()]);
            } catch (CouldNotSaveException $e) {
                $errorMessage = $e->getMessage();
                $this->messageManager->addErrorMessage($errorMessage);
                $this->logger->error($errorMessage);
            } catch (\Exception $e) {
                $this->messageManager->addExceptionMessage($e, __('Something went wrong while saving the banner.'));
                $this->logger->error($e->getMessage());
            }

            $this->dataPersistor->set('catalog_banner', $data);

            return $resultRedirect->setPath('*/*/form', ['banner_id' => $bannerId]);
        }

        return $resultRedirect->setPath('*/*/');
    }
}

Kontroler jest przygotowany tak, że obsłuży dane w przypadku dodawania nowego banneru jak i edycji istniejącego. Zauważ, że kontroler implementuje interfejs HttpPostActionInterface, ponieważ formularz jest wysyłany POSTem.

Dodatkowe zmiany

Dodanie zależności do modułu.

Nasz moduł wykorzystuje klasy z modułu Mageento_Catalog, oraz Magento_Ui, dlatego musimy dodać te zależności w pliku etc/module.xml

<sequence>
   <module name="Magento_Ui"/>
   <module name="Magento_Catalog"/>
</sequence>

Po dodaniu sekwencji należy wyłączyć moduł, włączyć go ponownie i zrobić setup:upgrade, aby mieć pewność, zę moduły wczytują się w odpowiedniej kolejności.

Zmiany w DB Schema

Przy tworzeniu tabeli w bazie zapomniałem dodać do klucza głównego flagi identity=”true”, która sprawia, ze do kolumny dodawany jest atrybut auto_increment

Poprawa błędu w interfejsie Api/Data/BannerInterface.php

Implementacja modelu dziedziczy po AbstractModel, dlatego definicja metody nie może mieć ustawionego typowania.

Podsumowanie

Możemy już dodawać i edytować bannery. Następnym razem pokażę Ci jak wyświetlić grid bannerów.

Cały kod modułu możesz znależć na GitHubie. Zmiany z dzisiejszego artykułu są w tym commicie.