Как реализовать сервисный контракт для пользовательского модуля в Magento 2?

42

Как видно на этом посту: Устаревшее сохранить и методы нагрузки в абстрактной моделиsave и loadметоды являются устаревшими в Magento 2 развивать отрасль.

Таким образом, хорошей практикой является реализация сервисных контрактов для взаимодействия с объектами CRUD.

Каков пошаговый процесс, которому я должен следовать для реализации сервисных контрактов для моих пользовательских модулей?

NB: я знаю, что в моих CRUD-моделях могут быть тысячи методов, я просто прошу очевидные методы, как указано здесь: http://devdocs.magento.com/guides/v2.0/extension-dev-guide /service-contracts/design-patterns.html :

  • get
  • save
  • getList
  • delete
  • deleteById
Рафаэль в цифровом пианизме
источник

Ответы:

90

Я хотел бы дать немного больше деталей в дополнение к отличному ответу @ryanF.

Я хотел бы суммировать причины добавления репозитория для пользовательских объектов, привести примеры, как это сделать, а также объяснить, как представить эти методы репозитория как часть веб-API.

Отказ от ответственности: я только описываю прагматичный подход, как сделать это для сторонних модулей - у основных команд есть свои собственные стандарты, которым они следуют (или нет).

В общем, цель репозитория - скрыть логику, связанную с хранилищем.
Клиент хранилища не должен заботиться о том, хранится ли возвращенная сущность в памяти в массиве, извлекается ли она из базы данных MySQL, извлекается ли из удаленного API или из файла.
Я предполагаю, что основная команда Magento сделала это, чтобы они могли изменить или заменить ORM в будущем. В Magento ORM в настоящее время состоит из моделей, моделей ресурсов и коллекций.
Если сторонний модуль использует только репозитории, Magento может изменить способ и место хранения данных, и модуль продолжит работать, несмотря на эти глубокие изменения.

Хранилища , как правило, такие методы , как findById(), findByName(), put()или remove().
В Magento их обычно называют getbyId(), save()иdelete() , даже не притворяясь, они делают что-то еще, кроме операций CRUD DB.

Методы хранилища Magento 2 можно легко представить в виде ресурсов API, что делает их полезными для интеграции со сторонними системами или безголовыми экземплярами Magento.

Msgstr "Должен ли я добавить репозиторий для моей пользовательской сущности?"

Как всегда, ответ

"Это зависит".

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

Здесь есть еще один фактор: в Magento 2 репозитории могут быть легко представлены как веб-API, то есть ресурсы REST и SOAP.

Если вам это интересно из-за интеграции системы сторонних производителей или установки Magento без головы, то, опять же, да, вы, вероятно, захотите добавить репозиторий для вашей сущности.

Как добавить репозиторий для моей пользовательской сущности?

Предположим, вы хотите представить свою сущность как часть REST API. Если это не так, вы можете пропустить следующую часть при создании интерфейсов и перейти к разделу «Создание реализации репозитория и модели данных» ниже.

Создать хранилище и интерфейсы модели данных

Создайте папки Api/Data/в вашем модуле. Это просто соглашение, вы можете использовать другое место, но вы не должны.
Репозиторий идет в Api/папку. Data/Подкаталог для позже.

В Api/, создайте интерфейс PHP с методами, которые вы хотите представить. Согласно соглашениям Magento 2 все имена интерфейсов заканчиваются суффиксом Interface.
Например, дляHamburger сущности я бы создал интерфейс Api/HamburgerRepositoryInterface.

Создайте интерфейс хранилища

Репозитории Magento 2 являются частью доменной логики модуля. Это означает, что не существует фиксированного набора методов, которые должен реализовывать репозиторий.
Это полностью зависит от цели модуля.

Однако на практике все репозитории довольно похожи. Они являются обертками для функциональности CRUD.
Большинство из них имеют методы getById, save, deleteи getList.
Может быть больше, например метод CustomerRepositoryhas get, который выбирает клиента по электронной почте, посредством чего getByIdиспользуется для получения клиента по идентификатору объекта.

Вот пример интерфейса репозитория для сущности гамбургера:

<?php

namespace VinaiKopp\Kitchen\Api;

use Magento\Framework\Api\SearchCriteriaInterface;
use VinaiKopp\Kitchen\Api\Data\HamburgerInterface;

interface HamburgerRepositoryInterface
{
    /**
     * @param int $id
     * @return \VinaiKopp\Kitchen\Api\Data\HamburgerInterface
     * @throws \Magento\Framework\Exception\NoSuchEntityException
     */
    public function getById($id);

    /**
     * @param \VinaiKopp\Kitchen\Api\Data\HamburgerInterface $hamburger
     * @return \VinaiKopp\Kitchen\Api\Data\HamburgerInterface
     */
    public function save(HamburgerInterface $hamburger);

    /**
     * @param \VinaiKopp\Kitchen\Api\Data\HamburgerInterface $hamburger
     * @return void
     */
    public function delete(HamburgerInterface $hamburger);

    /**
     * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria
     * @return \VinaiKopp\Kitchen\Api\Data\HamburgerSearchResultInterface
     */
    public function getList(SearchCriteriaInterface $searchCriteria);

}

Важный! Здесь быть временами!
Здесь есть несколько ошибок, которые трудно отладить, если вы их неправильно поняли:

  1. НЕ используйте скалярные типы аргументов PHP7 или возвращаемые типы, если вы хотите подключить это к REST API!
  2. Добавьте аннотации PHPDoc для всех аргументов и тип возврата для всех методов!
  3. Используйте полные имена классов в блоке PHPDoc!

Аннотации анализируются в Magento Framework, чтобы определить, как преобразовать данные в JSON или XML. Импорт классов (то есть useоператоров) не применяется!

Каждый метод должен иметь аннотацию с любыми типами аргументов и типом возвращаемого значения. Даже если метод не принимает аргументов и ничего не возвращает, он должен иметь аннотацию:

/**
 * @return void
 */

Скалярные типы ( string, int, floatи bool) также должны быть определены, как аргументы и в качестве возвращаемого значения.

