Como implementar o contrato de serviço para um módulo personalizado no Magento 2?

42

Como visto neste post: Métodos obsoletos de salvar e carregar no Modelo Abstrato, os métodos savee loadestão obsoletos no ramo de desenvolvimento do Magento 2.

Portanto, a boa prática agora é implementar contratos de serviço para lidar com entidades CRUD.

Qual é o processo passo a passo que preciso seguir para implementar contratos de serviço para minhas entidades de módulo personalizadas?

NB: Eu sei que pode haver milhares de métodos nos meus modelos CRUD, só estou pedindo os métodos óbvios, conforme indicado aqui: http://devdocs.magento.com/guides/v2.0/extension-dev-guide /service-contracts/design-patterns.html :

  • get
  • save
  • getList
  • delete
  • deleteById
Raphael na Digital Pianism
fonte

Respostas:

90

Gostaria de dar um pouco mais de detalhes, além da excelente resposta do @ryanF.

Gostaria de resumir os motivos para adicionar um repositório para entidades personalizadas, dar exemplos de como fazer isso e também explicar como expor esses métodos de repositório como parte da API da Web.

Isenção de responsabilidade: estou descrevendo apenas uma abordagem pragmática de como fazer isso para módulos de terceiros - as equipes principais têm seus próprios padrões que seguem (ou não).

Em geral, o objetivo de um repositório é ocultar a lógica relacionada ao armazenamento.
Um cliente de um repositório não deve se importar se a entidade retornada é mantida na memória em uma matriz, é recuperada de um banco de dados MySQL, obtida de uma API remota ou de um arquivo.
Suponho que a equipe principal do Magento fez isso para que eles possam alterar ou substituir o ORM no futuro. No Magento, o ORM atualmente consiste nos modelos, modelos de recursos e coleções.
Se um módulo de terceiros usar apenas os repositórios, o Magento poderá alterar como e onde os dados são armazenados, e o módulo continuará funcionando, apesar dessas mudanças profundas.

Repositórios têm geralmente métodos como findById(), findByName(), put()ou remove().
Em Magento estes comumente são chamados getbyId(), save()e delete(), nem mesmo fingir que estão fazendo qualquer outra coisa, mas as operações CRUD DB.

Os métodos de repositório Magento 2 podem ser facilmente expostos como recursos de API, tornando-os valiosos para integrações com sistemas de terceiros ou instâncias Magento sem cabeça.

"Devo adicionar um repositório para minha entidade personalizada?"

Como sempre, a resposta é

"Depende".

Para resumir uma longa história, se suas entidades serão usadas por outros módulos, sim, você provavelmente deseja adicionar um repositório.

Há outro fator que conta aqui: no Magento 2, os repositórios podem ser facilmente expostos como recursos da API da Web - que são REST e SOAP.

Se isso for interessante para você por causa de integrações de sistemas de terceiros ou uma configuração sem cabeçalho do Magento, novamente, sim, você provavelmente deseja adicionar um repositório para sua entidade.

Como adiciono um repositório à minha entidade personalizada?

Vamos supor que você deseja expor sua entidade como parte da API REST. Se isso não for verdade, você pode pular a próxima parte da criação das interfaces e ir direto para "Criar a implementação do repositório e do modelo de dados" abaixo.

Crie as interfaces de repositório e modelo de dados

Crie as pastas Api/Data/no seu módulo. Isso é apenas convenção, você pode usar um local diferente, mas não deve.
O repositório entra na Api/pasta. O Data/subdiretório é para mais tarde.

Em Api/, crie uma interface PHP com os métodos que você deseja expor. De acordo com as convenções do Magento 2, todos os nomes de interface terminam no sufixo Interface.
Por exemplo, para uma Hamburgerentidade, eu criaria a interface Api/HamburgerRepositoryInterface.

Crie a interface do repositório

Os repositórios Magento 2 fazem parte da lógica do domínio de um módulo. Isso significa que não há um conjunto fixo de métodos que um repositório precisa implementar.
Depende inteiramente da finalidade do módulo.

