Интерфейсы данных
Сервисный контракт — набор интерфейсов PHP, через которые другие модули работают с вашими данными. Интерфейс обещает: эти методы не изменятся, даже если таблица или модель внутри будут переписаны. Контракт состоит из двух частей: интерфейсы данных описывают запись, а репозитории — действия с ней. Этот урок посвящён первой части.
Где лежат интерфейсы
Все интерфейсы контракта находятся в папке Api модуля, интерфейсы данных — в Api/Data. Файлы создают вручную.
app/code/Acme/Hello/
├── Api/
│ └── Data/
│ └── ReviewInterface.php новый файл
├── Model/
│ └── Review.php изменён
└── etc/
└── di.xml изменёнИнтерфейс данных
Интерфейс перечисляет методы чтения и записи для каждого поля:
// app/code/Acme/Hello/Api/Data/ReviewInterface.php
namespace Acme\Hello\Api\Data;
interface ReviewInterface
{
/** @return int|null */
public function getId();
/** @return int */
public function getProductId();
/**
* @param int $productId
* @return $this
*/
public function setProductId($productId);
/** @return string */
public function getAuthor();
/**
* @param string $author
* @return $this
*/
public function setAuthor($author);
/** @return int */
public function getRating();
/**
* @param int $rating
* @return $this
*/
public function setRating($rating);
}Комментарии с @param и @return здесь обязательны: по ним Magento превращает объект в JSON и обратно, когда контракт открывают через API. Поле content добавляется такой же парой методов.
Реализация в модели
Модель из прошлого урока объявляет, что выполняет контракт, и получает явные методы:
// app/code/Acme/Hello/Model/Review.php
namespace Acme\Hello\Model;
use Acme\Hello\Api\Data\ReviewInterface;
use Magento\Framework\Model\AbstractModel;
class Review extends AbstractModel implements ReviewInterface
{
protected function _construct(): void
{
$this->_init(ResourceModel\Review::class);
}
public function getProductId()
{
return (int) $this->getData('product_id');
}
public function setProductId($productId)
{
return $this->setData('product_id', (int) $productId);
}
public function getAuthor()
{
return (string) $this->getData('author');
}
public function setAuthor($author)
{
return $this->setData('author', (string) $author);
}
public function getRating()
{
return (int) $this->getData('rating');
}
public function setRating($rating)
{
return $this->setData('rating', (int) $rating);
}
}Метод getId() уже есть в AbstractModel. Остальные читают и записывают значения столбцов через getData() и setData().
Предпочтение
Чтобы Magento знал, какой класс стоит за интерфейсом, добавьте строку в etc/di.xml:
<!-- app/code/Acme/Hello/etc/di.xml -->
<preference for="Acme\Hello\Api\Data\ReviewInterface" type="Acme\Hello\Model\Review"/>Выполните bin/magento cache:clean. На странице ничего не изменится — изменился способ, которым остальной код обращается к отзыву.
Использование
Теперь код зависит от интерфейса, а не от модели. Новый пустой отзыв даёт сгенерированная фабрика интерфейса:
// app/code/Acme/Hello/Model/ReviewService.php
use Acme\Hello\Api\Data\ReviewInterfaceFactory;
public function __construct(private ReviewInterfaceFactory $reviewFactory) {}
public function draft(int $productId, string $author): \Acme\Hello\Api\Data\ReviewInterface
{
return $this->reviewFactory->create()
->setProductId($productId)
->setAuthor($author)
->setRating(5);
}Метод возвращает заполненный, но ещё не сохранённый отзыв. Сохранять его будет репозиторий из следующего урока.
Что это даёт
| Без контракта | С контрактом |
|---|---|
Чужой код вызывает getData('author') и зависит от имени столбца | Чужой код вызывает getAuthor() из интерфейса |
| Изменение таблицы ломает другие модули | Таблицу можно менять, пока интерфейс прежний |
| Модель нельзя отдать через API | Интерфейс подключается к REST одной записью в XML |
Частая ошибка
В интерфейсе данных опускают комментарии или пишут в них array и mixed. В коде PHP всё работает, но при вызове через REST Magento не может определить тип поля и возвращает ошибку. Указывайте точные типы: int, string, float, bool, имя интерфейса или string[] для списка.