Magento 2 modulfejlesztés lépésről lépésre – 2. rész

Cikksorozatunk előző részében bemutattuk, miért érdemes webáruház fejlesztéséhez a Magento 2-t választani, és hogyan kezdhetünk bele a saját, felhasználói modulok fejlesztésébe. Összefoglaltuk a munkához szükséges követelményeket, bemutattuk, hogyan telepíthetjük a webáruházunk üzemeltetéséhez szükséges rendszert, és felvázoltuk a munka kezdetéhez nélkülözhetetlen beállításokat. Bemutattuk egy alap modul felépítését, valamint belekezdtünk annak elkészítésébe.

Ebben a cikkben folytatjuk a munkát, és komolyabb vizekre evezünk. Sok más, hasznos funkció melett megismerkedünk az adminisztrációs felületen a modulunkhoz tartozó menüpontok létrehozásának folyamatával, az editáláshoz szükséges admin block-ok létrehozásával, illetve a controller-ek és layout létrehozásával is.

A következő témákkal fogunk megismerkedni:

  • 1) Admin menüpont és táblázat (grid) elkészítése
  • 2) UI Components avagy az admin táblázat (grid) új kialakítása
  • 3) UI component osztályok
  • 4) Adminhtml controller-ek
  • 5) Object manager konfigurációs
  • 6) Editáláshoz szükséges admin block-ok létrehozása
  • 7) Controller-ek és layout létrehozása

1) Admin menüpont és táblázat (grid) elkészítése

Első lépésben létre kell hoznunk az adminisztrációs felületen a modulunkhoz tartozó menüpontot. Ezt külön (új) főmenüben is elhelyezhetjük, de célszerű a modul működéséhez igazodva besorolni a már létező főmenüpontok alá. Jelen esetben a Content főmenüpontban helyezzük el a sajátunkat.

Ehhez szükségünk lesz egy új file-ra. Ezt a menüpontot az app/code/Aion/Test/etc/adminhtml/ könyvtárban lévő menu.xml-ben valósíthatjuk meg. A fájl tartalma:

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">
    <menu>
        <add id="Aion_Test::content_elements" title="Test Extension" module="Magento_Backend" sortOrder="10" parent="Magento_Backend::content"
             resource="Magento_Backend::content_elements"/>
        <add id="Aion_Test::aion_test" title="Manage Items" module="Aion_Test" sortOrder="10" parent="Aion_Test::content_elements" action="test/test"
             resource="Aion_Test::test"/>
    </menu>
</config>

A file-ban első lépésben definiálunk egy főelemet, ami nem más, mint az id=Aion_Test::content_elements és a menüpontot ez alá helyezzük el, úgy hogy a menüpont (id=Aion_Test::aion_test) parent-nél a főelemet adjuk meg. Ha mindent jól csináltunk, az adminisztrációs felületen a Content főmenüpontban megjelenik a saját almenüpontunk is.

A menüpontnál még fontos megemlíteni az Action=”test/test” paramétert, ami a később kialakítandó adminhtml controller útvonalát hivatott megadni.   A következő lépésben szükséges elkészíteni az adminhtml controllert és layout file-t, ami a grid megjelenítésért felelős.

Azonban előtte létre kell hoznunk egy abstract controller osztályt, hogy a backend jogosultság kezelést egy helyen valósítsuk meg.   Az abstract controller osztályt az app/code/Aion/Test/Controller/Adminhtml/ könyvtárban lévő Test.php-ban valósítjuk meg. A fájl tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Controller\Adminhtml;

/**
 * Aion manage items controller
 */
abstract class Test extends \Magento\Backend\App\Action
{
    /**
     * Core registry
     *
     * @var \Magento\Framework\Registry
     */
    protected $_coreRegistry = null;

    /**
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Magento\Framework\Registry $coreRegistry
     */
    public function __construct(\Magento\Backend\App\Action\Context $context, \Magento\Framework\Registry $coreRegistry)
    {
        $this->_coreRegistry = $coreRegistry;
        parent::__construct($context);
    }

    /**
     * Init page
     *
     * @param \Magento\Backend\Model\View\Result\Page $resultPage
     * @return \Magento\Backend\Model\View\Result\Page
     */
    protected function initPage($resultPage)
    {
        $resultPage->setActiveMenu('Aion_Test::aion_test')
            ->addBreadcrumb(__('Test'), __('Test'))
            ->addBreadcrumb(__('Items'), __(''));
        return $resultPage;
    }

    /**
     * Check the permission to run it
     *
     * @return boolean
     */
    protected function _isAllowed()
    {
        return $this->_authorization->isAllowed('Aion_Test::test_menu');
    }
}

Az abstract controller initPage() függvénye (metódusa) felelős az aktív menüpont beállítása mellett a breadcrumb (morzsa menü) útvonal beállításáért is. A másik fontos függvény az _isAllowed(), ami az adminisztrátor jogosultságot ellenőrzi és kezeli.

Ezután elkészítjük az admin táblázat (grid) kezeléséhez szükséges controllert is, amit az imént említett abstract controller-ből fogunk kiterjeszteni. A controller osztályt az app/code/Aion/Test/Controller/Adminhtml/Test könyvtárban lévő Index.php-ban valósítjuk meg. A fájl tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Controller\Adminhtml\Test;

class Index extends \Aion\Test\Controller\Adminhtml\Test
{
    /**
     * @var \Magento\Framework\View\Result\PageFactory
     */
    protected $resultPageFactory;

    /**
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Magento\Framework\Registry $coreRegistry
     * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
     */
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\Registry $coreRegistry,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory
    ) {
        $this->resultPageFactory = $resultPageFactory;
        parent::__construct($context, $coreRegistry);
    }

    /**
     * Index action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        /** @var \Magento\Backend\Model\View\Result\Page $resultPage */
        $resultPage = $this->resultPageFactory->create();
        $this->initPage($resultPage)->getConfig()->getTitle()->prepend(__('Items'));
        return $resultPage;
    }
}

A controller-ben lévő $resultPage és $this->resultPageFactory helyettesíti Magento 1.x-ből megismert $this->loadLayout() és $this->renderLayout() hívásokat.   Ahhoz, hogy a Magento 2.0 felismerje a létrehozott adminhtml controller-ek útvonalát, ezt definiálnunk kell egy külön fájl-ban. Az útvonal definiálását az app/code/Aion/Test/etc/adminhtml/ könyvtárban lévő routes.xml-ban valósítjuk meg. A fájl tartalma:

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="admin">
        <route id="test" frontName="test">
            <module name="Aion_Test" before="Magento_Backend" />
        </route>
    </router>
</config>

A fájlban két lényeges tag és hozzá tartozó paraméter látható. Az első a <router id=”admin”> ami jelzi, hogy ez egy backend útvonal. A második a <route id=”test” frontName=”test”> ahol a frontName határozza meg az elkészült adminhtml controllerek fő útvonalát (az admin url utáni első paramétert).

Következő lépésben létre kell hoznunk a collection-t, melynek feladata, hogy a fenti admin táblázatot (grid) kiszolgálja a szükséges adatokkal. A collection-t az app/code/Aion/Model/ResourceModel/Test/Grid/ könyvtárban lévő Collection.php-ban valósítjuk meg. A fájl tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Model\ResourceModel\Test\Grid;

use Magento\Framework\Api\Search\SearchResultInterface;
use Magento\Framework\Search\AggregationInterface;
use Aion\Test\Model\ResourceModel\Test\Collection as TestCollection;

/**
 * Collection for displaying grid of Aion Items
 */
class Collection extends TestCollection implements SearchResultInterface
{
    /**
     * @var AggregationInterface
     */
    protected $aggregations;

    /**
     * @param \Magento\Framework\Data\Collection\EntityFactoryInterface $entityFactory
     * @param \Psr\Log\LoggerInterface $logger
     * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy
     * @param \Magento\Framework\Event\ManagerInterface $eventManager
     * @param \Magento\Store\Model\StoreManagerInterface $storeManager
     * @param string $mainTable
     * @param string $eventPrefix
     * @param string $eventObject
     * @param string $resourceModel
     * @param string $model
     * @param string|null $connection
     * @param \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource
     *
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
     */
    public function __construct(
        \Magento\Framework\Data\Collection\EntityFactoryInterface $entityFactory,
        \Psr\Log\LoggerInterface $logger,
        \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy,
        \Magento\Framework\Event\ManagerInterface $eventManager,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        $mainTable,
        $eventPrefix,
        $eventObject,
        $resourceModel,
        $model = 'Magento\Framework\View\Element\UiComponent\DataProvider\Document',
        $connection = null,
        \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource = null
    ) {
        parent::__construct(
            $entityFactory,
            $logger,
            $fetchStrategy,
            $eventManager,
            $storeManager,
            $connection,
            $resource
        );
        $this->_eventPrefix = $eventPrefix;
        $this->_eventObject = $eventObject;
        $this->_init($model, $resourceModel);
        $this->setMainTable($mainTable);
    }

    /**
     * @return AggregationInterface
     */
    public function getAggregations()
    {
        return $this->aggregations;
    }

    /**
     * @param AggregationInterface $aggregations
     * @return $this
     */
    public function setAggregations($aggregations)
    {
        $this->aggregations = $aggregations;
    }


    /**
     * Retrieve all ids for collection
     * Backward compatibility with EAV collection
     *
     * @param int $limit
     * @param int $offset
     * @return array
     */
    public function getAllIds($limit = null, $offset = null)
    {
        return $this->getConnection()->fetchCol($this->_getAllIdsSelect($limit, $offset), $this->_bindParams);
    }

    /**
     * Get search criteria.
     *
     * @return \Magento\Framework\Api\SearchCriteriaInterface|null
     */
    public function getSearchCriteria()
    {
        return null;
    }

    /**
     * Set search criteria.
     *
     * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria
     * @return $this
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function setSearchCriteria(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria = null)
    {
        return $this;
    }

    /**
     * Get total count.
     *
     * @return int
     */
    public function getTotalCount()
    {
        return $this->getSize();
    }

    /**
     * Set total count.
     *
     * @param int $totalCount
     * @return $this
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function setTotalCount($totalCount)
    {
        return $this;
    }

    /**
     * Set items list.
     *
     * @param \Magento\Framework\Api\ExtensibleDataInterface[] $items
     * @return $this
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function setItems(array $items = null)
    {
        return $this;
    }
}

A fájlban definiált osztály felelős a kialakítandó admin táblázatban (grid) az adatok hozzáadásáért, keresés és lapozás implementálásáért. A következőkben leírt UI Components megfelelő működéséhez szükséges a fenti függvények implementálása.

Másik előnye, hogy az itt definiált osztályt könnyen máshol is fel tudjuk használni, amennyiben máshol is megszeretnénk jeleníteni a modul adatait admin táblázatban (grid), például egy product vagy customer ajax tab-on az adminisztrációs felületen.

Már csak egy dolog van hátra, ami nem más, mint az Index controllerhez tartozó layout file elkészítése. A layout file-t az app/code/Aion/Test/view/adminhtml/layout/ könyvtárban lévő test_test_index.xml-ban valósítjuk meg. Jól látható a korábban definiált route a fájl nevében: alap route -> könyvtár -> controller action. A fájl tartalma:

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<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="test_test_listing"/>
        </referenceContainer>
    </body>
</page>

A layout fájlban lévő „content” referencia konténerben van definiálva a korábban említett UI component file neve, amire a következő pontban térünk ki részletesen.

2) UI Components avagy az admin táblázat (grid) új kialakítása

A Magento 2.0-ban bevezetett UI component-ek használatával sokkal könnyebben tudunk admin táblázatot (grid) létrehozni és emellett az adminisztrátor számára sokkal több lehetőséget nyújt a táblázatban történő kereséshez, szűréshez, az oszlopok tetszőleges megjelenítéséhez. Emellett külön megjelenítések menthetők le.

A UI component-ek működéséhez több fájlt is létre kell hoznunk és megfelelően implementálnunk. A legfontosabb fájl, ami az admin táblázat működését és megjelenését meghatározza egy xml fájl. Ez a mi modulunk esetében a test_test_listing.xml nevet kapta (lásd előző pont) és az app/code/Aion/Test/view/adminhtml/ui_component könyvtárban van elhelyezve. A fájl nagyon hosszú, így tartalmát darabolva jelenítjük meg cikkünkben.

<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<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">test_test_listing.test_test_listing_data_source</item>
            <item name="deps" xsi:type="string">test_test_listing.test_test_listing_data_source</item>
        </item>
        <item name="spinner" xsi:type="string">test_test_columns</item>
        <item name="buttons" xsi:type="array">
            <item name="add" xsi:type="array">
                <item name="name" xsi:type="string">add</item>
                <item name="label" xsi:type="string" translate="true">Add New Item</item>
                <item name="class" xsi:type="string">primary</item>
                <item name="url" xsi:type="string">*/*/new</item>
            </item>
        </item>
    </argument>
    <dataSource name="test_test_listing_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">TestGridDataProvider</argument>
            <argument name="name" xsi:type="string">test_test_listing_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">test_id</argument>
            <argument name="requestFieldName" xsi:type="string">id</argument>
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="update_url" xsi:type="url" path="mui/index/render"/>
                </item>
            </argument>
        </argument>
        <argument name="data" xsi:type="array">
            <item name="js_config" xsi:type="array">
                <item name="component" xsi:type="string">Magento_Ui/js/grid/provider</item>
            </item>
        </argument>
    </dataSource>

 

A fájlban lévő első argument tag-ben vannak definiálva az alábbiak:

  • adat forrás név (test_test_listing_data_source) lásd második dataSource tag: <dataSource name=”test_test_listing_data_source”>
  • oszlopok tag neve: test_test_columns, erre továbbiakban lesz szükségünk
  • az új elem hozzáadása és egyéb gombok definiálása, lásd: <item name=”buttons” xsi:type=”array”> tag
<container name="listing_top">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="template" xsi:type="string">ui/grid/toolbar</item>
        </item>
    </argument>
    <bookmark name="bookmarks">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="storageConfig" xsi:type="array">
                    <item name="namespace" xsi:type="string">test_test_listing</item>
                </item>
            </item>
        </argument>
    </bookmark>
    <component name="columns_controls">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="columnsData" xsi:type="array">
                    <item name="provider" xsi:type="string">test_test_listing.test_test_listing.test_test_columns</item>
                </item>
                <item name="component" xsi:type="string">Magento_Ui/js/grid/controls/columns</item>
                <item name="displayArea" xsi:type="string">dataGridActions</item>
            </item>
        </argument>
    </component>

Az xml fájlt tovább böngészve definiálásra kerülnek a táblázat felett elhelyezkedő funkciók. Ezek az alábbiak:

  • lementhetjük az aktuális táblázat megjelenést több view-ban, lásd: <bookmark name=”bookmarks”> tag
  • beállíthatjuk túl sok oszlop esetén, hogy melyek jelenjenek meg és az előbb említett bookmarks-nál menthetjük le, lásd: <component name=”columns_controls”> tag
<filterSearch name="fulltext">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="provider" xsi:type="string">test_test_listing.test_test_listing_data_source</item>
            <item name="chipsProvider" xsi:type="string">test_test_listing.test_test_listing.listing_top.listing_filters_chips</item>
            <item name="storageConfig" xsi:type="array">
                <item name="provider" xsi:type="string">test_test_listing.test_test_listing.listing_top.bookmarks</item>
                <item name="namespace" xsi:type="string">current.search</item>
            </item>
        </item>
    </argument>
</filterSearch>
<filters name="listing_filters">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="columnsProvider" xsi:type="string">test_test_listing.test_test_listing.test_test_columns</item>
            <item name="storageConfig" xsi:type="array">
                <item name="provider" xsi:type="string">test_test_listing.test_test_listing.listing_top.bookmarks</item>
                <item name="namespace" xsi:type="string">current.filters</item>
            </item>
            <item name="templates" xsi:type="array">
                <item name="filters" xsi:type="array">
                    <item name="select" xsi:type="array">
                        <item name="component" xsi:type="string">Magento_Ui/js/form/element/ui-select</item>
                        <item name="template" xsi:type="string">ui/grid/filters/elements/ui-select</item>
                    </item>
                </item>
            </item>
            <item name="childDefaults" xsi:type="array">
                <item name="provider" xsi:type="string">test_test_listing.test_test_listing.listing_top.listing_filters</item>
                <item name="imports" xsi:type="array">
                    <item name="visible" xsi:type="string">test_test_listing.test_test_listing.test_test_columns.${ $.index }:visible</item>
                </item>
            </item>
        </item>
    </argument>
</filters>

Hozzáadásra kerül a text alapú kereső és a táblázat szűrése (filters). Ezek az alábbiak:

  • varchar, text típusú oszlopokban kereshetünk egy input mezőben, lásd: <filterSearch name=”fulltext”> tag
  • minden egyes oszlopot szűrhetünk megfelelő paraméterek szerint view(Aion\Test\Ui\Component\Listing\Column\Test\Options), select, dátum, ID(range), text típusú szűrések, lásd: <filters name=”listing_filters”> tag
<massaction name="listing_massaction">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="selectProvider" xsi:type="string">test_test_listing.test_test_listing.test_test_columns.ids</item>
            <item name="indexField" xsi:type="string">test_id</item>
        </item>
    </argument>
    <action name="delete">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="type" xsi:type="string">delete</item>
                <item name="label" xsi:type="string" translate="true">Delete</item>
                <item name="url" xsi:type="url" path="test/test/massDelete"/>
                <item name="confirm" xsi:type="array">
                    <item name="title" xsi:type="string" translate="true">Delete items</item>
                    <item name="message" xsi:type="string" translate="true">Are you sure you wan't to delete selected items?</item>
                </item>
            </item>
        </argument>
    </action>
    <action name="disable">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="type" xsi:type="string">disable</item>
                <item name="label" xsi:type="string" translate="true">Disable</item>
                <item name="url" xsi:type="url" path="test/test/massDisable"/>
            </item>
        </argument>
    </action>
    <action name="enable">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="type" xsi:type="string">enable</item>
                <item name="label" xsi:type="string" translate="true">Enable</item>
                <item name="url" xsi:type="url" path="test/test/massEnable"/>
            </item>
        </argument>
    </action>
</massaction>

A massaction tag-en belül kerülnek hozzáadásra tetszőleges általunk definiált műveletek, melyek a tömeges adatmódosításra szolgálnak. A modulunkban ezek az alábbiak:

  • tömeges törlés, lásd: <action name=”delete”> tag
  • tömeges engedélyezés és tiltás, lásd: <action name=”disable”> és <action name=”enable”> tag-ek. Ezek a modulunk adatbázis táblájában korában kialakított is_active adatot módosítják.
    <paging name="listing_paging">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="storageConfig" xsi:type="array">
                    <item name="provider" xsi:type="string">test_test_listing.test_test_listing.listing_top.bookmarks</item>
                    <item name="namespace" xsi:type="string">current.paging</item>
                </item>
                <item name="selectProvider" xsi:type="string">test_test_listing.test_test_listing.test_test_columns.ids</item>
            </item>
        </argument>
    </paging>
</container>

A <paging name=”listing_paging”> tag implementálja lapozást és a listázott elemek számának választhatóságát (select) a táblázatunkban.

    <paging name="listing_paging">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="storageConfig" xsi:type="array">
                    <item name="provider" xsi:type="string">test_test_listing.test_test_listing.listing_top.bookmarks</item>
                    <item name="namespace" xsi:type="string">current.paging</item>
                </item>
                <item name="selectProvider" xsi:type="string">test_test_listing.test_test_listing.test_test_columns.ids</item>
            </item>
        </argument>
    </paging>
</container>

Fentiek után következik a táblázat oszlopainak meghatározása, lásd: <columns name=”test_test_columns”> tag. Ennek nevét már a fájl elején definiáltuk. A korábban említett mass action-ökhez beállított ID mező, lásd: <selectionsColumn name=”ids”> tag.

<column name="test_id">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">textRange</item>
                    <item name="sorting" xsi:type="string">asc</item>
                    <item name="label" xsi:type="string" translate="true">ID</item>
                </item>
            </argument>
        </column>
        <column name="name">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="editor" xsi:type="array">
                        <item name="editorType" xsi:type="string">text</item>
                        <item name="validation" xsi:type="array">
                            <item name="required-entry" xsi:type="boolean">true</item>
                        </item>
                    </item>
                    <item name="filter" xsi:type="string">text</item>
                    <item name="label" xsi:type="string" translate="true">Name</item>
                </item>
            </argument>
        </column>
        <column name="email">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="editor" xsi:type="array">
                        <item name="editorType" xsi:type="string">text</item>
                        <item name="validation" xsi:type="array">
                            <item name="required-entry" xsi:type="boolean">true</item>
                        </item>
                    </item>
                    <item name="filter" xsi:type="string">text</item>
                    <item name="label" xsi:type="string" translate="true">Email</item>
                </item>
            </argument>
        </column>
        <column name="creation_time" class="Magento\Ui\Component\Listing\Columns\Date">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">dateRange</item>
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/date</item>
                    <item name="dataType" xsi:type="string">date</item>
                    <item name="label" xsi:type="string" translate="true">Created</item>
                </item>
            </argument>
        </column>
        <column name="update_time" class="Magento\Ui\Component\Listing\Columns\Date">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">dateRange</item>
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/date</item>
                    <item name="dataType" xsi:type="string">date</item>
                    <item name="label" xsi:type="string" translate="true">Modified</item>
                </item>
            </argument>
        </column>
        <column name="sort_order">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="editor" xsi:type="array">
                        <item name="editorType" xsi:type="string">text</item>
                        <item name="validation" xsi:type="array">
                            <item name="required-entry" xsi:type="boolean">true</item>
                        </item>
                    </item>
                    <item name="filter" xsi:type="string">text</item>
                    <item name="label" xsi:type="string" translate="true">Sort Order</item>
                </item>
            </argument>
        </column>
        <column name="is_active">
            <argument name="data" xsi:type="array">
                <item name="options" xsi:type="array">
                    <item name="disable" xsi:type="array">
                        <item name="value" xsi:type="string">0</item>
                        <item name="label" xsi:type="string" translate="true">Disabled</item>
                    </item>
                    <item name="enable" xsi:type="array">
                        <item name="value" xsi:type="string">1</item>
                        <item name="label" xsi:type="string" translate="true">Enabled</item>
                    </item>
                </item>
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">select</item>
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/select</item>
                    <item name="editor" xsi:type="string">select</item>
                    <item name="dataType" xsi:type="string">select</item>
                    <item name="label" xsi:type="string" translate="true">Status</item>
                </item>
            </argument>
        </column>
        <actionsColumn name="actions" class="Aion\Test\Ui\Component\Listing\Column\TestActions">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="indexField" xsi:type="string">test_id</item>
                </item>
            </argument>
        </actionsColumn>
    </columns>
</listing>

Ezt követően a táblázatban lévő oszlopokat kell meghatároznunk. Az egyes oszlopoknál beállíthatjuk a típust, pl.: text, select, textRange, dateRange stb. Az utolsó oszlop az alap action-öket tartalmazza, lásd: <actionsColumn name=”actions” class=”Aion\Test\Ui\Component\Listing\Column\TestActions”> tag

Ezzel el is készültünk a grid definíciós xml-lel (test_test_listing.xml). A továbbiakban megnézünk néhány osztályt, ami az utolsó oszlopban lévő action-ökért felel.

3) UI component osztályok

Az előző pontban kialakított grid definíciós xml-ben található action oszlop működéséhez szükségünk van egy osztályra, mely a megjelenítést és a működést segítik.   Az első az előző pontban látható <actionsColumn name=”actions” class=”Aion\Test\Ui\Component\Listing\Column\TestActions”> tag-ben látható a TestActions osztály.

A fájl az app/code/Aion/Test/Ui/Component/Listing/Column könyvtárban van elhelyezve TestActions.php néven. A fájl tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Ui\Component\Listing\Column;

use Magento\Framework\UrlInterface;
use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Framework\View\Element\UiComponentFactory;
use Magento\Ui\Component\Listing\Columns\Column;

/**
 * Class TestActions
 */
class TestActions extends Column
{
    /**
     * Url path
     */
    const URL_PATH_EDIT = 'test/test/edit';
    const URL_PATH_DELETE = 'test/test/delete';

    /**
     * @var UrlInterface
     */
    protected $urlBuilder;