Обратите внимание, что в приведенном выше примере аннотации для методов, которые возвращают объекты, также указываются как интерфейсы.
Все интерфейсы возвращаемого типа находятся в Api\Dataпространстве имен / каталоге.
Это означает, что они не содержат никакой бизнес-логики. Они просто мешки с данными.
Мы должны создать эти интерфейсы дальше.

Создать интерфейс DTO

Я думаю, что Magento называет эти интерфейсы «моделями данных», это имя мне совсем не нравится.
Этот тип класса обычно известен как объект передачи данных или DTO .
Эти классы DTO имеют только методы получения и установки для всех своих свойств.

Причина, по которой я предпочитаю использовать DTO, а не модель данных, заключается в том, что не так легко спутать их с моделями данных ORM, моделями ресурсов или моделями представления ... слишком многие вещи уже являются моделями в Magento.

Те же ограничения в отношении типизации PHP7, которые применяются к репозиториям, также применяются к DTO.
Кроме того, каждый метод должен иметь аннотацию со всеми типами аргументов и типом возвращаемого значения.

<?php

namespace VinaiKopp\Kitchen\Api\Data;

use Magento\Framework\Api\ExtensibleDataInterface;

interface HamburgerInterface extends ExtensibleDataInterface
{
    /**
     * @return int
     */
    public function getId();

    /**
     * @param int $id
     * @return void
     */
    public function setId($id);

    /**
     * @return string
     */
    public function getName();

    /**
     * @param string $name
     * @return void
     */
    public function setName($name);

    /**
     * @return \VinaiKopp\Kitchen\Api\Data\IngredientInterface[]
     */
    public function getIngredients();

    /**
     * @param \VinaiKopp\Kitchen\Api\Data\IngredientInterface[] $ingredients
     * @return void
     */
    public function setIngredients(array $ingredients);

    /**
     * @return string[]
     */
    public function getImageUrls();

    /**
     * @param string[] $urls
     * @return void
     */
    public function setImageUrls(array $urls);

    /**
     * @return \VinaiKopp\Kitchen\Api\Data\HamburgerExtensionInterface|null
     */
    public function getExtensionAttributes();

    /**
     * @param \VinaiKopp\Kitchen\Api\Data\HamburgerExtensionInterface $extensionAttributes
     * @return void
     */
    public function setExtensionAttributes(HamburgerExtensionInterface $extensionAttributes);
}

Если метод извлекает или возвращает массив, тип элементов в массиве должен быть указан в аннотации PHPDoc, за которым следует открывающая и закрывающая квадратные скобки [].
Это верно как для скалярных значений (например int[]), так и для объектов (например IngredientInterface[]).

Обратите внимание, что я использую Api\Data\IngredientInterface в качестве примера для метода, возвращающего массив объектов, я не буду добавлять код ингредиентов в этот пост.

ExtensibleDataInterface?

В приведенном выше примере HamburgerInterfaceрасширяет ExtensibleDataInterface.
Технически это требуется только в том случае, если вы хотите, чтобы другие модули могли добавлять атрибуты к вашей сущности.
Если это так, вам также нужно добавить еще одну пару getter / setter, по соглашению называемую getExtensionAttributes()и setExtensionAttributes().

Наименование возвращаемого типа этого метода очень важно!

Среда Magento 2 сгенерирует интерфейс, реализацию и фабрику для реализации, если вы правильно их назовете. Подробности этой механики выходят за рамки этого поста, хотя.
Просто знайте, что если вызывается интерфейс объекта, который вы хотите сделать расширяемым \VinaiKopp\Kitchen\Api\Data\HamburgerInterface, то тип атрибутов расширения должен быть таким \VinaiKopp\Kitchen\Api\Data\HamburgerExtensionInterface. Таким образом, слово Extensionдолжно быть вставлено после имени сущности, прямо перед Interfaceсуффиксом.

Если вы не хотите, чтобы ваша сущность была расширяемой, то интерфейсу DTO не нужно расширять какой-либо другой интерфейс, getExtensionAttributes()и setExtensionAttributes()методы и могут быть опущены.

Пока достаточно об интерфейсе DTO, время вернуться к интерфейсу репозитория.

Возвращаемый тип getList () SearchResults

Метод репозитория getListвозвращает еще один тип, то есть SearchResultsInterfaceэкземпляр.

Этот метод getListможет, конечно, просто возвращать массив объектов, соответствующих указанному SearchCriteria, но возвращение SearchResultsэкземпляра позволяет добавить некоторые полезные метаданные к возвращаемым значениям.

Вы можете увидеть, как это работает ниже в getList()реализации метода репозитория .

Вот пример интерфейса результатов поиска гамбургера:

<?php

namespace VinaiKopp\Kitchen\Api\Data;

use Magento\Framework\Api\SearchResultsInterface;

interface HamburgerSearchResultInterface extends SearchResultsInterface
{
    /**
     * @return \VinaiKopp\Kitchen\Api\Data\HamburgerInterface[]
     */
    public function getItems();

    /**
     * @param \VinaiKopp\Kitchen\Api\Data\HamburgerInterface[] $items
     * @return void
     */
    public function setItems(array $items);
}

Все, что делает этот интерфейс, - это переопределяет типы для двух методов getItems()и setItems()родительского интерфейса.

Резюме интерфейсов

Теперь у нас есть следующие интерфейсы:

  • \VinaiKopp\Kitchen\Api\HamburgerRepositoryInterface
  • \VinaiKopp\Kitchen\Api\Data\HamburgerInterface
  • \VinaiKopp\Kitchen\Api\Data\HamburgerSearchResultInterface

Хранилище не проходит ничего, расширяет , и расширяет .
HamburgerInterface\Magento\Framework\Api\ExtensibleDataInterface
HamburgerSearchResultInterface\Magento\Framework\Api\SearchResultsInterface

Создать репозиторий и реализации модели данных

Следующим шагом является создание реализаций трех интерфейсов.

Репозиторий

По сути, хранилище использует ORM для своей работы.

В getById(), save() и delete()методы довольно прямо вперед.
Он HamburgerFactoryвставляется в хранилище в качестве аргумента конструктора, как можно увидеть чуть ниже.

