W tym artykule zademonstruję jak utworzyć model CRUD w Magento 2. W poprzednim artykule z tej serii dodawałem tabelę i właśnie do tej tabeli będę teraz robił Model.

Model

W Mageno 2 Model to taka klasa, która definiuje metody służące do interakcji z danymi. Do utworzenia modelu potrzebna jest klasa modelu i interfejs, który tą klasa implementuje, ponieważ Service Contracts w M2 oparte są o interfejsy. Zobacz na przykładowy Model z modułu Magento_Cms:

Zacznę od zdefiniowania mojego data interfejsu. Tworzę nowy plik BannerInterface.php w app/code/Mkwiatkowski/CatalogBanners/Api/Data

<?php declare(strict_types=1);
/**
 * @copyright Copyright (C) 2020 Marcin Kwiatkowski (http://marcin-kwiatkowski.com)
 */
namespace Mkwiatkowski\CatalogBanners\Api\Data;

/**
 * Interface BannerInterface
 * @package Mkwiatkowski\CatalogBanners\Api\Data
 * @api
 */
interface BannerInterface
{
    
}

Teraz zdefiniuje stałe, które będą odzwierciedleniem kolumn w bazie danych:

/**
 * Constants for getters and setters
 */
const BANNER_ID = 'banner_id';
const CATEGORY_ID = 'category_id';
const IS_ACTIVE = 'is_active';
const CONTENT = 'content';

Czas na zdefiniowanie getterów i setterów w interfejsie:

/**
 * Get Category Id
 *
 * @return int|null
 */
public function getCategoryId(): ?int;

/**
 * Set category Id
 *
 * @param int $categoryId
 * @return BannerInterface
 */
public function setCategoryId(int $categoryId): BannerInterface;

Analogicznie tworzę gettery i settery dla pozostałych pól.

Możesz zobaczyć skończony interfejs tutaj.

Mam już data interfejs, więc zabieram się za zdefiniowanie klasy modelu. Tworzę plik Banner.php w folderze app/code/Mkwiatkowski/CatalogBanners/Model. Klasa modelu dziedziczy po AbstractModel i implementuje dwa interfejsy: BannerInterface oraz IdentityInterface

Co to jest IdentityInterface?

Użycie tego interfejsu jest niezbędne dla każdego Modelu, który musi mieć wyczyszczone cache podczas zmian na tym modelu oraz dla każdego Bloku, który takie dane używa. W praktyce każdy Model i Blok w M2 implementuje ten interfejs, który posiada jedną metodę:getIdentities

Metoda ta zwraca unikalny cache tag dla bloku/modelu.

<?php declare(strict_types=1);
/**
 * @copyright Copyright (C) 2020 Marcin Kwiatkowski (http://marcin-kwiatkowski.com)
 */
namespace Mkwiatkowski\CatalogBanners\Model;

use Magento\Framework\DataObject\IdentityInterface;
use Magento\Framework\Model\AbstractModel;
use Mkwiatkowski\CatalogBanners\Api\Data\BannerInterface;

/**
 * Banner model
 *
 * Class Banner
 * @package Mkwiatkowski\CatalogBanners\Model
 */
class Banner extends AbstractModel implements BannerInterface, IdentityInterface
{
}

Teraz dodam do klasy stałe odpowiadające za cache tag i event prefix.

/**
 * Banner cache tag
 */
const CACHE_TAG = 'catalog_custom_banner';

/**
 * Banner event prefix
 */
const EVENT_PREFIX = 'catalog_custom_banner';

/**
 * Banner cache tag
 *
 * @var string
 */
protected $_cacheTag = self::CACHE_TAG; // @codingStandardsIgnoreLine

/**
 * Prefix for model events
 *
 * @var string
 */
protected $_eventPrefix = self::EVENT_PREFIX; // @codingStandardsIgnoreLine

Kolejną rzeczą jaką zrobię jest zaimplementowanie metody _construct, która inicjuje Resource Model. NIe mam jeszcze klasy Resource modelu, zajmę się jej implementacją gdy tylko uporam się z modelem.

protected function _construct()
{
    $this->_init(\Mkwiatkowski\CatalogBanners\Model\ResourceModel\Banner::class);
}

Aby zakończyć pracę nad modelem trzeba zaimplementować wszystkie metody pochodzące z interfejsów. Ostatnią rzeczą na jaką chciałem zwrócić uwagę to fakt, że muszę przeciążyć pole $_idFieldName, które moja klasa dziedziczy z AbstractModel.

/**
 * Id field name
 *
 * @var string
 */
protected $_idFieldName = self::BANNER_ID; // @codingStandardsIgnoreFile;

Możesz zobaczyć jak wygląda skończony Model tutaj.

Dependency injection

Interfejs i klasa Modelu jest gotowa. Teraz skonfiguruję Dependecy Injection. W tym celu muszę ustawić preferencję dla mojego interfejsu. Tworzę plik app/code/Mkwiatkowski/CatalogBanners/etc/di.xml

<?xml version="1.0"?>
<!--
/**
 * @copyright Copyright (C) 2020 Marcin Kwiatkowski (http://marcin-kwiatkowski.com)
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Mkwiatkowski\CatalogBanners\Api\Data\BannerInterface" type="Mkwiatkowski\CatalogBanners\Model\Block" />
</config>

Resource Model

Teraz utworzę klasę dla Resource Modelu. W Magento Resource Model to klasa, która bezpośrednio ma styczność z bazą danych i pobiera z niej dane. Każdy Model CRUD w Magento musi mieć swój Resource Model. Tworzę klasę Banner w folderze Mkwiatkowski/CatalogBanners/Model/ResourceModel

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

namespace Mkwiatkowski\CatalogBanners\Model\ResourceModel;

use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
use Mkwiatkowski\CatalogBanners\Api\Data\BannerInterface;

/**
 * Class Banner
 */
class Banner extends AbstractDb
{
    
}

Jedyne co tutaj muszę zrobić to wywołąć metodę _init, która przyjmuję dwa parametry: nazwę tabeli i nazwę pola ID dla tej tabeli.

/**
 * @inheritDoc
 */
protected function _construct() // @codingStandardsIgnoreLine
{
    $this->_init('catalog_banners', BannerInterface::BANNER_ID);
}

Gotową klasę możesz zobaczyć tutaj.

Collection

Ostatnią rzeczą jaką Ci dzisiaj pokażę jest zdefiniowanie klasy odpowiedzialnej za Kolekcję. Tworzę nową klasę \Mkwiatkowski\CatalogBanners\Model\ResourceModel\Banner\Collection, która dziedziczy po AbstractCollection Magento. W klasie przeciążam kilka pól takich jak $_idFieldName, $_eventPrefix i $_eventObject. Najważniejszą rzeczą w tej klasie jest odpalenie metody _init, która przyjmuje dwa parametry: Model i Resource Model.

Zaimplementowaną klasę możesz zobaczyć tutaj.

Podsumowanie

W tym artykule pokazałem krok po kroku jak w Magento 2 tworzy się model CRUD. Wszystko co tutaj zrobiłem możesz zobaczyć w repozytorium na GitHubie. Następnym razem będę robił Repository danych dla tego modelu. Do zobaczenia!