    /**
     * Constructor
     *
     * @param ContextInterface $context
     * @param UiComponentFactory $uiComponentFactory
     * @param UrlInterface $urlBuilder
     * @param array $components
     * @param array $data
     */
    public function __construct(
        ContextInterface $context,
        UiComponentFactory $uiComponentFactory,
        UrlInterface $urlBuilder,
        array $components = [],
        array $data = []
    ) {
        $this->urlBuilder = $urlBuilder;
        parent::__construct($context, $uiComponentFactory, $components, $data);
    }

    /**
     * Prepare Data Source
     *
     * @param array $dataSource
     * @return array
     */
    public function prepareDataSource(array $dataSource)
    {
        if (isset($dataSource['data']['items'])) {
            foreach ($dataSource['data']['items'] as & $item) {
                if (isset($item['test_id'])) {
                    $item[$this->getData('name')] = [
                        'edit' => [
                            'href' => $this->urlBuilder->getUrl(
                                static::URL_PATH_EDIT,
                                [
                                    'test_id' => $item['test_id']
                                ]
                            ),
                            'label' => __('Edit')
                        ],
                        'delete' => [
                            'href' => $this->urlBuilder->getUrl(
                                static::URL_PATH_DELETE,
                                [
                                    'test_id' => $item['test_id']
                                ]
                            ),
                            'label' => __('Delete'),
                            'confirm' => [
                                'title' => __('Delete "${ $.$data.name }"'),
                                'message' => __('Are you sure you wan\'t to delete a "${ $.$data.name }" record?')
                            ]
                        ]
                    ];
                }
            }
        }

        return $dataSource;
    }
}

Az osztály előállítja a mass action megjelenítéséhez szükséges tömböt a megfelelő formátumban. A file elején lévő konstantsoknál fontos a pontos útvonal meghatározása, hogy a megfelelő adminhtml controller-re mutassanak.

4) Adminhtml controller-ek

A grid teljes működéséhez néhány controller-t még el kell készíteni. Nézzük sorban őket.

A tömeges törléshez a massDelete controller-t használjuk. A fájl az app/code/Aion/Test/Controller/Adminhtml/Test/ könyvtárban van elhelyezve MassDelete.php néven. A fájl tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Controller\Adminhtml\Test;

use Magento\Framework\Controller\ResultFactory;
use Magento\Backend\App\Action\Context;
use Magento\Ui\Component\MassAction\Filter;
use Aion\Test\Model\ResourceModel\Test\CollectionFactory;

/**
 * Class MassDelete
 */
class MassDelete extends \Magento\Backend\App\Action
{
    /**
     * @var Filter
     */
    protected $filter;

    /**
     * @var CollectionFactory
     */
    protected $collectionFactory;

    /**
     * @param Context $context
     * @param Filter $filter
     * @param CollectionFactory $collectionFactory
     */
    public function __construct(Context $context, Filter $filter, CollectionFactory $collectionFactory)
    {
        $this->filter = $filter;
        $this->collectionFactory = $collectionFactory;
        parent::__construct($context);
    }

    /**
     * Execute action
     *
     * @return \Magento\Backend\Model\View\Result\Redirect
     * @throws \Magento\Framework\Exception\LocalizedException|\Exception
     */
    public function execute()
    {
        $collection = $this->filter->getCollection($this->collectionFactory->create());
        $collectionSize = $collection->getSize();

        foreach ($collection as $item) {
            $item->delete();
        }

        $this->messageManager->addSuccess(__('A total of %1 record(s) have been deleted.', $collectionSize));

        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
        return $resultRedirect->setPath('*/*/');
    }
}

A controller osztály execute() függvénye – vagyis az action – egy collection-t kap (\Magento\Ui\Component\MassAction\Filter osztálytól), amin végig iterálva törli az elemeket. A tömeges státusz módosításhoz a massEnable és massDisable controller-eket használjuk.

A fájlok az app/code/Aion/Test/Controller/Adminhtml/Test/ könyvtárban vannak elhelyezve MassEnable.php és MassDisable.php néven. A fájlok tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Controller\Adminhtml\Test;

use Magento\Framework\Controller\ResultFactory;
use Magento\Backend\App\Action\Context;
use Magento\Ui\Component\MassAction\Filter;
use Aion\Test\Model\ResourceModel\Test\CollectionFactory;

/**
 * Class MassEnable
 */
class MassEnable extends \Magento\Backend\App\Action
{
    /**
     * @var Filter
     */
    protected $filter;

    /**
     * @var CollectionFactory
     */
    protected $collectionFactory;

    /**
     * @param Context $context
     * @param Filter $filter
     * @param CollectionFactory $collectionFactory
     */
    public function __construct(Context $context, Filter $filter, CollectionFactory $collectionFactory)
    {
        $this->filter = $filter;
        $this->collectionFactory = $collectionFactory;
        parent::__construct($context);
    }

    /**
     * Execute action
     *
     * @return \Magento\Backend\Model\View\Result\Redirect
     * @throws \Magento\Framework\Exception\LocalizedException|\Exception
     */
    public function execute()
    {
        $collection = $this->filter->getCollection($this->collectionFactory->create());

        foreach ($collection as $item) {
            $item->setIsActive(true);
            $item->save();
        }

        $this->messageManager->addSuccess(__('A total of %1 record(s) have been enabled.', $collection->getSize()));

        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
        return $resultRedirect->setPath('*/*/');
    }
}
<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Controller\Adminhtml\Test;

use Magento\Framework\Controller\ResultFactory;
use Magento\Backend\App\Action\Context;
use Magento\Ui\Component\MassAction\Filter;
use Aion\Test\Model\ResourceModel\Test\CollectionFactory;

/**
 * Class MassDisable
 */
class MassDisable extends \Magento\Backend\App\Action
{
    /**
     * @var Filter
     */
    protected $filter;

    /**
     * @var CollectionFactory
     */
    protected $collectionFactory;

    /**
     * @param Context $context
     * @param Filter $filter
     * @param CollectionFactory $collectionFactory
     */
    public function __construct(Context $context, Filter $filter, CollectionFactory $collectionFactory)
    {
        $this->filter = $filter;
        $this->collectionFactory = $collectionFactory;
        parent::__construct($context);
    }

    /**
     * Execute action
     *
     * @return \Magento\Backend\Model\View\Result\Redirect
     * @throws \Magento\Framework\Exception\LocalizedException|\Exception
     */
    public function execute()
    {
        $collection = $this->filter->getCollection($this->collectionFactory->create());

        foreach ($collection as $item) {
            $item->setIsActive(false);
            $item->save();
        }

        $this->messageManager->addSuccess(__('A total of %1 record(s) have been disabled.', $collection->getSize()));

        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
        return $resultRedirect->setPath('*/*/');
    }
}

A két controller működése nagyon hasonló egymáshoz. Mindkettő a Filter osztálytól kapott collection-ön iterál végig, és állítja be az is_active data kulcsot massEnbale esetén true-ra, míg massDisable esetén false-ra, majd menti a collection elemeit.

5) Object manager konfigurációs

Ahhoz, hogy az elkészített admin táblázat(grid) megfelelően működjön, meg kell adnunk a forrás adat objektumokat és filter-eket. Ehhez szükségünk lesz egy definíciós xml-re. A fájl az app/code/Aion/Test/etc/ könyvtárban van elhelyezve di.xml néven. A fájl tartalma:

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory">
        <arguments>
            <argument name="collections" xsi:type="array">
                <item name="test_test_listing_data_source" xsi:type="string">Aion\Test\Model\ResourceModel\Test\Grid\Collection</item>
            </argument>
        </arguments>
    </type>
    <type name="Aion\Test\Model\ResourceModel\Test\Grid\Collection">
        <arguments>
            <argument name="mainTable" xsi:type="string">aion_test</argument>
            <argument name="eventPrefix" xsi:type="string">aion_test_grid_collection</argument>
            <argument name="eventObject" xsi:type="string">test_grid_collection</argument>
            <argument name="resourceModel" xsi:type="string">Aion\Test\Model\ResourceModel\Test</argument>
        </arguments>
    </type>
    <virtualType name="TestGirdFilterPool" type="Magento\Framework\View\Element\UiComponent\DataProvider\FilterPool">
        <arguments>
            <argument name="appliers" xsi:type="array">
                <item name="regular" xsi:type="object">Magento\Framework\View\Element\UiComponent\DataProvider\RegularFilter</item>
                <item name="fulltext" xsi:type="object">Magento\Framework\View\Element\UiComponent\DataProvider\FulltextFilter</item>
            </argument>
        </arguments>
    </virtualType>
    <virtualType name="TestGridDataProvider" type="Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider">
        <arguments>
            <argument name="collection" xsi:type="object" shared="false">Aion\Test\Model\ResourceModel\Test\Collection</argument>
            <argument name="filterPool" xsi:type="object" shared="false">TestGirdFilterPool</argument>
        </arguments>
    </virtualType>
</config>

Ebben a fájlban definiáljuk a grid-hez szükséges collection-t (lásd: <item name=”test_test_listing_data_source” xsi:type=”string”>Aion\Test\Model\ResourceModel\Test\Grid\Collection</item>), filter-t és data provider-t, mely UI component megfelelő működéséhez szükséges.

Az egyes elemek editálását, mentését és egyenkénti törlését a következőkben írjuk le.

6) Editáláshoz szükséges admin block-ok létrehozása

Ahhoz, hogy a modulhoz tartozó adatokat létre tudjuk hozni az admin felületen és szerkeszteni tudjuk, szükségünk lesz a megfelelő osztályokra. Első lépésben a container osztályt kell létrehozni, mely később a form-ot fogja tartalmazni.

Az osztályt az Aion/Test/Block/Adminhtml/ könyvtárban lévő Test.php fájlban valósítjuk meg:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Block\Adminhtml;

/**
 * Adminhtml Aion items content block
 */
class Test extends \Magento\Backend\Block\Widget\Grid\Container
{
    /**
     * @return void
     */
    protected function _construct()
    {
        $this->_blockGroup = 'Aion_Test';
        $this->_controller = 'adminhtml_test';
        $this->_headerText = __('Items');
        $this->_addButtonLabel = __('Add New Item');
        parent::_construct();
    }
}

Az osztályban lényeges a megfelelő blockGroup és controller meghatározása.

A következő lépésben szükségünk lesz a form container osztályra. Itt határozzuk meg szerkesztetés alatt álló objektum admin oldalának title-jét, és adhatunk hozzá tetszőleges button-okat az alap gombokon kívül, vagy távolíthatunk el.

Az osztályt az Aion/Test/Block/Adminhtml/Test könyvtárban lévő Edit.php fájlban valósítjuk meg:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Block\Adminhtml;

/**
 * Adminhtml Aion items content block
 */
class Test extends \Magento\Backend\Block\Widget\Grid\Container
{
    /**
     * @return void
     */
    protected function _construct()
    {
        $this->_blockGroup = 'Aion_Test';
        $this->_controller = 'adminhtml_test';
        $this->_headerText = __('Items');
        $this->_addButtonLabel = __('Add New Item');
        parent::_construct();
    }
}

Amennyiben WYSWYG editort is szeretnénk használni például textarea típusú mezőhöz, akkor azt a _construct() függvényben kell elhelyezni, vagy a prepareLayout() függvényben. Az osztályban lévő getHeaderText() függvény határozza meg az admin oldal title értékét.

Az utolsó block, amit el kell készítenünk, a form megjelenítését és kezelését végzi. Az osztályt az Aion/Test/Block/Adminhtml/Test/Edit könyvtárban lévő Form.php fájlban valósítjuk meg:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Block\Adminhtml\Test\Edit;

/**
 * Adminhtml Aion item edit form
 */
class Form extends \Magento\Backend\Block\Widget\Form\Generic
{
    /**
     * @var \Magento\Cms\Model\Wysiwyg\Config
     */
    protected $_wysiwygConfig;

    /**
     * @var \Magento\Store\Model\System\Store
     */
    protected $_systemStore;