public function getById($id)
{
    $hamburger = $this->hamburgerFactory->create();
    $hamburger->getResource()->load($hamburger, $id);
    if (! $hamburger->getId()) {
        throw new NoSuchEntityException(__('Unable to find hamburger with ID "%1"', $id));
    }
    return $hamburger;
}

public function save(HamburgerInterface $hamburger)
{
    $hamburger->getResource()->save($hamburger);
    return $hamburger;
}

public function delete(HamburgerInterface $hamburger)
{
    $hamburger->getResource()->delete($hamburger);
}

Теперь о самой интересной части хранилища, getList()метод. Метод должен перевести условия в вызовы методов по сбору.
getList()SerachCriteria

Сложная часть этого заключается в том, чтобы получить правильные условия ANDи ORусловия для фильтров, тем более что синтаксис для установки условий для коллекции различен в зависимости от того, является ли это объектом EAV или плоской таблицей.

В большинстве случаев getList()может быть реализовано, как показано в примере ниже.

<?php

namespace VinaiKopp\Kitchen\Model;

use Magento\Framework\Api\SearchCriteriaInterface;
use Magento\Framework\Api\SortOrder;
use Magento\Framework\Exception\NoSuchEntityException;
use VinaiKopp\Kitchen\Api\Data\HamburgerInterface;
use VinaiKopp\Kitchen\Api\Data\HamburgerSearchResultInterface;
use VinaiKopp\Kitchen\Api\Data\HamburgerSearchResultInterfaceFactory;
use VinaiKopp\Kitchen\Api\HamburgerRepositoryInterface;
use VinaiKopp\Kitchen\Model\ResourceModel\Hamburger\CollectionFactory as HamburgerCollectionFactory;
use VinaiKopp\Kitchen\Model\ResourceModel\Hamburger\Collection;

class HamburgerRepository implements HamburgerRepositoryInterface
{
    /**
     * @var HamburgerFactory
     */
    private $hamburgerFactory;

    /**
     * @var HamburgerCollectionFactory
     */
    private $hamburgerCollectionFactory;

    /**
     * @var HamburgerSearchResultInterfaceFactory
     */
    private $searchResultFactory;

    public function __construct(
        HamburgerFactory $hamburgerFactory,
        HamburgerCollectionFactory $hamburgerCollectionFactory,
        HamburgerSearchResultInterfaceFactory $hamburgerSearchResultInterfaceFactory
    ) {
        $this->hamburgerFactory = $hamburgerFactory;
        $this->hamburgerCollectionFactory = $hamburgerCollectionFactory;
        $this->searchResultFactory = $hamburgerSearchResultInterfaceFactory;
    }

    // ... getById, save and delete methods listed above ...

    public function getList(SearchCriteriaInterface $searchCriteria)
    {
        $collection = $this->collectionFactory->create();

        $this->addFiltersToCollection($searchCriteria, $collection);
        $this->addSortOrdersToCollection($searchCriteria, $collection);
        $this->addPagingToCollection($searchCriteria, $collection);

        $collection->load();

        return $this->buildSearchResult($searchCriteria, $collection);
    }

    private function addFiltersToCollection(SearchCriteriaInterface $searchCriteria, Collection $collection)
    {
        foreach ($searchCriteria->getFilterGroups() as $filterGroup) {
            $fields = $conditions = [];
            foreach ($filterGroup->getFilters() as $filter) {
                $fields[] = $filter->getField();
                $conditions[] = [$filter->getConditionType() => $filter->getValue()];
            }
            $collection->addFieldToFilter($fields, $conditions);
        }
    }

    private function addSortOrdersToCollection(SearchCriteriaInterface $searchCriteria, Collection $collection)
    {
        foreach ((array) $searchCriteria->getSortOrders() as $sortOrder) {
            $direction = $sortOrder->getDirection() == SortOrder::SORT_ASC ? 'asc' : 'desc';
            $collection->addOrder($sortOrder->getField(), $direction);
        }
    }

    private function addPagingToCollection(SearchCriteriaInterface $searchCriteria, Collection $collection)
    {
        $collection->setPageSize($searchCriteria->getPageSize());
        $collection->setCurPage($searchCriteria->getCurrentPage());
    }

    private function buildSearchResult(SearchCriteriaInterface $searchCriteria, Collection $collection)
    {
        $searchResults = $this->searchResultFactory->create();

        $searchResults->setSearchCriteria($searchCriteria);
        $searchResults->setItems($collection->getItems());
        $searchResults->setTotalCount($collection->getSize());

        return $searchResults;
    }
}

Фильтры внутри FilterGroupдолжны быть объединены с помощью оператора ИЛИ .
Отдельные группы фильтров объединяются с помощью логического оператора AND .

Фу
Это была самая большая часть работы. Другие реализации интерфейса проще.

DTO

Изначально Magento предназначал разработчиков для реализации DTO как отдельных классов, отличных от сущностной модели.

Основная команда сделала это только для клиентского модуля ( \Magento\Customer\Api\Data\CustomerInterfaceреализовано \Magento\Customer\Model\Data\Customer, а не \Magento\Customer\Model\Customer).
Во всех других случаях объектная модель реализует интерфейс DTO (например \Magento\Catalog\Api\Data\ProductInterface, реализуется с помощью \Magento\Catalog\Model\Product).

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

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

Если интерфейс DTO расширяет Magento\Framework\Api\ExtensibleDataInterface, модель должна расширяться Magento\Framework\Model\AbstractExtensibleModel.
Если вас не волнует расширяемость, модель может просто продолжить расширять базовый класс модели ORM Magento\Framework\Model\AbstractModel.

Так как пример HamburgerInterfaceрасширяет, ExtensibleDataInterfaceмодель гамбургера расширяет AbstractExtensibleModel, как можно видеть здесь:

<?php

namespace VinaiKopp\Kitchen\Model;

use Magento\Framework\Model\AbstractExtensibleModel;
use VinaiKopp\Kitchen\Api\Data\HamburgerExtensionInterface;
use VinaiKopp\Kitchen\Api\Data\HamburgerInterface;

class Hamburger extends AbstractExtensibleModel implements HamburgerInterface
{
    const NAME = 'name';
    const INGREDIENTS = 'ingredients';
    const IMAGE_URLS = 'image_urls';

    protected function _construct()
    {
        $this->_init(ResourceModel\Hamburger::class);
    }