No entanto, na prática, todos os repositórios são bastante semelhantes. Eles são invólucros para a funcionalidade CRUD.
A maioria tem os métodos getById, save, deletee getList.
Pode haver mais, por exemplo, CustomerRepositorytem um método get, que busca um cliente por email, pelo qual getByIdé usado para recuperar um cliente por ID da entidade.

Aqui está um exemplo de interface de repositório para uma entidade de hambúrguer:

<?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);

}

Importante! Aqui estão timesinks!
Existem algumas dicas aqui que são difíceis de depurar se você errar:

  1. NÃO use tipos de argumentos escalares do PHP7 ou tipos de retorno, se desejar conectar isso à API REST!
  2. Adicione anotações PHPDoc para todos os argumentos e o tipo de retorno para todos os métodos!
  3. Use nomes de classe totalmente qualificados no bloco PHPDoc!

As anotações são analisadas pelo Magento Framework para determinar como converter dados de e para JSON ou XML. As importações de classe (ou seja, useinstruções) não são aplicadas!

Todo método precisa ter uma anotação com qualquer tipo de argumento e o tipo de retorno. Mesmo que um método não use argumentos e não retorne nada, ele precisa ter a anotação:

/**
 * @return void
 */

Tipos escalares ( string, int, floate bool) também tem que ser especificado, tanto para os argumentos e como um valor de retorno.

Observe que no exemplo acima, as anotações para métodos que retornam objetos também são especificadas como interfaces.
As interfaces do tipo de retorno estão todas no Api\Datanamespace / diretório.
Isso é para indicar que eles não contêm nenhuma lógica comercial. Eles são simplesmente sacos de dados.
Temos que criar essas interfaces a seguir.

Crie a interface DTO

Eu acho que o Magento chama essas interfaces de "modelos de dados", um nome que eu não gosto.
Esse tipo de classe é comumente conhecido como um objeto de transferência de dados ou DTO .
Essas classes DTO têm apenas getters e setters para todas as suas propriedades.

A razão pela qual prefiro usar o DTO sobre o modelo de dados é que é menos fácil confundir com os modelos de dados ORM, modelos de recursos ou modelos de exibição ... muitas coisas já são modelos no Magento.

As mesmas restrições em relação à digitação do PHP7 que se aplicam aos repositórios também se aplicam aos DTOs.
Além disso, todo método deve ter uma anotação com todos os tipos de argumentos e o tipo de retorno.

<?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);
}

Se um método recuperar ou retornar uma matriz, o tipo dos itens na matriz deverá ser especificado na anotação PHPDoc, seguido por um colchete de abertura e fechamento [].
Isso é verdade tanto para valores escalares (por exemplo int[]) quanto para objetos (por exemplo IngredientInterface[]).

Observe que estou usando um Api\Data\IngredientInterfacecomo exemplo para um método retornando uma matriz de objetos, não adicionarei o código dos ingredientes a este post difícil.

ExtensibleDataInterface?

No exemplo acima, o HamburgerInterfaceestende o ExtensibleDataInterface.
Tecnicamente, isso é necessário apenas se você desejar que outros módulos possam adicionar atributos à sua entidade.
Nesse caso, você também precisará adicionar outro par getter / setter, por convenção chamada getExtensionAttributes()e setExtensionAttributes().

A nomeação do tipo de retorno desse método é muito importante!

A estrutura do Magento 2 irá gerar a interface, a implementação e a fábrica para a implementação, se você der o nome correto. Os detalhes dessas mecânicas estão fora do escopo deste post.
Apenas saiba que, se a interface do objeto que você deseja tornar extensível for chamada \VinaiKopp\Kitchen\Api\Data\HamburgerInterface, o tipo de atributo de extensão deverá ser \VinaiKopp\Kitchen\Api\Data\HamburgerExtensionInterface. Portanto, a palavra Extensiondeve ser inserida após o nome da entidade, logo antes do Interfacesufixo.

Se você não deseja que sua entidade seja extensível, a interface do DTO não precisa estender nenhuma outra interface e os métodos getExtensionAttributes()e setExtensionAttributes()podem ser omitidos.

Chega de informações sobre a interface do DTO, por enquanto, hora de retornar à interface do repositório.