    /**
     * @param \Magento\Backend\Block\Template\Context $context
     * @param \Magento\Framework\Registry $registry
     * @param \Magento\Framework\Data\FormFactory $formFactory
     * @param \Magento\Cms\Model\Wysiwyg\Config $wysiwygConfig
     * @param \Magento\Store\Model\System\Store $systemStore
     * @param array $data
     */
    public function __construct(
        \Magento\Backend\Block\Template\Context $context,
        \Magento\Framework\Registry $registry,
        \Magento\Framework\Data\FormFactory $formFactory,
        \Magento\Cms\Model\Wysiwyg\Config $wysiwygConfig,
        \Magento\Store\Model\System\Store $systemStore,
        array $data = []
    ) {
        $this->_wysiwygConfig = $wysiwygConfig;
        $this->_systemStore = $systemStore;
        parent::__construct($context, $registry, $formFactory, $data);
    }

    /**
     * Init form
     *
     * @return void
     */
    protected function _construct()
    {
        parent::_construct();
        $this->setId('test_form');
        $this->setTitle(__('Item Information'));
    }

    /**
     * Prepare form
     *
     * @return $this
     */
    protected function _prepareForm()
    {
        $model = $this->_coreRegistry->registry('test_item');

        /** @var \Magento\Framework\Data\Form $form */
        $form = $this->_formFactory->create(
            ['data' => ['id' => 'edit_form', 'action' => $this->getData('action'), 'method' => 'post']]
        );

        $form->setHtmlIdPrefix('item_');

        $fieldset = $form->addFieldset(
            'base_fieldset',
            ['legend' => __('General Information'), 'class' => 'fieldset-wide']
        );

        if ($model->getId()) {
            $fieldset->addField('test_id', 'hidden', ['name' => 'test_id']);
        }

        $fieldset->addField(
            'name',
            'text',
            [
                'name' => 'name',
                'label' => __('Name'),
                'title' => __('Name'),
                'required' => true
            ]
        );

        $fieldset->addField(
            'email',
            'text',
            [
                'name' => 'email',
                'label' => __('Email'),
                'title' => __('Email'),
                'required' => true,
                'class' => 'validate-email'
            ]
        );

        $fieldset->addField(
            'is_active',
            'select',
            [
                'label' => __('Status'),
                'title' => __('Status'),
                'name' => 'is_active',
                'required' => true,
                'options' => ['1' => __('Enabled'), '0' => __('Disabled')]
            ]
        );
        if (!$model->getId()) {
            $model->setData('is_active', '1');
        }

        $fieldset->addField(
            'sort_order',
            'text',
            [
                'name' => 'sort_order',
                'label' => __('Sort Order'),
                'title' => __('Sort Order'),
                'required' => false
            ]
        );

        $form->setValues($model->getData());
        $form->setUseContainer(true);
        $this->setForm($form);

        return parent::_prepareForm();
    }
}

Az osztály _prepareForm() függvényében adjuk hozzá a szerkesztésre szánt mezőket, ami a mi esetünkben a name, email és sort_order mezők. Ezek mellett szerepel még a multistore kezelés szempontjából fontos store_id field is, illetve is_active field is, ami jelen esetben select típusú és a szerkesztés alatt álló elem státuszát hivatott beállítani.

A fent említett három osztállyal el is készítettük az adminisztrációs felületen történő szerkesztéshez szükséges fájlokat.

7) Controller-ek és layout létrehozása

Az editálás megvalósításához a fenti osztályokon kívül szükségünk lesz még a megfelelő controller osztályokra és layout fájlokra.   Az első osztályt az Aion/Test/Controller/Adminhtml/Test/ könyvtárban lévő NewAction.php fájlban valósítjuk meg:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Controller\Adminhtml\Test;

class NewAction extends \Aion\Test\Controller\Adminhtml\Test
{
    /**
     * @var \Magento\Backend\Model\View\Result\ForwardFactory
     */
    protected $resultForwardFactory;

    /**
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Magento\Framework\Registry $coreRegistry
     * @param \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory
     */
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\Registry $coreRegistry,
        \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory
    ) {
        $this->resultForwardFactory = $resultForwardFactory;
        parent::__construct($context, $coreRegistry);
    }

    /**
     * Create new item
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        /** @var \Magento\Framework\Controller\Result\Forward $resultForward */
        $resultForward = $this->resultForwardFactory->create();
        return $resultForward->forward('edit');
    }
}

Az osztály az új elemek létrehozására szolgál és lényegében az action függvénye (execute()) átirányít az Edit controller osztályra.   A következő lépésben létrehozzuk a szerkesztéshez szükséges controller-t.

Az osztályt az Aion/Test/Controller/Adminhtml/Test/ könyvtárban lévő Edit.php fájlban valósítjuk meg:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Controller\Adminhtml\Test;

class Edit extends \Aion\Test\Controller\Adminhtml\Test
{
    /**
     * @var \Magento\Framework\View\Result\PageFactory
     */
    protected $resultPageFactory;

    /**
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Magento\Framework\Registry $coreRegistry
     * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
     */
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\Registry $coreRegistry,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory
    ) {
        $this->resultPageFactory = $resultPageFactory;
        parent::__construct($context, $coreRegistry);
    }

    /**
     * Edit item
     *
     * @return \Magento\Framework\Controller\ResultInterface
     * @SuppressWarnings(PHPMD.NPathComplexity)
     */
    public function execute()
    {
        // 1. Get ID and create model
        $id = $this->getRequest()->getParam('test_id');
        $model = $this->_objectManager->create('Aion\Test\Model\Test');

        // 2. Initial checking
        if ($id) {
            $model->load($id);
            if (!$model->getId()) {
                $this->messageManager->addError(__('This item no longer exists.'));
                /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
                $resultRedirect = $this->resultRedirectFactory->create();
                return $resultRedirect->setPath('*/*/');
            }
        }
        // 3. Set entered data if was error when we do save
        $data = $this->_objectManager->get('Magento\Backend\Model\Session')->getFormData(true);
        if (!empty($data)) {
            $model->setData($data);
        }

        // 4. Register model to use later in blocks
        $this->_coreRegistry->register('test_item', $model);

        /** @var \Magento\Backend\Model\View\Result\Page $resultPage */
        $resultPage = $this->resultPageFactory->create();

        // 5. Build edit form
        $this->initPage($resultPage)->addBreadcrumb(
            $id ? __('Edit Item') : __('New Item'),
            $id ? __('Edit Item') : __('New Item')
        );
        $resultPage->getConfig()->getTitle()->prepend(__('Items'));
        $resultPage->getConfig()->getTitle()->prepend($model->getId() ? $model->getName() : __('New Item'));
        return $resultPage;
    }
}

Az edit action(execute() függvény) első lépésben lekéri a test_id paramétert. Ezt követően inicializálja az Aion/Test/Model/Test modell osztályt. Amennyiben a test_id paraméternek van értéke, a modellt megpróbálja betöltelni az említett id-val.

Sikertelen esetben hibaüzenet állít be, majd visszairányít. Ellenkező esetben a betöltött modellt a registry-ben tárolja ($this->_coreRegistry->register(’test_item’, $model)). Ezt olvassa ki a registry-ből a fent már említett form container osztály is, és használja fel.

Végezetül létrehozza az oldalt ($resultPage), majd beállítja az oldal title paraméterét és breadcrumb-ot is.   A controller-hez tartozó layout fájlt az Aion/Test/view/adminhtml/layout/ könyvtárban lévő test_test_edit.xml fájlban valósítjuk meg:

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <update handle="editor"/>
    <body>
        <referenceContainer name="content">
            <block class="Aion\Test\Block\Adminhtml\Test\Edit" name="test_test_edit"/>
        </referenceContainer>
    </body>
</page>

A következő lépés a mentés elkészítése. Az osztályt az Aion/Test/Controller/Adminhtml/Test/ könyvtárban lévő Save.php fájlban valósítjuk meg:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Controller\Adminhtml\Test;

class Save extends \Aion\Test\Controller\Adminhtml\Test
{
    /**
     * Save action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultRedirectFactory->create();
        // check if data sent
        $data = $this->getRequest()->getPostValue();
        if ($data) {
            $id = $this->getRequest()->getParam('test_id');
            $model = $this->_objectManager->create('Aion\Test\Model\Test')->load($id);
            if (!$model->getId() && $id) {
                $this->messageManager->addError(__('This item no longer exists.'));
                return $resultRedirect->setPath('*/*/');
            }

            // init model and set data

            $model->setData($data);

            // try to save it
            try {
                // save the data
                $model->save();
                // display success message
                $this->messageManager->addSuccess(__('You saved the item.'));
                // clear previously saved data from session
                $this->_objectManager->get('Magento\Backend\Model\Session')->setFormData(false);

                // check if 'Save and Continue'
                if ($this->getRequest()->getParam('back')) {
                    return $resultRedirect->setPath('*/*/edit', ['test_id' => $model->getId()]);
                }
                // go to grid
                return $resultRedirect->setPath('*/*/');
            } catch (\Exception $e) {
                // display error message
                $this->messageManager->addError($e->getMessage());
                // save data in session
                $this->_objectManager->get('Magento\Backend\Model\Session')->setFormData($data);
                // redirect to edit form
                return $resultRedirect->setPath('*/*/edit', ['test_id' => $this->getRequest()->getParam('test_id')]);
            }
        }
        return $resultRedirect->setPath('*/*/');
    }
}

A controller osztály első lépésben várja a korábban kialakított form által posztolt adatokat ($data = $this->getRequest()->getPostValue();). Amennyiben ez nem egy üres tömb, inicializálja az Aion/Test/Model/Test modell osztályt és ha létezik a paraméterként kapott test_id is (vagyis nem új objektum mentésére kerül sor), akkor betölti a megfelelő id-val. Ezt követően a post-ban kapott adatokat beállítja majd menti a modellt.

Amennyiben mindezzel elkészültünk, akkor a korábban kialakított admin grid-ből (táblázat) új objektumokat tudunk hozzáadni és menteni, majd ezeket szerkeszteni. Még egy fontos controller van hátra, ami a törlést hivatott megvalósítani.

Az osztályt az Aion/Test/Controller/Adminhtml/Test/ könyvtárban lévő Delete.php fájlban valósítjuk meg:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Controller\Adminhtml\Test;

class Delete extends \Aion\Test\Controller\Adminhtml\Test
{
    /**
     * Delete action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultRedirectFactory->create();
        // check if we know what should be deleted
        $id = $this->getRequest()->getParam('test_id');
        if ($id) {
            try {
                // init model and delete
                $model = $this->_objectManager->create('Aion\Test\Model\Test');
                $model->load($id);
                $model->delete();
                // display success message
                $this->messageManager->addSuccess(__('You deleted the item.'));
                // go to grid
                return $resultRedirect->setPath('*/*/');
            } catch (\Exception $e) {
                // display error message
                $this->messageManager->addError($e->getMessage());
                // go back to edit form
                return $resultRedirect->setPath('*/*/edit', ['test_id' => $id]);
            }
        }
        // display error message
        $this->messageManager->addError(__('We can\'t find the item to delete.'));
        // go to grid
        return $resultRedirect->setPath('*/*/');
    }
}

A delete action (execute() függvény) első lépésben lekéri a test_id paramétert. Ezt követően inicializálja az Aion/Test/Model/Test model osztályt. Amennyiben a test_id paraméternek van értéke, a modellt megpróbálja betölteni az említett id-val, majd elvégzi a törlést.

Folytatása következik

Ebben a cikkben ismét átvettünk néhány témakört, melyek segítenek a saját Magento 2.0 modulod fejlesztésében. Bízunk benne, hogy sikerült átadnunk mindazt a hasznos tudást, mely hozzájárul ahhoz, hogy funkcionális modulokat fejlesztess, elvégezd azok beállításait, valamint olyan fájlokat és elemeket szerkeszthess, mint az adatbázis tábla, modell, collection, block, admin táblázat, layout stb.

A 2. RÉSZNEK VÉGE, DE HAMAROSAN FOLYTATJUK!   A cikk első részét ide kattintva olvashatod. A következő részben ismét érdekes és hasznos témakörökkel, többek közt az observer-ek létrehozásával és implementálásával ismerkedhetszt meg.

 

Segítünk webáruházra váltani!

Nincsenek vásárlók a boltban? Kénytelen voltál bezárni? Elképesztő válság alakult ki egyes szektorokban a koronavírus járvány hatására, -pedig még nem is látjuk a végét, mi lesz májusban, júniusban. Nemzetközi hálózatkutató szakértők szerint 2020 őszére lehet vége a koronavírus járványnak. A működő webáruházak értékesebbé váltak, a jelentőségük a kereskedelemben tovább fog nőni.