    public function getName()
    {
        return $this->_getData(self::NAME);
    }

    public function setName($name)
    {
        $this->setData(self::NAME, $name);
    }

    public function getIngredients()
    {
        return $this->_getData(self::INGREDIENTS);
    }

    public function setIngredients(array $ingredients)
    {
        $this->setData(self::INGREDIENTS, $ingredients);
    }

    public function getImageUrls()
    {
        $this->_getData(self::IMAGE_URLS);
    }

    public function setImageUrls(array $urls)
    {
        $this->setData(self::IMAGE_URLS, $urls);
    }

    public function getExtensionAttributes()
    {
        return $this->_getExtensionAttributes();
    }

    public function setExtensionAttributes(HamburgerExtensionInterface $extensionAttributes)
    {
        $this->_setExtensionAttributes($extensionAttributes);
    }
}

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

SearchResult

Это SearchResultsInterfaceсамый простой из трех реализуемых интерфейсов, поскольку он может наследовать всю свою функциональность от класса фреймворка.

<?php

namespace VinaiKopp\Kitchen\Model;

use Magento\Framework\Api\SearchResults;
use VinaiKopp\Kitchen\Api\Data\HamburgerSearchResultInterface;

class HamburgerSearchResult extends SearchResults implements HamburgerSearchResultInterface
{

}

Настройте параметры ObjectManager

Несмотря на то, что реализации завершены, мы все равно не можем использовать интерфейсы в качестве зависимостей других классов, поскольку менеджер объектов Magento Framework не знает, какие реализации использовать. Нам нужно добавить etc/di.xmlконфигурацию с настройками.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="VinaiKopp\Kitchen\Api\HamburgerRepositoryInterface" type="VinaiKopp\Kitchen\Model\HamburgerRepository"/>
    <preference for="VinaiKopp\Kitchen\Api\Data\HamburgerInterface" type="VinaiKopp\Kitchen\Model\Hamburger"/>
    <preference for="VinaiKopp\Kitchen\Api\Data\HamburgerSearchResultInterface" type="VinaiKopp\Kitchen\Model\HamburgerSearchResult"/>
</config>

Как хранилище может быть представлено как ресурс API?

Эта часть действительно проста, это награда за то, что вы проделали всю работу, создавая интерфейсы, реализации и соединяя их вместе.

Все, что нам нужно сделать, это создать etc/webapi.xmlфайл.

<?xml version="1.0"?>
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">
    <route method="GET" url="/V1/vinaikopp_hamburgers/:id">
        <service class="VinaiKopp\Kitchen\Api\HamburgerRepositoryInterface" method="getById"/>
        <resources>
            <resource ref="anonymous"/>
        </resources>
    </route>
    <route method="GET" url="/V1/vinaikopp_hamburgers">
        <service class="VinaiKopp\Kitchen\Api\HamburgerRepositoryInterface" method="getList"/>
        <resources>
            <resource ref="anonymouns"/>
        </resources>
    </route>
    <route method="POST" url="/V1/vinaikopp_hamburgers">
        <service class="VinaiKopp\Kitchen\Api\HamburgerRepositoryInterface" method="save"/>
        <resources>
            <resource ref="anonymous"/>
        </resources>
    </route>
    <route method="PUT" url="/V1/vinaikopp_hamburgers">
        <service class="VinaiKopp\Kitchen\Api\HamburgerRepositoryInterface" method="save"/>
        <resources>
            <resource ref="anonymous"/>
        </resources>
    </route>
    <route method="DELETE" url="/V1/vinaikopp_hamburgers">
        <service class="VinaiKopp\Kitchen\Api\HamburgerRepositoryInterface" method="delete"/>
        <resources>
            <resource ref="anonymous"/>
        </resources>
    </route>
</routes>

Обратите внимание, что эта конфигурация не только позволяет использовать репозиторий в качестве конечных точек REST, но также предоставляет методы как часть SOAP API.

В первом примере маршрут, <route method="GET" url="/V1/vinaikopp_hamburgers/:id">заполнитель :idдолжен совпадать с именем аргумента преобразованного метода public function getById($id).
Два имени должны совпадать, например /V1/vinaikopp_hamburgers/:hamburgerId, не будет работать, так как имя переменной аргумента метода равно $id.

Для этого примера я установил доступность <resource ref="anonymous"/>. Это означает, что ресурс открыт публично без каких-либо ограничений!
Чтобы сделать ресурс доступным только зарегистрированному клиенту, используйте <resource ref="self"/>. В этом случае специальное слово meв URL-адресе конечной точки ресурса будет использоваться для заполнения переменной аргумента $idидентификатором текущего пользователя, вошедшего в систему.
Посмотрите на клиента Magento etc/webapi.xmlи, CustomerRepositoryInterfaceесли вам это нужно.

Наконец, <resources>можно также использовать для ограничения доступа к ресурсу учетной записи администратора. Для этого установите в качестве <resource>ссылки идентификатор, определенный в etc/acl.xmlфайле.
Например, <resource ref="Magento_Customer::manage"/>будет ограничивать доступ к любой учетной записи администратора, который имеет право управлять клиентами.

Пример запроса API с использованием curl может выглядеть следующим образом:

$ curl -X GET http://example.com/rest/V1/vinaikopp_hamburgers/123

Примечание: написание этого началось как ответ на https://github.com/astorm/pestle/issues/195
Проверьте пестик , купите Commercebug и станьте покровителем @alanstorm