O tipo de retorno getList () SearchResults

O método do repositório getListretorna ainda outro tipo, ou seja, uma SearchResultsInterfaceinstância.

getListObviamente, o método poderia retornar apenas uma matriz de objetos que correspondesse ao especificado SearchCriteria, mas o retorno de uma SearchResultsinstância permite adicionar alguns metadados úteis aos valores retornados.

Você pode ver como isso funciona abaixo na getList()implementação do método do repositório .

Aqui está o exemplo de interface de resultado de pesquisa de hambúrguer:

<?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);
}

Tudo o que essa interface faz é substituir os tipos dos dois métodos getItems()e setItems()da interface pai.

Resumo das interfaces

Agora temos as seguintes interfaces:

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

O repositório estende nada,
o HamburgerInterfaceestende a \Magento\Framework\Api\ExtensibleDataInterface,
ea HamburgerSearchResultInterfaceestende a \Magento\Framework\Api\SearchResultsInterface.

Crie as implementações de repositório e modelo de dados

O próximo passo é criar as implementações das três interfaces.

O Repositório

Em essência, o repositório usa o ORM para fazer seu trabalho.

Os métodos getById(), save() e delete()são bastante diretos.
O HamburgerFactoryé injetado no repositório como um argumento construtor, como pode ser visto um pouco mais abaixo.

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);
}

Agora, a parte mais interessante de um repositório, o getList()método
O getList()método precisa converter as SerachCriteriacondições em chamadas de método na coleção.

A parte complicada disso é acertar as condições ANDe ORpara os filtros, especialmente porque a sintaxe para definir as condições na coleção é diferente dependendo se é um EAV ou uma entidade de tabela plana.

Na maioria dos casos, getList()pode ser implementado como ilustrado no exemplo abaixo.

<?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;
    }
}

Os filtros em um FilterGroupdevem ser combinados usando um operador OU .
Grupos de filtros separados são combinados usando o operador AND lógico .

Ufa
Este foi o maior trabalho. As outras implementações de interface são mais simples.

O DTO

O Magento originalmente pretendia que os desenvolvedores implementassem o DTO como classes separadas, distintas do modelo de entidade.

A equipe principal só fez isso para o módulo do cliente ( \Magento\Customer\Api\Data\CustomerInterfaceé implementado por \Magento\Customer\Model\Data\Customer, não \Magento\Customer\Model\Customer).
Em todos os outros casos, o modelo de entidade implementa a interface DTO (por exemplo, \Magento\Catalog\Api\Data\ProductInterfaceé implementada por \Magento\Catalog\Model\Product).

Perguntei aos membros da equipe principal sobre isso em conferências, mas não recebi uma resposta clara sobre o que deve ser considerado uma boa prática.
Minha impressão é que essa recomendação foi abandonada. Seria bom obter uma declaração oficial sobre isso.

Por enquanto, tomei a decisão pragmática de usar o modelo como a implementação da interface DTO. Se você achar que é mais limpo usar um modelo de dados separado, sinta-se à vontade para fazê-lo. Ambas as abordagens funcionam bem na prática.

Se a interface do DTO estender o Magento\Framework\Api\ExtensibleDataInterface, o modelo deverá ser estendido Magento\Framework\Model\AbstractExtensibleModel.
Se você não se importa com a extensibilidade, o modelo pode simplesmente continuar a estender a classe base do modelo ORM Magento\Framework\Model\AbstractModel.

Como o exemplo HamburgerInterfaceestende o ExtensibleDataInterfacemodelo de hambúrguer, estende o AbstractExtensibleModel, como pode ser visto aqui:

<?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);
    }
}

Extrair os nomes das propriedades em constantes permite mantê-los em um só lugar. Eles podem ser usados ​​pelo par getter / setter e também pelo script de instalação que cria a tabela do banco de dados. Caso contrário, não há benefício em extraí-los em constantes.

O SearchResult

A SearchResultsInterfaceé a mais simples das três interfaces para implementar, pois pode herdar toda a sua funcionalidade de uma classe de estrutura.

<?php

namespace VinaiKopp\Kitchen\Model;

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