Akinek most stabil webáruháza és vevőköre van, átvészelheti a válságot

Az elmúlt hónapban dupla annyian kerestek rá élelmiszerek házhozszállításával kapcsolatos kifejezésekre hazánkban. Megtriplázódó kosárértékekről osztanak meg híreket webkereskedők egymás közt. A jelenlegi egészségügyi helyzetben a webáruházas értékesítés tűnik az egyedül biztonságosnak. A vásárlók számára a biztonság elsődleges szempont lett: így weben rendelnek. Kevésbé árérzékenyen. Megfigyelhető még egy fontos trend: az eddig a magyar piacon szinte lebeszélhetetlenek voltak a vásárlók az utánvétes, készpénzes fizetésről. Most készpénzmentesen, előre utalással kifizetik a megrendelt árut, csökkentve ezzel a fertőzés veszélyt.

 

Hogy a különbséget érzékeltessük a boltok és a webáruházak forgalma között, a Dockyard-nál tavaly márciushoz képest 80%-kal növekedett a webshop forgalma a hónap első felében, míg az üzletekbe betérő vásárlóké 30-40%-kal visszaesett.  Persze sok webshop tulajdonos számára nehézséget is jelent az értékesítés felfuttatása. Akinek akadozó, rosszul kereshető, kevéssé használható a webshopja, sok bevételtől esik el.

 

A webáruházak egyre népszerűbbé válnak

Bár a plázák, butikok, szállodák és éttermek komolyan megsínylik a koronavírus járvány miatt kialakult válságot, a webáruházak előtt hatalmas felfutás áll. Több helyről lehet hallani, hogy olyan termékköröket forgalmazó webáruházakban is megugrottak a megrendelések, jelentek meg az érdeklődők, ahol eddig kifejezetten mérsékelt volt az érdeklődés (pl. barkács szerszámok).

Az idei év a webáruházak és az online piacterek erősödéséről fog szólni. Mindent visznek a vásárlók, amihez webáruházon keresztül hozzá lehet jutni. A saját árukészlettel rendelkező, gyors és kényelmes fizetést lehetővé tevő, vásárlási élményt adó áruház tulajdonosok most nyertesei ennek a pörgésnek.

Magento webáruházakat fejlesztünk. Egy Magento webáruház képes arra, hogy azt a vásárlási biztonságot megteremtse, amire a vásárlók törekedni fognak. Szabad kapacitásaink felszabadulásának ütemében vállalunk most is projekteket! A tervezéstől egészen a működtetés beindításáig segítséget adunk ügyfeleinknek. Évtizedes tapasztalattal segítünk kereskedőknek átállni a webáruházas értékesítésre. Ez idő alatt többszáz kereskedőnek hoztunk létre webáruházat.

Vérbeli kereskedő vagy? Az előtted álló heteket, hónapokat töltsd felkészüléssel!

A vevőid talán éppen most regisztrálnak egy webshopba, mert azt a terméket keresik, amit eddig Tőled vettek.

Ha két hét van a válság végéig? Felmérjük az igényeidet és komplett árajánlatot kaphatsz, mennyibe kerülne most egy új webáruház lefejlesztése, vagy átállítása Magento alapokra.

Ha két hónap? Ez idő alatt el is készül az új Magento webáruházad, amelyben kezdheted az internetes értékesítést.

És ha mindaz, amit eddig gondoltunk a vásárlásról, gyökeresen meg fog változni? Még inkább szükséged lesz arra, hogy megtaláld azt a formát, ahogy értékesíteni tudsz!

Magento 2 modul fejlesztés lépésről lépésre – 1. rész

A Magento jelenleg a világ egyik vezető webáruház motorja, melyet világszerte rengeteg kis- és középvállalkozás mellett megannyi nagy cég is elégedetten használ. Ennek oka a rendkívül gazdag funkcionalitás, mely a gyári funkciók mellett az egyedi, fejlesztői megoldásoknak is köszönhető.

A Magento ugyanis nyílt forráskóddal rendelkező szoftver, ami azt jelenti, hogy szabadon használható, másolható, terjeszthető, tanulmányozható és módosítható. Community edition verziója ráadásul ingyenesen elérhető. Így nem csak bárki szabadon használhatja, de aktív fejlesztői közösség is épül köré. Ilyen módon a rendszerrel kapcsolatos kérdésekre, problémákra bármikor könnyen választ kaphatunk.

Sőt, a jó tanácsok mellett Magento alapú rendszerünk rengeteg különleges modullal, hasznos funkcióval is bővíthető, melyek könnyebbé teszik a munkát és javítják a felhasználói élményt. Ezek a kiegészítők igen széles skálán mozognak és elérhetők a Magento Marketplace piactérről

De annak érdekében, hogy oldalunk tökéletesen működjünk, mi magunk is fejleszthetünk felhasználói modulokat. Ehhez természetesen azonban programozói előképzettség mellett szükségünk lesz bizonyos ismeretekre, melyeket egy többrészes cikksorozatban fogunk részletesen bemutatni.

Ebben a cikkben a következő témákat fogjuk feldolgozni:

  1. Követelmények, dokumentumok és Magento 2.0 installálás
  2. Célszerű IDE beállítások a fejlesztés megkezdése előtt
  3. Alap modul felépítése és elkészítése
  4. Modul admin konfiguráció és jogosultság kezelés
  5. Alap frontend controller, block, layout és template
  6. Modulhoz tartozó adatbázis tábla elkészítése
  7. Modulhoz tartozó adatbázis tábla elkészítése
  8. Modulhoz tartozó modellek és collection létrehozása
  9. Tábla feltöltése adatokkal, script segítségével
  10. Tábla update script és verzió növelés
  11. Adatok megjelenítése frontend-en

1) Követelmények, dokumentumok és Magento 2.0 installálás

A Magento telepítése már önmagában véve olyan feladat, mely igényel némi hozzáértést. Figyelnünk kell arra, hogy minden rendben menjen, különben a munka nagy eséllyel leet hiányos vagy sikertelen. Arra az esetre, ha valami nem a terv szerint történne, érdemes minden fontos adatról biztonsági mentést készítenünk, mielőtt belefognánk a munkába.

Ahhoz, hogy a Magento 2.0 rendszert telepíteni tudjuk, először is kell végeznünk a fejlesztői környezetünk minimális beállításait, mellyel kapcsolatban részletes információt ezzel kapcsolatban ezen a linken olvashatunk: http://devdocs.magento.com/guides/v2.0/install-gde/bk-install-guide.html

Itt az alapvető követelmények jól láthatók, a telepítéshez a következőkre mindenképp szükségünk lesz:

  • PHP 7.1 és ennél nagyobb verziók
  • MySQL 5.6.x és ennél nagyobb verziók
  • Ajánlott legalább 2 GB fizikai memória

Amennyiben nem rendelkezünk ezekkel, a telepítés megkezdése előtt frissíteni kell lokális fejlesztői környezetünket.

A Magento 2.0 rendszer telepítésével kapcsolatban a legfőbb fájlok az alábbi két linken érhetők el: http://devdocs.magento.com/
https://github.com/magento/magento2

Az installálás során három módszert is tudunk alkalmazni.

  1. Az első esetben egyszerűen letöltjük a Magento hivatalos oldalról a két elérhető verziót. Az egyik csak az alap rendszert tartalmazza, míg a másik példa adatokkal is ellátja az installálás során a rendszert (Sample Data). A két csomag itt érhető el: https://www.magentocommerce.com/download
  2. Második telepítési lehetőség, amikor a Github-ról klónozzuk a Magento rendszert. Ennek előnye, hogy folyamatosan frissíteni tudjuk a Git segítségével a fejlesztőkörnyezetünkben. Fontos itt megjegyezni, hogy a Git klónozás során a develop branch-et kapjuk meg, így ahogy a klónozás befejeződött, célszerű átváltani (git checkout) a 2.0 vagy a merchant_beta branch-ekre. A Magento 2.0 Github elérés: https://github.com/magento/magento2
  3. A harmadik lehetőség a Metapackage installáció. Ebben az esetben a letöltött package után a composer segítségével kell installálnunk a Magento 2.0 rendszert, vagyis az alap Magento modulokat a hivatalos Magento Repo-ból kapjuk, az egyéb összetevőket más repo-ból. Metapackage installálási útmutató: magento integrator install

Ha sikerült telepítenünk a keretrendszert, akad néhány beállítás, melyet nem csak érdemes, de szükségszerű is lehet elvégezni. Ezután láthatunk majd neki az első alapmodulunk felépítésének.

2) Célszerű IDE beállítások a fejlesztés megkezdése előtt

Az IDE nem más, mint az integrált fejlesztői környezet (Integrated Development Environment), melynek alapvető szerepe van a gyors alkalmazásfejlesztésben. Az IDE-k általában egy, a a program forráskódjának szerkesztésére alkalmas szövegszerkesztőt, egy fordítóprogramot vagy értelmezőt, fordításautomatizálási eszközöket, valamint nyomkövetési, grafikusfelület-szerkesztési és verziókezelési lehetőségeket is tartalmaznak.

Miután sikeresen installáltuk a Magento 2.0 rendszert fejlesztőkörnyezetünkbe és az megfelelőn működik, célszerű beállítani a fejlesztéshez használt IDE-t, ami e cikk esetében a Jetbrains PhpStorm-ra vonatkozik.

A Magento 2.0 működése és fejlesztése során az alábbi technológiaákat alkalmazza: devdocs magento tech stack.

Első lépésben a projekt megnyitása után a PhpStorm-ban célszerű lefuttatni a Detect PSR-0 Namspace Roots parancsot, ami a menüből elérhető, de egyébként az IDE ezt automatikusan fel fogja ajánlani.

Következő lépés a megfelelő PHP CodeSniffer beállítása. Alapból az IDE a PSR-1 és PSR-2 coding standard-ot támogatja, így itt célszerű a PSR-2-őt választani, de ajánlottabb a PSR-4 alkalmazása (azonban ez alapból nincs a PhpStorm-ban). Az IDE-ben ezen beállítások elvégzésére a cikk nem tér ki. Amennyiben megnyitunk a vendor/magento könyvtárban lévő bármely modulhoz tartozó etc/module.xml fájlt (vagy egyéb itt található xml fájlt), láthatjuk, hogy az URN feloldás jelenleg sikertelen. A Magento 2.0 számos fejlesztői parancsot tartogat számunkra, ami a fejlesztés során segítségünkre lehet. Ezen parancsok listáját így tekintethetjük meg:

  • belépünk a bin könyvtárba a terminálunkban
  • kiadjuk a „php magento” parancsot
  • ennek eredményeképpen kilistázódik az összes lehetséges parancs, amit a fejlesztés során használhatunk és igen hasznos

Visszatérve az URN feloldási problémára, ezt az alábbi paranccsal tudjunk megoldani: php bin/magento dev:urn-catalog:generate .idea/misc.xml (látható, hogy ezt nem a bin könyvtárból futtatjuk, hanem root könyvtárból) A fejlesztés megkezdése előtt kikapcsoljuk a teljes cache-t (admin felületről, vagy fent említett php magento parancsok segítségével). Amennyiben Apache szerver alatt fejlesztünk, a root könyvtárban található .htaccess fájlban beállítjuk a developer módot: SetEnv MAGE_MODE developer Célszerű a fejlesztői környezetünkben az xdebug-ot kikapcsolni (php.ini beállítás), mert ez jelentősen lassítja a rendszert.

3) Alap modul felépítése és elkészítése

Amint installáltuk a Magento 2.0-át (és nem Github-ról klónoztuk), szembesülünk azzal, hogy a jól megszokott app/code könyvtár hiányzik és minden alap Magento modul a vendor/magento könyvtárban van elhelyezve. Ettől nem kell megijedni, a fejlesztés első lépése, hogy létrehozzuk az app/code könyvtárat. Miután ez elkészült, létrehozzuk a modulunk alap könyvtárát (Vendor/Module). Ez a mi esetünkben az Aion/Test könyvtár lesz. Következő lépésben két könyvtárat hozunk létre:

  • app/code/Aion/Test/etc
  • app/code/Aion/Test/Helper