Vinai
источник
1
Спасибо за этот отличный ответ. Извините, может быть, я что-то упустил, но какой смысл иметь чистый интерфейс для сущности, когда в конце он должен расширяться от AbstractModel, у которого есть метод setData, что означает, что вы можете добавить что-либо к объекту независимо от интерфейса?
LDusan
Класс может реализовывать любое количество интерфейсов и добавлять дополнительные методы. Важно то, что любой другой класс зависит только от методов интерфейса и поэтому не знает ни о каких других. Это делает детали реализации неинтерфейсных методов, которые могут быть изменены в любое время без нарушения внешних классов. Это идея инверсии зависимости. И класс, и любые клиенты зависят от интерфейса и не знают о деталях реализации. Это проясняет?
Vinai
Спасибо за ответ, я понимаю, что вы имеете в виду. Дело в том, что setData является публичным методом, поэтому я не уверен, что его можно рассматривать как детали реализации. Если он предназначен для использования в качестве публичного метода, как мы можем быть уверены, что он не нарушит ничего внешнего при изменении?
LDusan
3
Приношу извинения. То, что вы описываете, является общей точкой зрения. Механика зависимостей не является интуитивно понятной, и поскольку PHP позволяет вызывать методы, которые не являются частью зависимого от интерфейса, а также потому, что его не нужно компилировать, он делает способ работы зависимостей еще более размытым и трудно увидеть ясно , Это также можно наблюдать в ядре Magento 2, где есть много мест, где вызываются методы реализации, которые не являются частью зависимого от интерфейса. Это служит плохим примером и еще сложнее получить ясное и четкое понимание.
Vinai
35

@ Рафаэль в Цифровой пианизм:

Пожалуйста, обратитесь к следующей примерной структуре модуля:

app/
   code/
  |    Namespace/
  |   |    Custom/
  |   |   |    Api/
  |   |   |   |    CustomRepositoryInterface.php
  |   |   |   |    Data/
  |   |   |   |   |    CustomInterface.php
  |   |   |   |   |    CustomSearchResultsInterface.php
  |   |   |    etc/
  |   |   |   |    di.xml
  |   |   |   |    module.xml
  |   |   |    Model/
  |   |   |   |    Custom.php
  |   |   |   |    CustomRepository.php
  |   |   |   |    ResourceModel/
  |   |   |   |   |    Custom.php
  1. Создать интерфейс репозитория (Сервисный контракт)
    Namespace/Custom/Api/CustomRepositoryInterface.php: http://codepad.org/WognSKnH

  2. Создать SearchResultsInterface
    Namespace/Custom/Api/Data/CustomSearchResultsInterface.php: http://codepad.org/zcbi8X4Z

  3. Создать пользовательский интерфейс (контейнер данных)
    Namespace/Custom/Api/Data/CustomInterface.php: http://codepad.org/Ze53eT4o

  4. Создать CustomRepository (Конкретный репозиторий)
    Namespace/Custom/Model/CustomRepository.php: http://codepad.org/KNt5QAGZ
    Вот где происходит "волшебство". Через конструктор DI вы передаете модель ресурса / фабрику сбора для вашего пользовательского модуля; Что касается метода сохранения CRUD в этом репозитории, то из-за вашего CustomRepositoryInterface вы должны передать параметр CustomInterface. У вашего модуля di.xml есть предпочтение заменить интерфейс этого типа моделью объекта. Модель объекта передается в модель ресурсов и сохраняется.

  5. Установите предпочтения в
    Namespace/Custom/etc/di.xml: http://codepad.org/KmcoOUeV

  6. Модель сущностей, реализующая пользовательский интерфейс (контейнер данных)
    Namespace/Custom/Model/Custom.php: http://codepad.org/xQiBU7p7 .

  7. Модель ресурса
    Namespace/Custom/Model/ResourceModel/Custom.php: http://codepad.org/IOsxm9qW

Несколько вещей, на которые стоит обратить внимание:

  • Отказ от ответственности !!! Я использовал «Пространство имен» вместо вашего пользовательского имени поставщика, названия агентства и т. Д. ... независимо от того, какое имя вы используете для группировки своих модулей ... фактическое использование "Пространства имен" в Php совершенно недопустимо ... так что знайте что я сделал это для удобства, и что я не думаю, что это сработает, и я не предлагаю это никоим образом.

  • @ Райан-стрит научил меня этому ... так что я не хочу брать весь кредит

  • Четко измените реализацию репозитория в соответствии с вашими потребностями.

  • Вы реализуете взаимодействие с вашими пользовательскими моделями сущностей / моделями ресурсов / коллекциями в конкретном репозитории ...

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

ryanF
источник
Райан, являются ли методы, упомянутые в сервисных контрактах, обязательными для любых пользовательских мыльных API, которые мы создаем, т.е. save (), delete () и т. Д.?
Сушивам
Не могли бы вы дать мне представление о том, как создать пользовательский API мыла в magento 2?
Сушивам
@SachinS К сожалению, я не могу ничего рассказать о SOAP. Я еще не изучал это, и я еще не реализовал это. Лучшее, что я могу предложить, - это задать новый вопрос по этому поводу. Я бы сказал, также проверьте документы, но, к сожалению, это не всегда лучший способ действий (они могут отсутствовать). Вы всегда можете взглянуть на базовую кодовую базу или стороннее расширение и посмотреть, есть ли там какая-то идея. Удачи! Если вы найдете ответ, было бы неплохо добавить ссылку здесь. Спасибо
ryanF
Спасибо за ответ @ryan, в любом случае я реализовал свой модуль с помощью REST, так как его легкий вес по сравнению с SOAP ... Если я реализую то же самое в SOAP, плохо опубликую его
опубликую Sushivam
3
@ryanF Спасибо за этот очень полезный ответ. Я знаю, что это не должен быть рабочий код копирования / вставки, но вот несколько опечаток в пользу других, следующих далее. В репозитории CustomSearchResultsInterfaceFactory должен быть CustomSearchResultsFactory. $ searchResults-> setCriteria должно быть $ searchResults-> setSearchCriteria. $ Customs [] в foreach должен быть $ customs []. Я думаю, что это об этом.
тетранц
3

полные файлы использования сервисных контрактов

Пользовательский / модуль / registration.php

<?php

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

../etc/module.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Custom_Module" setup_version="1.0.0" />
</config>

../Setup/InstallSchema.php