class HamburgerSearchResult extends SearchResults implements HamburgerSearchResultInterface
{

}

Configure as preferências do ObjectManager

Embora as implementações estejam completas, ainda não podemos usar as interfaces como dependências de outras classes, já que o gerenciador de objetos do Magento Framework não sabe quais implementações usar. Precisamos adicionar uma etc/di.xmlconfiguração para com as preferências.

<?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>

Como o repositório pode ser exposto como um recurso de API?

Esta parte é realmente simples, é a recompensa por realizar todo o trabalho criando as interfaces, as implementações e conectando-as.

Tudo o que precisamos fazer é criar um etc/webapi.xmlarquivo.

<?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>

Observe que essa configuração não apenas habilita o uso do repositório como pontos de extremidade REST, mas também expõe os métodos como parte da API SOAP.

No primeiro exemplo de rota, <route method="GET" url="/V1/vinaikopp_hamburgers/:id">o espaço reservado :iddeve corresponder o nome do argumento ao método mapeado public function getById($id).
Os dois nomes têm que combinar, por exemplo, /V1/vinaikopp_hamburgers/:hamburgerIdnão iria funcionar, pois o método nome da variável argumento é $id.

Para este exemplo, eu configurei a acessibilidade para <resource ref="anonymous"/>. Isso significa que o recurso é exposto publicamente sem nenhuma restrição!
Para disponibilizar um recurso apenas para um cliente conectado, use <resource ref="self"/>. Nesse caso, a palavra especial meno URL do terminal de recurso será usada para preencher uma variável de argumento $idcom o ID do cliente conectado no momento.
Dê uma olhada no Cliente Magento etc/webapi.xmle CustomerRepositoryInterfacese você precisar.

Por fim, <resources>também pode ser usado para restringir o acesso a um recurso a uma conta de usuário administrador. Para fazer isso, defina a <resource>ref como um identificador definido em um etc/acl.xmlarquivo.
Por exemplo, <resource ref="Magento_Customer::manage"/>restringiria o acesso a qualquer conta de administrador que tenha o privilégio de gerenciar clientes.

Um exemplo de consulta da API usando curl pode ser assim:

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

Nota: escrevendo isso começou como uma resposta para https://github.com/astorm/pestle/issues/195
Confira o pilão , compre o Commercebug e torne-se um patreon de @alanstorm

Vinai
fonte
1
Obrigado por esta ótima resposta. Desculpe, talvez esteja faltando alguma coisa, mas qual é o sentido de ter uma interface limpa para uma entidade quando, no final, ela deve se estender de AbstractModel que possui o método setData, o que significa que você pode adicionar qualquer coisa ao objeto, independentemente da interface?
LDusan
Uma classe pode implementar qualquer número de interfaces e adicionar métodos adicionais também. O importante é que qualquer outra classe depende apenas dos métodos da interface e, portanto, não conhece nenhuma das outras. Isso faz com que os métodos de interface não sejam detalhados, que podem ser alterados a qualquer momento sem interromper as classes externas. Essa é a ideia por trás da inversão de dependência. A classe e qualquer cliente dependem da interface e não conhecem os detalhes da implementação. Isso esclarece?
Vinai
Obrigado pela resposta, entendo o que você quer dizer. O fato é que setData é um método público, portanto, não tenho certeza se pode ser considerado um detalhe de implementação. Se é para ser usado como método público, como podemos ter certeza de que não quebrará nada externo quando alterado?
LDusan
3
Peço desculpas. O que você está descrevendo é um ponto de vista comum. A mecânica das dependências não é intuitiva e, como o PHP permite a chamada de métodos que não fazem parte do dependente da interface, e também como não precisa ser compilado, torna o modo como as dependências funcionam ainda mais embaçadas e difíceis de ver com clareza. . Isso também pode ser observado no núcleo do Magento 2, onde há muitos lugares onde os métodos de implementação são chamados que não fazem parte do dependente da interface. Estes servem como maus exemplos e tornam ainda mais difícil obter uma compreensão clara e sólida.
Vinai
35

@Raphael no Digital Pianism:

Consulte a seguinte estrutura de módulo de amostra:

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. Criar interface de repositório (Contrato de Serviço)
    Namespace/Custom/Api/CustomRepositoryInterface.php: http://codepad.org/WognSKnH

  2. Crie SearchResultsInterface
    Namespace/Custom/Api/Data/CustomSearchResultsInterface.php: http://codepad.org/zcbi8X4Z

  3. Crie CustomInterface (Data Container)
    Namespace/Custom/Api/Data/CustomInterface.php: http://codepad.org/Ze53eT4o

  4. Criar CustomRepository (Concrete Repository)
    Namespace/Custom/Model/CustomRepository.php: http://codepad.org/KNt5QAGZ
    É aqui que a "mágica" acontece. Por meio do construtor DI, você passa no modelo de recursos / fábrica de coleta para seu módulo customizado; Em relação ao método save CRUD neste Repositório, devido ao seu CustomRepositoryInterface, você deve passar um parâmetro de CustomInterface. O di.xml do seu módulo prefere substituir uma interface desse tipo por um modelo de entidade. O modelo de entidade é passado para o Modelo de Recursos e é salvo.

  5. Defina a preferência em
    Namespace/Custom/etc/di.xml: http://codepad.org/KmcoOUeV

  6. Modelo de entidade implementando a Interface Personalizada (Data Container)
    Namespace/Custom/Model/Custom.php: http://codepad.org/xQiBU7p7 .

  7. Modelo de recurso
    Namespace/Custom/Model/ResourceModel/Custom.php: http://codepad.org/IOsxm9qW

Algumas coisas a serem observadas:

  • Aviso Legal!!! Eu usei "Namespace" no lugar de seu nome de fornecedor personalizado, nome da agência, etc ... qualquer nome que você use para agrupar seus módulos ... o uso real de "Namespace" não é totalmente válido em Php ... então, saiba que fiz isso por conveniência e que não acho que isso funcione, nem sugiro de forma alguma.

  • @Ryan Street me ensinou isso ... então eu não quero levar todo o crédito

  • Mude claramente a implementação do Repositório para atender às suas necessidades

  • Você implementa a interação com seus modelos de entidade / modelos de recursos / coleções personalizados no Repositório concreto ...

  • Sei que não resolvi todos os métodos listados na sua pergunta, mas este é um ótimo começo e deve preencher a lacuna entre os documentos e a implementação real.

ryanF
fonte
Ryan, os métodos mencionados nos Contratos de serviço são obrigatórios para qualquer API de sabão personalizada que criamos, ou seja, save (), delete () etc?
Sushivam 8/08
Você poderia me dar uma idéia de como criar uma API de sabão personalizada no magento 2?
Sushivam 8/08
@SachinS Infelizmente, não tenho nenhum insight a oferecer em relação ao SOAP. Ainda não examinei nem o implementei. O melhor que eu poderia sugerir seria abrir uma nova pergunta aqui sobre isso. Eu diria que verifique os documentos também, mas infelizmente esse nem sempre é o melhor curso de ação (eles podem estar faltando). Você sempre pode dar uma olhada na base de código principal ou em uma extensão de terceiros e verificar se há algum insight. Boa sorte! Se você encontrar sua resposta, pode ser bom adicionar o link aqui. Graças
ryanF
Obrigado pela @ Ryan resposta, de qualquer maneira eu implementou meu módulo usando REST, desde a sua luz ponderada em comparação com SABÃO ... Se eu implementar o mesmo em SOAP, doente postá-lo
Sushivam
3
@ryanF Obrigado por esta resposta muito útil. Eu sei que não é para copiar / colar código de trabalho, mas aqui estão alguns erros de digitação para o benefício de outros que estão seguindo. No repositório, CustomSearchResultsInterfaceFactory deve ser CustomSearchResultsFactory. $ searchResults-> setCriteria deve ser $ searchResults-> setSearchCriteria. $ Alfândega [] no foreach deve ser $ alfândega []. Eu acho que é sobre isso.
Tetranz
3

arquivos completos de uso de contratos de serviço

Custom / Module / 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" => "[email protected]",
            "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");
    }
}
Asad Ullah
fonte