A modul alap definíciós fájlja az app/code/Aion/Test/etc könyvtárba kerül elhelyezésre, melynek neve module.xml.

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2015 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Aion_Test" setup_version="2.0.0">
        <sequence>
            <module name="Magento_Store"/>
        </sequence>
    </module>
</config>

A fájlban definiáljuk a modul nevét (Vendor_Module -> Aion_Test), a verzió számát, ami jelen esetben 2.0.0 és a függőségeket, ami jelenleg csak az alap Magento_Store modul. A következő lépésben létrehozzuk a Helper fájlt. Az alap helper fájl az app/code/Aion/Test/Helper/Data.php lesz. Nézzük a tartalmát:

<?php
/**
 * Copyright © 2015 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Helper;

/**
 * Aion Test helper
 */
class Data extends \Magento\Framework\App\Helper\AbstractHelper
{
    /**
     * Path to store config if extension is enabled
     *
     * @var string
     */
    const XML_PATH_ENABLED = 'aion/basic/enabled';

    /**
     * Check if extension enabled
     *
     * @return string|null
     */
    public function isEnabled()
    {
        return $this->scopeConfig->isSetFlag(
            self::XML_PATH_ENABLED,
            \Magento\Store\Model\ScopeInterface::SCOPE_STORE
        );
    }
}

A helper fájlt elég üresen is létrehozni, de mi már egy alap függvényt implementáltunk (public function is Enabled()) és egy konstanst. A konstansban meghatározott string (útvonal) később a modulhoz tartozó admin konfigurációs fájlban fog értelmet kapni (system.xml). Következő lépésben elhelyezzük a modul könyvtárba (app/code/Aion/Test) a registration.php fájlt, ami a modul felismerését hivatott elvégezni a Magento 2.0 rendszerben. Nézzük ennek tartalmát:

<?php
/**
 * Copyright © 2015 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */

\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Aion_Test',
    __DIR__
); 

A file tartalmát bármelyik alap Magento 2.0 modulból átmásolhatjuk, a lényeg, hogy a második paraméternél a saját modulunk szerepeljen (Vendor_Modul -> Aion_Test). Mivel az alap Magento 2.0 modulok is composer-t használnak a frissítésre, mi is elkészítjük saját composer.json fájlunkat. Ennek tartalma így néz ki:

{
    "name": "aion/module-test",
    "description": "N/A",
    "require": {
        "php": "~5.5.0|~5.6.0|~7.0.0",
        "magento/module-config": "100.0.*",
        "magento/module-store": "100.0.*",
        "magento/module-backend": "100.0.*",
        "magento/framework": "100.0.*"
    },
    "type": "magento2-module",
    "version": "100.0.2",
    "license": [
        "OSL-3.0",
        "AFL-3.0"
    ],
    "autoload": {
        "files": [
            "registration.php"
        ],
        "psr-4": {
            "Aion\Test\": ""
        }
    }
}

A composer.json tartalmazza modulunk alap információt, függőségeket, licenc, verzió stb. adatokat. Már szinte el is készültünk, de még két fájl hiányzik egy szépen felépített modulhoz.

Ez a két fájl, mely szintén a modulunk könyvtárában (app/code/Aion/Test) kap helyet: README.md és COPYING.txt. Az előbbi a Git leírás, míg az utóbbi a licenc információkat tartalmazza, ami jelen esetben OSL 3.0. Tehát így néz ki a modulunk felépítése: app/code Aion/ Test/ etc/module.xml Helper/Data.php composer.json COPYING.txt README.md registration.php

A következő lépésben aktiválni kell a modult, és megvizsgálni, hogy a Magento 2.0 rendszer felismerte-e megfelelően. A Magento 1.x rendszerben ez a frontend vagy admin oldal frissítéssel történt meg, azonban a 2.0-ban a korábban már említett parancsokat kell alkalmaznunk.

A terminálban belépünk a /bin könyvtárba, majd kiadjuk a php magento module:status parancsot. Ha mindent jól végeztünk el, akkor listázásra kerül az engedélyezett (Enabled) modulok listája és még nem aktív vagy nem engedélyezett (Disabled) modulok listája. Utóbbi helyen jelenik meg a saját modulunk is: Aion_Test. Amennyiben nem jelenik meg, a modulunk felépítése során valamit elrontottunk.

Ezután kiadjuk a terminálban /bin könyvtárban a php magento module:enable Aion_Test parancsot. Ekkor megjelenik, hogy a modult a rendszer felismerte és engedélyezte. Ez valójában azt jelenti, hogy az app/etc/config.php fájlban lévő tömbbe bekerült az ’Aion_Test’ => 1 bejegyzés. Ezután értesítést kapunk, hogy a modult adatbázis szinten is „regisztráljuk” be. Kiadjuk a terminálban a php magento setup:upgrade parancsot.

A parancs végrehajtása során a Magento 2.0 végigfut az összes alap modulon és egyedi modulon és update-eli a rendszert, vagyis lefuttatja a séma és adat scripteket, amennyiben nagyobb verzió számot talál, mint a jelenlegi. Mindez a mi modulunk esetében az jelenti, hogy korábban leírt 2.0.0 verzió szám beírásra kerül a setup_module táblába.

Ha ezután belépünk az admin felületre és kiválasztjuk Store / Configuration menüpontot, majd ezt követően az Advanced / Advanced aloldalt, akkor megjelenik a modulunk a listában enabled állapotban. Ezzel el is készültünk az alap modulunkkal.

4) Modul admin konfiguráció és jogosultság kezelés

Miután az alap modulunk elkészült, felépítjük a hozzá tartozó admin konfigurációt és jogosultság kezelést. Első lépésben a konfigurációhoz szükséges fájlt hozzuk létre, ami az app/code/Aion/Test/etc/adminhtml könyvtárban lesz elhelyezve és a neve system.xml.

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2015 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <tab id="aion_tab" translate="label" sortOrder="500">
            <label>Aion Extensions</label>
        </tab>
        <section id="aion" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="1">
            <label>Test Extension</label>
            <tab>aion_tab</tab>
            <resource>Aion_Test::test</resource>
            <group id="basic" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>Basic</label>
                <field id="enabled" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Enable Extension</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
            </group>
            <group id="more" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>More</label>
                <field id="variable" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Some Variable</label>
                </field>
            </group>
        </section>
    </system>
</config>

A fájlban definiálunk egy <tab> tag-et is, így a modulunk saját menüpontban (Aion Extensions -> Test Extension) fog megjelenni az admin Stores / Configuration aloldalán. Ezen belül implementálunk két tab-ot (basic és more id-kkal). Az elsőbe helyezzük el az alap No / Yes select típusú konfigurációs változót, amit a modul engedélyezésére és tiltására használunk. Lekérdezését már korábban implementáltuk a Data.php file-ban(Helper/Data.php). A példa kedvéért létrehozunk egy text típusú konfigurációs változót is Variable névvel későbbi használatra.

A fájlban még fontos rész a <resource> tag is, aminek a jogosultság kezelés implementálásakor lesz szerepe. Ez a mi esetünkben Aion_Test::test.   A következő lépésben létrehozzuk a jogosultság kezeléshez szükséges fájlt is, ami az app/code/Aion/Test/etc könyvtárba kerül acl.xml néven. Ennek tartalma:

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2015 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<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="Magento_Backend::stores">
                    <resource id="Magento_Backend::stores_settings">
                        <resource id="Magento_Config::config">
                            <resource id="Aion_Test::test" title="Aion Test Extension Section" />
                        </resource>
                    </resource>
                </resource>
            </resource>
        </resources>
    </acl>
</config>

Amint ezzel készen vagyunk, leellenőrizzük, hogy megfelelően létrejött-e a modulhoz tartozó jogosultság beállítás a Role Resource (Admin felhasználó szerepek) részben. Az admin felületen megnyitjuk a System / User Roles menüpontot, majd itt bármelyik adminisztrátori csoportot (alapból egy van) megnyitva a Role Resouces almenüt kiválasztva, majd a Resource Access-nél a Custom lehetőség alatt megvizsgáljuk, hogy a fában megjelenik-e a saját modulunk.

Ezt követően leellenőrizzük a korábban kialakított konfigurációs menüpont, és hogy tartalma is megjelenik-e. Az adminban a Stores / Configuration-t választva a bal menüben megnézzük, hogy megjelent-e a saját menüpontunk (Aion Extensions) és annak taralma (Test Extension). Az alap konfigurációs értékeket az app/code/Aion/Test/etc könyvtárban lévő config.xml fájlban határozzuk meg:

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2015 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
    <default>
        <aion>
            <basic>
                <enabled>1</enabled> 
            </basic>
        </aion>
    </default>
</config>

Jól látható, hogy a file-ban látható tag-ek megfelelnek a system.xml-ben lévő saját id-kkal.

5) Alap frontend controller, block, layout és template

A következőkben létrehozunk egy saját frontend controller-t, a hozzátartozó layout és router konfigurációt. Majd ezt követően egy template fájlt és a hozzá tartozó block-ot is implementáljuk. Elsőként elkészítjük a controller fájlt, ami valójában egy Action-t jelent. Ezt az app/code/Aion/Test/Controller/Index/ könyvtárban lévő Index.php-val implementáljuk. A fájl tartalma:

<?php
/**
 * Copyright © 2015 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Controller\Index;

use Magento\Framework\App\Action\Context;
use Magento\Framework\View\Result\PageFactory;
use Magento\Framework\Controller\Result\ForwardFactory;
use Magento\Framework\Exception\NotFoundException;
use Magento\Framework\App\RequestInterface;

/**
 * Contact index controller
 */
class Index extends \Magento\Framework\App\Action\Action
{
    /**
     * @var PageFactory
     */
    protected $resultPageFactory;

    /**
     * @var ForwardFactory
     */
    protected $resultForwardFactory;

    /**
     * @var \Aion\Test\Helper\Data
     */
    protected $helper;

    /**
     * Index constructor.
     *
     * @param \Magento\Framework\App\Action\Context $context
     * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
     * @param \Magento\Framework\Controller\Result\ForwardFactory $resultForwardFactory
     * @param \Aion\Test\Helper\Data $helper
     */
    public function __construct(
        Context $context,
        PageFactory $resultPageFactory,
        ForwardFactory $resultForwardFactory,
        \Aion\Test\Helper\Data $helper
    ) {
        $this->resultPageFactory = $resultPageFactory;
        $this->resultForwardFactory = $resultForwardFactory;
        $this->helper = $helper;
        parent::__construct($context);
    }

    /**
     * Dispatch request
     *
     * @param RequestInterface $request
     * @return \Magento\Framework\App\ResponseInterface
     * @throws \Magento\Framework\Exception\NotFoundException
     */
    public function dispatch(RequestInterface $request)
    {
        if (!$this->helper->isEnabled()) {
            throw new NotFoundException(__('Page not found.'));
        }
        return parent::dispatch($request);
    }

    /**
     * Aion Test Page
     *
     * @return \Magento\Framework\View\Result\Page
     */
    public function execute()
    {
        /** @var \Magento\Framework\View\Result\Page $resultPage */
        $resultPage = $this->resultPageFactory->create();
        $resultPage->getConfig()->getTitle()->set(__('Aion Test Page'));
        if (!$resultPage) {
            $resultForward = $this->resultForwardFactory->create();
            return $resultForward->forward('noroute');
        }
        return $resultPage;
    }
} 

A fájlban három fontos függvény található, mindhárom a szülőosztályban van definiálva. A __construct függvényt használjuk arra, hogy az általunk használni kívánt egyéb osztályokat injektáljuk (pl. helper osztály, dependency injection).  A dispatch függvény automatikusan le fog futni a __construct után. Itt vizsgáljuk meg, hogy a modulunk engedélyezve van-e, vagy sem és megfelelően lekezeljük. Végül pedig az execute függvény jelenti magát az action-t, ami a jelenlegi esetben az Index action-nek felel meg.

A működése során a $resultPageFactory objektumot létrehozzuk (ami a későbbi layout konfiguráció alapján felépíti az oldalt). Az objektum setConfig függvényével beállítunk egy oldal title-t. Ezután megvizsgáljuk, hogy az objektum létrejött-e, vagy sem. Amennyiben nincs hiba, visszatérünk a $resultPage objektummal, ellenkező esetben noroute (vagyis 404-es) oldalra irányítjuk az action-t.   Létrehozzuk az app/code/Aion/Test/etc/frontend könyvtárban a routes.xml fájlt, amiben meghatározzuk, hogy milyen url alatt szeretnénk meghívni a modulunkhoz controller-eket. A fájl tartalma:

<?php
/**
 * Copyright © 2015 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Controller\Index;

use Magento\Framework\App\Action\Context;
use Magento\Framework\View\Result\PageFactory;
use Magento\Framework\Controller\Result\ForwardFactory;
use Magento\Framework\Exception\NotFoundException;
use Magento\Framework\App\RequestInterface;

/**
 * Contact index controller
 */
class Index extends \Magento\Framework\App\Action\Action
{
    /**
     * @var PageFactory
     */
    protected $resultPageFactory;

    /**
     * @var ForwardFactory
     */
    protected $resultForwardFactory;

    /**
     * @var \Aion\Test\Helper\Data
     */
    protected $helper;

    /**
     * Index constructor.
     *
     * @param \Magento\Framework\App\Action\Context $context
     * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
     * @param \Magento\Framework\Controller\Result\ForwardFactory $resultForwardFactory
     * @param \Aion\Test\Helper\Data $helper
     */
    public function __construct(
        Context $context,
        PageFactory $resultPageFactory,
        ForwardFactory $resultForwardFactory,
        \Aion\Test\Helper\Data $helper
    ) {
        $this->resultPageFactory = $resultPageFactory;
        $this->resultForwardFactory = $resultForwardFactory;
        $this->helper = $helper;
        parent::__construct($context);
    }

    /**
     * Dispatch request
     *
     * @param RequestInterface $request
     * @return \Magento\Framework\App\ResponseInterface
     * @throws \Magento\Framework\Exception\NotFoundException
     */
    public function dispatch(RequestInterface $request)
    {
        if (!$this->helper->isEnabled()) {
            throw new NotFoundException(__('Page not found.'));
        }
        return parent::dispatch($request);
    }

    /**
     * Aion Test Page
     *
     * @return \Magento\Framework\View\Result\Page
     */
    public function execute()
    {
        /** @var \Magento\Framework\View\Result\Page $resultPage */
        $resultPage = $this->resultPageFactory->create();
        $resultPage->getConfig()->getTitle()->set(__('Aion Test Page'));
        if (!$resultPage) {
            $resultForward = $this->resultForwardFactory->create();
            return $resultForward->forward('noroute');
        }
        return $resultPage;
    }
} 

A router konfigurációban látható, hogy a modulunkhoz a „test” url-t rendeltük, vagyis ez esetben a {{base_url}}test/index/index fogja meghívni a fent részletezett controller-t és azon belül az execute függvényt (Index action). Természetesen index controllert és action-t nem kell megadni mindig az url-ben, a {{base_url}}test/ is ugyanerre a helyre fog minket irányítani.   Következőkben létrehozzuk a layout fájlt ami az app/code/Aion/Test/view/frontend/layout könyvtárban kap helyet test_index_index.xml néven. Jól látható, hogy a fájl neve követi a router -> controller -> action neveket. A fájl tartalma:

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2015 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <head>
        <title>Aion Test Page</title>
    </head>
    <body>
        <referenceContainer name="content">
            <block class="Aion\Test\Block\Test" name="testPage" template="Aion_Test::test.phtml" />
        </referenceContainer>
    </body>
</page>

A layout file <head> részében beállítjuk az oldal alap címét, a tartalmi részben (content) egy block-ot és a hozzá tartozó template fájlt is definiáljuk. A block-ot az app/code/Aion/Test/Block könyvtárban lévő Test.php-ban valósítjuk meg. A fájl tartalma:

<?php
/**
 * Copyright © 2015 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Block;

use Magento\Framework\View\Element\Template;

/**
 * Aion Test Page block
 */
class Test extends Template
{
    /**
     * @param Template\Context $context
     * @param array $data
     */
    public function __construct(Template\Context $context, array $data = [])
    {
        parent::__construct($context, $data);
    }

    /**
     * Test function
     *
     * @return string
     */
    public function getTest()
    {
        return 'This is a test function for some logic...';
    }
}  

Észrevehetjük, hogy a példában használt __construct függvényben nem deklaráltunk semmit, így ez lényegében el is hagyható. Azonban célszerű már az elején megvalósítani, ha például később egy storeManager vagy Helper-t vagy bármi mást is szeretnénk implementálni. A template file az app/code/Aion/Test/view/frontend/templates könyvtárban kap helyet test.phtml néven. A fájl tartalma:

<?php
/**
 * Copyright © 2015 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
?>

<?php
/**
 * @var $block \Aion\Test\Block\Test
 */
?>

<p><?php echo $block->getTest(); ?></p>
<p><?php echo __('This is a text.') ?></p>

A template file a példa kedvéért meghívja a Block-ban definiált teszt függvényt, és emellett egy egyszerű string-et is kiír, ami egy translate függvénybe van ágyazva. Ha mindennel elkészültünk, a modulunk így néz ki: app/code Aion/ Test/ Block/Test.php Controller/Index/Index.php etc/adminhtml/system.xml /frontend/routes.xml acl.xml config.xml module.xml Helper/Data.php view/frontend /layout/test_index_index.xml /templates/test.phtml composer.json COPYING.txt README.md registration.php

Az előzőekben megismerkedtünk egy alap Magento 2.0 modul elkészítésével és felépítésével. Most folytatjuk, és megismerkedünk azzal, hogyan készíthetünk saját adatbázis táblát, tölthetjük fel alapadatokkal a modulunkhoz, és a táblaadatok kezeléséhez szükséges modellekkel, ill. collection-nel. Emellett a frontend-en megnézzük, hogy érhetőek el tábla adatok és milyen módon jeleníthetjük meg őket.

6) Modulhoz tartozó adatbázis tábla elkészítése – 1

A modulhoz tartozó adatbázis táblát egy a Magento 1.x-ből már ismert installer script segítségével készíthetjük el, azonban a file neve és felépítése kicsit eltérő.   Az adatbázis táblát az app/code/Aion/Test/Setup könyvtárban lévő InstallSchema.php-ban valósítjuk meg. A fájl tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */

namespace Aion\Test\Setup;

use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\DB\Adapter\AdapterInterface;

/**
 * @codeCoverageIgnore
 */
class InstallSchema implements InstallSchemaInterface
{
    /**
     * Install table
     *
     * @param \Magento\Framework\Setup\SchemaSetupInterface $setup
     * @param \Magento\Framework\Setup\ModuleContextInterface $context
     * @throws \Zend_Db_Exception
     */
    public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
    {
        $installer = $setup;

        $installer->startSetup();
        /**
         * Create table 'aion_test'
         */
        $table = $installer->getConnection()->newTable(
            $installer->getTable('aion_test')
        )->addColumn(
            'test_id',
            \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT,
            null,
            ['identity' => true, 'nullable' => false, 'primary' => true],
            'Test ID'
        )->addColumn(
            'name',
            \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
            255,
            ['nullable' => false],
            'Test Name'
        )->addColumn(
            'email',
            \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
            255,
            ['nullable' => false],
            'Test Email'
        )->addColumn(
            'creation_time',
            \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP,
            null,
            ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT],
            'Test Creation Time'
        )->addColumn(
            'update_time',
            \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP,
            null,
            ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT_UPDATE],
            'Test Modification Time'
        )->addColumn(
            'is_active',
            \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT,
            null,
            ['nullable' => false, 'default' => '1'],
            'Is Test Active'
        )->addIndex(
            $setup->getIdxName(
                $installer->getTable('aion_test'),
                ['name', 'email'],
                AdapterInterface::INDEX_TYPE_FULLTEXT
            ),
            ['name', 'email'],
            ['type' => AdapterInterface::INDEX_TYPE_FULLTEXT]
        )->setComment(
            'Aion Test Table'
        );
        $installer->getConnection()->createTable($table);

        $installer->endSetup();
    }
}

A script létrehozza a modulhoz tartozó példa táblát, aminek neve „aion_test” lesz. Ezután létrehozza az alábbi mezőket is:

  • test_id – primary key, smallint (6)
  • name – varchar (255)
  • email – varchar (255)
  • creation_time – timestamp
  • update_time – timestamp
  • is_active – smallint (6)

Ezt követően a scriptben célszerű és erősen ajánlott index-szel (addIndex) ellátni azon text (text és varchar és egyéb, ha szükséges) típusú oszlopokat, melyekben keresni fogunk a későbbiekben vagy a leendő collection-t szűrni fogjuk. A példa esetében ez a két mező a name és az email.

7) Modulhoz tartozó adatbázis tábla elkészítése – 2

Korábban már adtunk verziószámot a modulunkhoz az app/code/Aion/Test/etc könyvtárban lévő module.xml segítségével ‒ amiben meghatároztuk az alap verziót, majd a modul engedélyezése (terminál parancsok) segítségével ez a verziószám bekerült a „setup_module” Magento 2.0 táblába. Arra, hogy fent elkészített script lefusson, két lehetőségünk van: növeljük a verziószámot a modulunkban, vagy töröljük a modulunkhoz tartozó korábbi verziószám bejegyzést.

Mivel még csak a modul fejlesztésének elején tartunk, így célszerű törölni „setup_module” Magento 2.0 táblából a modulunkhoz tartozó bejegyzést. Miután ezt megtettük, ismét futtassuk le a setup:upgrade magento parancsot a terminálból. Ha mindent jól csináltunk, a modulhoz tartozó táblánk létrejön az adatbázisban.

8) Modulhoz tartozó modellek és collection létrehozása

Ahhoz, hogy adatokkal tudjuk feltölteni az elkészült táblát vagy adatokat tudjunk lekérdezni, el kell készítenünk a modul model, resource és collection file-okat. Az alap model file az app/code/Aion/Test/Model könyvtárban lévő Test.php-ban valósítjuk meg. A fájl tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Model;

/**
 * Aion Test model
 *
 * @method \Aion\Test\Model\ResourceModel\Test _getResource()
 * @method \Aion\Test\Model\ResourceModel\Test getResource()
 * @method string getId()
 * @method string getName()
 * @method string getEmail()
 * @method setSortOrder()
 * @method int getSortOrder()
 */
class Test extends \Magento\Framework\Model\AbstractModel
{
    /**
     * Statuses
     */
    const STATUS_ENABLED = 1;
    const STATUS_DISABLED = 0;

    /**
     * Aion Test cache tag
     */
    const CACHE_TAG = 'aion_test';

    /**
     * @var string
     */
    protected $_cacheTag = 'aion_test';

    /**
     * Prefix of model events names
     *
     * @var string
     */
    protected $_eventPrefix = 'aion_test';

    /**
     * @return void
     */
    protected function _construct()
    {
        $this->_init('Aion\Test\Model\ResourceModel\Test');
    }

    /**
     * Get identities
     *
     * @return array
     */
    public function getIdentities()
    {
        return [self::CACHE_TAG . '_' . $this->getId(), self::CACHE_TAG . '_' . $this->getId()];
    }

    /**
     * Prepare item's statuses
     *
     * @return array
     */
    public function getAvailableStatuses()
    {
        return [self::STATUS_ENABLED => __('Enabled'), self::STATUS_DISABLED => __('Disabled')];
    }

}

A model file-ban definiáljuk a Magento 2.0 cache működéshez szükséges két cache tag-et. Fontos még event prefix-et is definiálni, hogy később observer-ek használata esetén tudjuk használni, mint event name. A legfontosabb a _construct() függvény, melyben meghatározzuk a modellhez tartozó resource modellt. A getAvailableStatuses() függvényt és két STATUS_* konstansokat későbbi használatra célszerű létrehozni. A getIdentities() függvény implementálása nem kötelező, de a cache kezelés megfelelő működéséhez célszerű.

Miután elkészült az alap modell fájlunk, hozzuk létre a hozzá tartozó resource modellt is. Az alap resource modell fájlt az app/code/Aion/Test/Model/ResourceModel könyvtárban lévő Test.php-ban valósítjuk meg. A fájl tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Model;