<?php
namespace Custom\Module\Setup;
use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\DB\Ddl\Table;
class InstallSchema implements InstallSchemaInterface {
    public function install( SchemaSetupInterface $setup, ModuleContextInterface $context ) {
        $installer = $setup;
        $installer->startSetup();
        $table = $installer->getConnection()->newTable(
            $installer->getTable( 'ad_shipping_quote' )
        )->addColumn(
            'entity_id',
            Table::TYPE_SMALLINT,
            null,
            [ 'identity' => true, 'nullable' => false, 'primary' => true ],
            'Post ID'
        )->addColumn(
            'product_id',
            Table::TYPE_SMALLINT,
            255,
            [ ],
            'Post ID'
        )
            ->addColumn(
            'customer_name',
            Table::TYPE_TEXT,
            255,
            [ 'nullable' => false ],
            'Post Title'
        )

            ->addColumn(
            'customer_email',
            Table::TYPE_TEXT,
            '2M',
            [ ],
            'Post Content'
        ) ->addColumn(
                'customer_comments',
                Table::TYPE_TEXT,
                255,
                [ 'nullable' => false ],
                'Post Title'
            )->addColumn(
                'date_added',
                Table::TYPE_TEXT,
                255,
                [ 'nullable' => false ],
                'Post Title'
            )->addColumn(
                'date_updated',
                Table::TYPE_TEXT,
                255,
                [ 'nullable' => false ],
                'Post Title'
            )
            ->setComment(
            'Ad Shipping Quote Table'
        );
        $installer->getConnection()->createTable( $table );
        $installer->endSetup();
    }
}

../etc/di.xml

<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Custom\Module\Api\ModelRepositoryInterface"
                type="Custom\Module\Model\ModelRepository" />
    <preference for="Custom\Module\Api\Data\ModelInterface"
                type="Custom\Module\Model\Model" />
    <preference for="Custom\Module\Api\Data\ModelSearchResultsInterface"
                type="Custom\Module\Model\ModelSearchResults" />
</config>

../etc/webapi.xml

  <?xml version="1.0"?>
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">

    <route method="GET" url="/V1/model/:id">
        <service class="Custom\Module\Api\ModelRepositoryInterface" method="getById"/>
        <resources>
            <resource ref="anonymous"/>
        </resources>
    </route>


    <route method="GET" url="/V1/model">
        <service class="Custom\Module\Api\ModelRepositoryInterface" method="getList"/>
        <resources>
            <resource ref="anonymous"/>
        </resources>
    </route>
</routes>

../Api/ModelRepositoryInterface.php

  <?php
namespace Custom\Module\Api;

use \Custom\Module\Api\Data\ModelInterface;
use \Magento\Framework\Api\SearchCriteriaInterface;

interface ModelRepositoryInterface
{
    /**
     * @api
     * @param \Custom\Module\Api\Data\ModelInterface $model
     * @return \Custom\Module\Api\Data\ModelInterface
     */
    public function save(ModelInterface $model);

    /**
     * @api
     * @param \Custom\Module\Api\Data\ModelInterface $model
     * @return \Custom\Module\Api\Data\ModelInterface
     */
    public function delete(ModelInterface $model);

    /**
     * @api
     * @param \Custom\Module\Api\Data\ModelInterface $id
     * @return void
     */
    public function deleteById($id);

    /**
     * @api
     * @param int $id
     * @return \Custom\Module\Api\Data\ModelInterface
     * @throws \Magento\Framework\Exception\NoSuchEntityException
     */
    public function getById($id);

    /**
     * @api
     * @param \Magento\Framework\Api\SearchCriteriaInterface $criteria
     * @return \Custom\Module\Api\Data\ModelSearchResultsInterface
     */
    public function getList(SearchCriteriaInterface $criteria);
}

../Api/Data/ModelInterface.php

<?php
namespace Custom\Module\Api\Data;

interface ModelInterface
{
    /**
     * Return the Entity ID
     *
     * @return int
     */
    public function getEntityId();

    /**
     * Set Entity ID
     *
     * @param int $id
     * @return $this
     */
    public function setEntityId($id);

    /**
     * Return the Product ID associated with Quote
     *
     * @return int
     */
    public function getProductId();

    /**
     * Set the Product ID associated with Quote
     *
     * @param int $productId
     * @return $this
     */
    public function setProductId($productId);

    /**
     * Return the Customer Name
     *
     * @return string
     */
    public function getCustomerName();

    /**
     * Set the Customer Name
     *
     * @param string $customerName
     * @return $this
     */
    public function setCustomerName($customerName);

    /**
     * Return the Customer Email
     *
     * @return string
     */
    public function getCustomerEmail();

    /**
     * Set the Customer Email
     *
     * @param string $customerEmail
     * @return $this
     */
    public function setCustomerEmail($customerEmail);

    /**
     * Return the Customer Comments
     *
     * @return string
     */
    public function getCustomerComments();

    /**
     * Set the Customer Comments
     *
     * @param string $customerComments
     * @return $this
     */
    public function setCustomerComments($customerComments);

    /**
     * Return the Date and Time of record added
     *
     * @return string
     */
    public function getDateAdded();

    /**
     * Set the Date and Time of record added
     *
     * @param string $date
     * @return $this
     */
    public function setDateAdded($date);

    /**
     * Return the Date and Time of record updated
     *
     * @return string
     */
    public function getDateUpdated();

    /**
     * Set the Date and Time of record updated
     *
     * @param string $date
     * @return $this
     */
    public function setDateUpdated($date);
}

..Api / Data / ModelSearchResultsInterface.php

<?php

namespace Custom\Module\Api\Data;

use Magento\Framework\Api\SearchResultsInterface;

interface ModelSearchResultsInterface extends SearchResultsInterface
{
    /**
     * @return \Custom\Module\Api\Data\ModelInterface[]
     */
    public function getItems();

    /**
     * @param \Custom\Module\Api\Data\ModelInterface[] $items
     * @return $this
     */
    public function setItems(array $items);
}

../Model/Model.php

    <?php

namespace Custom\Module\Model;

use Custom\Module\Api\Data\ModelInterface;