/**
 * Aion Test model
 *
 * @method \Aion\Test\Model\ResourceModel\Test _getResource()
 * @method \Aion\Test\Model\ResourceModel\Test getResource()
 * @method string getId()
 * @method string getName()
 * @method string getEmail()
 * @method setSortOrder()
 * @method int getSortOrder()
 */
class Test extends \Magento\Framework\Model\AbstractModel
{
    /**
     * Statuses
     */
    const STATUS_ENABLED = 1;
    const STATUS_DISABLED = 0;

    /**
     * Aion Test cache tag
     */
    const CACHE_TAG = 'aion_test';

    /**
     * @var string
     */
    protected $_cacheTag = 'aion_test';

    /**
     * Prefix of model events names
     *
     * @var string
     */
    protected $_eventPrefix = 'aion_test';

    /**
     * @return void
     */
    protected function _construct()
    {
        $this->_init('Aion\Test\Model\ResourceModel\Test');
    }

    /**
     * Get identities
     *
     * @return array
     */
    public function getIdentities()
    {
        return [self::CACHE_TAG . '_' . $this->getId(), self::CACHE_TAG . '_' . $this->getId()];
    }

    /**
     * Prepare item's statuses
     *
     * @return array
     */
    public function getAvailableStatuses()
    {
        return [self::STATUS_ENABLED => __('Enabled'), self::STATUS_DISABLED => __('Disabled')];
    }

}

A legfontosabb, amit implementálnunk kell, a protected _construct() függvény, amiben meghatározzuk, hogy a korábban létrehozott táblához melyik mező a primary key. Miután elkészültünk a resource modellel, készítsük el a collection-t is. Az alap collection osztály fájlt az app/code/Aion/Test/Model/ResourceModel/Test könyvtárban lévő Collection.php-ban valósítjuk meg. A fájl tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Model\ResourceModel\Test;

/**
 * Aion Test collection
 */
class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection
{
    /**
     * @var string
     */
    protected $_idFieldName = 'test_id';

    /**
     * Store manager
     *
     * @var \Magento\Store\Model\StoreManagerInterface
     */
    protected $storeManager;

    /**
     * @param \Magento\Framework\Data\Collection\EntityFactoryInterface $entityFactory
     * @param \Psr\Log\LoggerInterface $logger
     * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy
     * @param \Magento\Framework\Event\ManagerInterface $eventManager
     * @param \Magento\Store\Model\StoreManagerInterface $storeManager
     * @param \Magento\Framework\DB\Adapter\AdapterInterface|null $connection
     * @param \Magento\Framework\Model\ResourceModel\Db\AbstractDb|null $resource
     */
    public function __construct(
        \Magento\Framework\Data\Collection\EntityFactoryInterface $entityFactory,
        \Psr\Log\LoggerInterface $logger,
        \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy,
        \Magento\Framework\Event\ManagerInterface $eventManager,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        \Magento\Framework\DB\Adapter\AdapterInterface $connection = null,
        \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource = null
    ) {
        parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $connection, $resource);
        $this->storeManager = $storeManager;
    }

    /**
     * Define resource model
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_init('Aion\Test\Model\Test', 'Aion\Test\Model\ResourceModel\Test');
    }
}

A collection osztályban a _construct() függvényt implementáljuk, ahol valójában meghatározzuk, hogy a korábban elkészített modell osztályhoz melyik resource modell osztály tartozik. Lényegében ez elég is lenne collection-nek, azonban előretekintve helyezzük be (injektáljuk) már most a store manager osztályt is a public __construct() függvénybe későbbi felhasználás végett, hiszen minden modulnál követelmény a multistore támogatás. Amennyiben a három fájllal elkészültünk, akkor már létre is hoztuk azon osztályokat, melyekkel adatokat tudunk írni, ill. olvasni korábban létrehozott adatbázis táblánkból.

9) Tábla feltöltése adatokkal, script segítségével

A modell struktúra elkészítése szükséges volt ahhoz, hogy script-tel is tudjunk adatokat hozzáadni a modul alap táblájához. Az adat script-et az app/code/Aion/Test/Setup könyvtárban lévő InstallData.php-ban valósítjuk meg. A fájl tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Setup;

use Aion\Test\Model\Test;
use Aion\Test\Model\TestFactory;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;

/**
 * @codeCoverageIgnore
 */
class InstallData implements InstallDataInterface
{
    /**
     * Test factory
     *
     * @var TestFactory
     */
    private $testFactory;

    /**
     * Init
     *
     * @param TestFactory $testFactory
     */
    public function __construct(TestFactory $testFactory)
    {
        $this->testFactory = $testFactory;
    }

    /**
     * {@inheritdoc}
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
     */
    public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
    {
        $testItems = [
            [
                'name' => 'John Doe',
                'email' => '[email protected]',
                'is_active' => 1,
            ],
            [
                'name' => 'Jane Doe',
                'email' => '[email protected]',
                'is_active' => 0,
            ],

            [
                'name' => 'Steve Test',
                'email' => '[email protected]',
                'is_active' => 1,
            ],
        ];

        /**
         * Insert default items
         */
        foreach ($testItems as $data) {
            $this->createTest()->setData($data)->save();
        }

        $setup->endSetup();
    }

    /**
     * Create Test item
     *
     * @return Test
     */
    public function createTest()
    {
        return $this->testFactory->create();
    }
}

Az InstallData osztályban implementáljuk az install(…) függvényt, melyben létrehozzuk a teszt adatokat egy többdimenziós tömbben. Majd ezen végig iterálva meghívjuk a createTest() függvényt, melynek az új Test modell létrehozása a feladata, és a tömbök, mint adatok hozzáadása után mentjük a modellt.

Itt fontos megemlíteni az osztályban létrehozott és __construct() függvényben injektált TestFactory osztályt is. Ezt az osztályt a Magento 2.0 hozza létre automatikusan a /var/generation/Aion/Test/Model könyvtárba az első futása során ($this->testFactory-> create()). Mivel még mindig a modulunk fejlesztése elején tartunk, dobjuk el manuálisan a korábban létrehozott „aion_test” táblát az adatbázisból, és töröljük a „setup_module” Magento 2.0 táblából a modulunkhoz tartozó bejegyzést. Miután ezt megtettük, ismét futtassuk le a setup:upgrade magento parancsot terminálból.

Ha mindent jól csináltunk, a modulhoz tartozó táblánk ismét létrejön az adatbázisban immár adatokkal feltöltve. Természetesen ehelyett verziószám módosítással (emeléssel) is tudjuk az adatfeltöltést futtatni parancssorból, így ekkor nem kell manuálisan eldobni a korábban létrehozott táblánkat és „setup_module” táblába sem kell belenyúlni.

10) Tábla update script és verzió növelés

Ahogy fejlesztjük a modulunkat, sokszor lesz szükségünk az alap adatbázis tábla módosítására, esetleg új adatbázis tábla létrehozására. A Magento 1.x-ben ezt upgrade script-ekkel tehettük meg, mint adatbázis, mint pedig data oldalon. A Magento 2.0 modulunk sincs másképp, csak szerencsére az adatbázis script-eket egy fájlban kell ezúton kezelni, több különálló fájl helyett.

Az adatbázis update script-et az app/code/Aion/Test/Setup könyvtárban lévő UpgradeSchema.php-ban valósítjuk meg. A fájl tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Setup;

use Aion\Test\Model\Test;
use Aion\Test\Model\TestFactory;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;

/**
 * @codeCoverageIgnore
 */
class InstallData implements InstallDataInterface
{
    /**
     * Test factory
     *
     * @var TestFactory
     */
    private $testFactory;

    /**
     * Init
     *
     * @param TestFactory $testFactory
     */
    public function __construct(TestFactory $testFactory)
    {
        $this->testFactory = $testFactory;
    }

    /**
     * {@inheritdoc}
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
     */
    public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
    {
        $testItems = [
            [
                'name' => 'John Doe',
                'email' => '[email protected]',
                'is_active' => 1,
            ],
            [
                'name' => 'Jane Doe',
                'email' => '[email protected]',
                'is_active' => 0,
            ],

            [
                'name' => 'Steve Test',
                'email' => '[email protected]',
                'is_active' => 1,
            ],
        ];

        /**
         * Insert default items
         */
        foreach ($testItems as $data) {
            $this->createTest()->setData($data)->save();
        }

        $setup->endSetup();
    }

    /**
     * Create Test item
     *
     * @return Test
     */
    public function createTest()
    {
        return $this->testFactory->create();
    }
}

A példában egy új mezőt (sort_order) adunk az alap adatbázis táblához („aion_test”) upgrade script segítségével. Az upgrade függvényben implementált módosítás csak akkor fog lefutni, ha a modul verziószáma eléri a példában látható 2.0.1 értéket.

11) Adatok megjelenítése frontend-en

Ahhoz, hogy frontend-en a létrehozott adatokat meg tudjuk jeleníteni, módosítani kell a frontend template-et és a hozzá tartozó block osztályt is. Az block osztályt már korábban elkészítettük, most kiegészítjük. A block osztály az  app/code/Aion/Block/ könyvtárban lévő Test.php-ban valósítjuk meg. A fájl tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Setup;

use Aion\Test\Model\Test;
use Aion\Test\Model\TestFactory;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;

/**
 * @codeCoverageIgnore
 */
class InstallData implements InstallDataInterface
{
    /**
     * Test factory
     *
     * @var TestFactory
     */
    private $testFactory;

    /**
     * Init
     *
     * @param TestFactory $testFactory
     */
    public function __construct(TestFactory $testFactory)
    {
        $this->testFactory = $testFactory;
    }

    /**
     * {@inheritdoc}
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
     */
    public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
    {
        $testItems = [
            [
                'name' => 'John Doe',
                'email' => '[email protected]',
                'is_active' => 1,
            ],
            [
                'name' => 'Jane Doe',
                'email' => '[email protected]',
                'is_active' => 0,
            ],

            [
                'name' => 'Steve Test',
                'email' => '[email protected]',
                'is_active' => 1,
            ],
        ];

        /**
         * Insert default items
         */
        foreach ($testItems as $data) {
            $this->createTest()->setData($data)->save();
        }

        $setup->endSetup();
    }

    /**
     * Create Test item
     *
     * @return Test
     */
    public function createTest()
    {
        return $this->testFactory->create();
    }
}

A block osztály konstruktorába implementáljuk a korábban létrehozott Test modelt és a hozzá tartozó testFactory-t és ezek mellett a collectionFactory-t (itemCollectionFactory) is. A collectionFactory osztály nagyon hasonló a testFactory-hoz, szintén a Magento 2.0 hozza létre a var/generation/Aion/Test/Model/ResourceModel/Test/ könyvtárban az első meghívása után. A getTestModel() függvény egy modellt példányosít, és a betöltésért felelős, míg a getItems() függvény egy teljes collection-t hoz létre.

A block-hoz tartozó template file-ban kiírathatjuk az adatokat tesztelésképp. A fájl tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
?>

<?php
/**
 * @var $block \Aion\Test\Block\Test
 */
?>
<div class="aion-test">
    <h2><?php echo __('This is a test extension!') ?></h2>
    <!-- See a sample model -->
    <?php \Zend_Debug::dump($block->getTestModel()->getData()); ?>
    <!-- See a sample collection -->
    <?php \Zend_Debug::dump($block->getItems()->getData()); ?>

    <!-- See a sample collection iteration -->
    <?php $items = $block->getItems(); ?>
    <?php if ($items->getSize()) : ?>
        <?php foreach ($items as $item) : ?>
            <h3><?php echo $block->stripTags($item->getName()) ?></h3>
            <p>
                <span><?php echo __('Email:'); ?></span>&nbsp;
                <span><?php echo $item->getEmail() ?></span>
            </p>
        <?php endforeach; ?>
    <?php endif; ?>
</div>

Ebben a cikkben részletesen átvettünk jó néhány témakört az alap Magento 2.0 modulunk fejlesztéséhez: hozzáadtunk egy saját adatbázis táblát, és teszt adatokkal feltöltöttük. Az ehhez szükséges modellt, resource-t és collection-t is létrehoztuk, ill. a teszt adatokat megjelenítettük a frontend-en is.

VÉGE AZ 1. RÉSZNEK, DE HAMAROSAN FOLYTATJUK!   A 2. részben  a modulhoz tartozó admin táblázat (grid) elkészítésével, illetve a szükséges controllerek-kel is megismerkedünk majd.