class Model extends \Magento\Framework\Model\AbstractModel implements
    \Custom\Module\Api\Data\ModelInterface
{
    protected function _construct()
    {
        $this->_init('Custom\Module\Model\ResourceModel\Model');
    }

    /**
     * @inheritdoc
     */
    public function getEntityId()
    {
        return $this->_getData('entity_id');
    }

    /**
     * @inheritdoc
     */
    public function setEntityId($id)
    {
        $this->setData('entity_id', $id);
    }

    /**
     * @inheritdoc
     */
    public function getProductId()
    {
        return $this->_getData('product_id');
    }

    /**
     * @inheritdoc
     */
    public function setProductId($productId)
    {
        $this->setData('product_id', $productId);
    }

    /**
     * @inheritdoc
     */
    public function getCustomerName()
    {
        return $this->_getData('customer_name');
    }

    /**
     * @inheritdoc
     */
    public function setCustomerName($customerName)
    {
        $this->setData('customer_name', $customerName);
    }

    /**
     * @inheritdoc
     */
    public function getCustomerEmail()
    {
        return $this->_getData('customer_email');
    }

    /**
     * @inheritdoc
     */
    public function setCustomerEmail($customerEmail)
    {
        $this->setData('customer_email', $customerEmail);
    }

    /**
     * @inheritdoc
     */
    public function getCustomerComments()
    {
        return $this->_getData('customer_comments');
    }

    /**
     * @inheritdoc
     */
    public function setCustomerComments($customerComments)
    {
        $this->setData('customer_comments', $customerComments);
    }

    /**
     * @inheritdoc
     */
    public function getDateAdded()
    {
        return $this->_getData('date_added');
    }

    /**
     * @inheritdoc
     */
    public function setDateAdded($date)
    {
        $this->setData('date_added', $date);
    }

    /**
     * @inheritdoc
     */
    public function getDateUpdated()
    {
        return $this->_getData('date_updated');
    }

    /**
     * @inheritdoc
     */
    public function setDateUpdated($date)
    {
        $this->setData('date_updated', $date);
    }
}

../Model/ResourceModel/Model.php

<?php

namespace Custom\Module\Model\ResourceModel;

class Model extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
{
    protected $_idFieldName = 'entity_id';

    protected function _construct()
    {
        $this->_init('ad_shipping_quote','entity_id');
    }
}

../Model/ResourceModel/Model/Collection.php

<?php

namespace Custom\Module\Model\ResourceModel\Model;

class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection
{
    protected $_idFieldName = 'entity_id';
    protected $_eventPrefix = 'ad_shipping_quote_collection';
    protected $_eventObject = 'quote_collection';

    protected function _construct()
    {
        $this->_init('Custom\Module\Model\Model', 'Custom\Module\Model\ResourceModel\Model');
    }
}

../Model/ModelRepository.php

 <?php
    namespace Custom\Module\Model;

    use \Custom\Module\Api\Data\ModelInterface;
    use \Custom\Module\Model\ResourceModel\Model as ObjectResourceModel;
    use \Magento\Framework\Api\SearchCriteriaInterface;
    use \Magento\Framework\Exception\CouldNotSaveException;
    use \Magento\Framework\Exception\NoSuchEntityException;
    use \Magento\Framework\Exception\CouldNotDeleteException;

    class ModelRepository implements \Custom\Module\Api\ModelRepositoryInterface
    {
        protected $objectFactory;

        protected $objectResourceModel;

        protected $collectionFactory;

        protected $searchResultsFactory;

        public function __construct(
            \Custom\Module\Model\ModelFactory $objectFactory,
            ObjectResourceModel $objectResourceModel,
            \Custom\Module\Model\ResourceModel\Model\CollectionFactory $collectionFactory,
            \Magento\Framework\Api\SearchResultsInterfaceFactory $searchResultsFactory
        ) {
            $this->objectFactory        = $objectFactory;
            $this->objectResourceModel  = $objectResourceModel;
            $this->collectionFactory    = $collectionFactory;
            $this->searchResultsFactory = $searchResultsFactory;
        }

        public function save(ModelInterface $object)
        {
            $name = $object->getCustomerName();
            $hasSpouse = $object->getSpouse();
            if ($hasSpouse == true) {
                $name = "Mrs. " . $name;
            } else {
                $name = "Miss. " . $name;
            }
            $object->setCustomerName($name);
            try {
                $this->objectResourceModel->save($object);
            } catch (\Exception $e) {
                throw new CouldNotSaveException(__($e->getMessage()));
            }
            return $object;
        }

        /**
         * @inheritdoc
         */
        public function getById($id)
        {
            $object = $this->objectFactory->create();
            $this->objectResourceModel->load($object, $id);
            if (!$object->getId()) {
                throw new NoSuchEntityException(__('Object with id "%1" does not exist.', $id));
            }
            return $object;
        }

        public function delete(ModelInterface $object)
        {
            try {
                $this->objectResourceModel->delete($object);
            } catch (\Exception $exception) {
                throw new CouldNotDeleteException(__($exception->getMessage()));
            }
            return true;
        }

        public function deleteById($id)
        {
            return $this->delete($this->getById($id));
        }

        /**
         * @inheritdoc
         */
        public function getList(SearchCriteriaInterface $criteria)
        {
            $searchResults = $this->searchResultsFactory->create();
            $searchResults->setSearchCriteria($criteria);
            $collection = $this->collectionFactory->create();
            foreach ($criteria->getFilterGroups() as $filterGroup) {
                $fields = [];
                $conditions = [];
                foreach ($filterGroup->getFilters() as $filter) {
                    $condition = $filter->getConditionType() ? $filter->getConditionType() : 'eq';
                    $fields[] = $filter->getField();
                    $conditions[] = [$condition => $filter->getValue()];
                }
                if ($fields) {
                    $collection->addFieldToFilter($fields, $conditions);
                }
            }
            $searchResults->setTotalCount($collection->getSize());
            $sortOrders = $criteria->getSortOrders();
            if ($sortOrders) {
                /** @var SortOrder $sortOrder */
                foreach ($sortOrders as $sortOrder) {
                    $collection->addOrder(
                        $sortOrder->getField(),
                        ($sortOrder->getDirection() == SortOrder::SORT_ASC) ? 'ASC' : 'DESC'
                    );
                }
            }
            $collection->setCurPage($criteria->getCurrentPage());
            $collection->setPageSize($criteria->getPageSize());
            $objects = [];
            foreach ($collection as $objectModel) {
                $objects[] = $objectModel;
            }
            $searchResults->setItems($objects);
            return $searchResults;
        }
    }

../Model/ModelSearchResults.php

namespace Custom\Module\Model;

use \Magento\Framework\Api\SearchResults;
use \Custom\Module\Api\Data\ModelSearchResultsInterface;


class ModelSearchResults extends SearchResults implements ModelSearchResultsInterface
{

}

../Controller/Index/Save.php

<?php

namespace Custom\Module\Controller\Index;

use \Magento\Framework\Controller\Result\RawFactory;

class Save extends \Magento\Framework\App\Action\Action
{

    /**
     * Index resultPageFactory
     * @var PageFactory
     */
    private $resultPageFactory;
    /**
     * @var
     */
    private $modelFactory;
    /**
     * @var
     */
    private $modelRepository;


    /**
     * Index constructor.
     * @param \Magento\Framework\App\Action\Context $context
     * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
     * @param \Custom\Module\Model\ModelFactory $modelFactory
     * @param \Custom\Module\Model\ModelRepository $modelRepository
     */
    public function __construct(
        \Magento\Framework\App\Action\Context $context,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory,
        \Custom\Module\Model\ModelFactory $modelFactory,
        \Custom\Module\Model\ModelRepository $modelRepository
) {
        $this->resultPageFactory = $resultPageFactory;
        $this->modelFactory = $modelFactory;
        $this->modelRepository = $modelRepository;
        return parent::__construct($context);


    }

    /**
     * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        $data = [

            "product_id" => 201,
            "customer_name" => "Katrina",
            "customer_email" => "karina@kapoor.com",
            "spouse" => 1
        ];

        $obj = $this->modelFactory->create();
        $this->modelRepository->save($obj->addData($data)); // Service Contract


        //$obj->addData($data)->save(); // Model / Resource Model

        $this->resultFactory->create("raw");
    }
}

../Controller/Index/Getlist.php

<?php

namespace Custom\Module\Controller\Index;

use \Magento\Framework\Controller\Result\RawFactory;

class Getlist extends \Magento\Framework\App\Action\Action
{

    /**
     * Index resultPageFactory
     * @var PageFactory
     */
    private $resultPageFactory;
    /**
     * @var
     */
    private $modelFactory;
    /**
     * @var
     */
    private $modelRepository;
    /**
     * @var
     */
    private $searchCriteriaBuilder;


    /**
     * Index constructor.
     * @param \Magento\Framework\App\Action\Context $context
     * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
     * @param \Custom\Module\Model\ModelRepository $modelRepository
     * @param \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder
     */
    public function __construct(
        \Magento\Framework\App\Action\Context $context,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory,
        \Custom\Module\Model\ModelRepository $modelRepository,
        \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder
) {
        $this->resultPageFactory = $resultPageFactory;
        $this->modelRepository = $modelRepository;
        $this->searchCriteriaBuilder = $searchCriteriaBuilder;
        return parent::__construct($context);
    }

    /**
     * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        $_filter = $this->searchCriteriaBuilder
            ->addFilter("customer_name", "%na%", "like")->create();
        $list = $this->modelRepository->getList($_filter);
        $results = $list->getItems();
        foreach ($results as $result) {
            echo $result->getCustomerName() . "<br>";
        }




        $this->resultFactory->create("raw");
    }
}

../Controller/Index/Getbyid.php

<?php

namespace Custom\Module\Controller\Index;

use \Magento\Framework\Controller\Result\RawFactory;

class Getbyid extends \Magento\Framework\App\Action\Action
{

    /**
     * Index resultPageFactory
     * @var PageFactory
     */
    private $resultPageFactory;
    /**
     * @var
     */
    private $modelRepository;

    /**
     * Index constructor.
     * @param \Magento\Framework\App\Action\Context $context
     * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
     * @param \Custom\Module\Model\ModelRepository $modelRepository
     */
    public function __construct(
        \Magento\Framework\App\Action\Context $context,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory,
        \Custom\Module\Model\ModelRepository $modelRepository

) {
        $this->resultPageFactory = $resultPageFactory;
        $this->modelRepository = $modelRepository;
        return parent::__construct($context);
    }

    public function execute()
    {

        $search = $this->modelRepository->getById(1);
        print_r($search->getData());

        $this->resultFactory->create("raw");
    }
}

../Controller/Index/Deletebyid.php

<?php

namespace Custom\Module\Controller\Index;

use \Magento\Framework\Controller\Result\RawFactory;

class Deletbyid extends \Magento\Framework\App\Action\Action
{

    /**
     * Index resultPageFactory
     * @var PageFactory
     */
    private $resultPageFactory;
    /**
     * @var
     */
    private $modelRepository;

    /**
     * Index constructor.
     * @param \Magento\Framework\App\Action\Context $context
     * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
     * @param \Custom\Module\Model\ModelRepository $modelRepository
     */
    public function __construct(
        \Magento\Framework\App\Action\Context $context,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory,
        \Custom\Module\Model\ModelRepository $modelRepository

) {
        $this->resultPageFactory = $resultPageFactory;
        $this->modelRepository = $modelRepository;
        return parent::__construct($context);
    }

    public function execute()
    {

        $this->modelRepository->deleteById(1);

        $this->resultFactory->create("raw");
    }
}

../Controller/Index/Del.php

<?php

namespace Custom\Module\Controller\Index;

use \Magento\Framework\Controller\Result\RawFactory;

class Del extends \Magento\Framework\App\Action\Action
{

    /**
     * Index resultPageFactory
     * @var PageFactory
     */
    private $resultPageFactory;
    /**
     * @var
     */
    private $modelRepository;

    /**
     * Index constructor.
     * @param \Magento\Framework\App\Action\Context $context
     * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
     * @param \Custom\Module\Model\ModelFactory $modelFactory
     * @param \Custom\Module\Model\ModelRepository $modelRepository
     */
    public function __construct(
        \Magento\Framework\App\Action\Context $context,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory,
        \Custom\Module\Model\ModelFactory $modelFactory,
        \Custom\Module\Model\ModelRepository $modelRepository

) {
        $this->resultPageFactory = $resultPageFactory;
        $this->modelFactory = $modelFactory;
        $this->modelRepository = $modelRepository;
        return parent::__construct($context);
    }

    public function execute()
    {
        $obj = $this->modelFactory->create()->load(2);
         $this->modelRepository->delete($obj);

        $this->resultFactory->create("raw");
    }
}
Асад Улла
источник