¿Cómo implementar un contrato de servicio para un módulo personalizado en Magento 2?

42

Como se ve en este post: Desaprobado guardar y métodos de carga en Extracto del savey loadmétodos están en desuso en el Magento 2 desarrollan rama.

Por lo tanto, la buena práctica ahora es implementar contratos de servicio para tratar con entidades CRUD.

¿Cuál es el proceso paso a paso que debo seguir para implementar contratos de servicio para mis entidades de módulos personalizados?

NB: Sé que puede haber miles de métodos en mis modelos CRUD, solo estoy pidiendo los métodos obvios como se indica aquí: http://devdocs.magento.com/guides/v2.0/extension-dev-guide /service-contracts/design-patterns.html :

  • get
  • save
  • getList
  • delete
  • deleteById
Raphael en Digital Pianism
fuente

Respuestas:

90

Me gustaría dar un poco más de detalle además de la excelente respuesta de @ryanF.

Me gustaría resumir las razones para agregar un repositorio para entidades personalizadas, dar ejemplos de cómo hacerlo y también explicar cómo exponer esos métodos de repositorio como parte de la API web.

Descargo de responsabilidad: solo estoy describiendo un enfoque pragmático sobre cómo hacer esto para módulos de terceros: los equipos principales tienen sus propios estándares que siguen (o no).

En general, el propósito de un repositorio es ocultar la lógica relacionada con el almacenamiento.
Un cliente de un repositorio no debería preocuparse si la entidad devuelta se mantiene en memoria en una matriz, se recupera de una base de datos MySQL, se obtiene de una API remota o de un archivo.
Supongo que el equipo central de Magento hizo esto para poder cambiar o reemplazar el ORM en el futuro. En Magento, el ORM consta actualmente de Modelos, Modelos de recursos y Colecciones.
Si un módulo de terceros usa solo los repositorios, Magento puede cambiar cómo y dónde se almacenan los datos, y el módulo continuará funcionando, a pesar de estos cambios profundos.

Repositories generalmente tienen métodos como findById(), findByName(), put()o remove().
En Magento éstas comúnmente se les llama getbyId(), save()y delete(), ni siquiera fingir que están haciendo otra cosa que las operaciones CRUD DB.

Los métodos de repositorio de Magento 2 pueden exponerse fácilmente como recursos de API, lo que los hace valiosos para integraciones con sistemas de terceros o instancias sin cabeza de Magento.

"¿Debo agregar un repositorio para mi entidad personalizada?".

Como siempre, la respuesta es

"Depende".

Para resumir, si otros módulos utilizarán sus entidades, entonces sí, probablemente desee agregar un repositorio.

Aquí hay otro factor que cuenta: en Magento 2, los repositorios pueden exponerse fácilmente como recursos de API web, es decir, REST y SOAP.

Si eso es interesante para usted debido a integraciones de sistemas de terceros o una configuración de Magento sin cabeza, entonces nuevamente, sí, es probable que desee agregar un repositorio para su entidad.

¿Cómo agrego un repositorio para mi entidad personalizada?

Supongamos que desea exponer su entidad como parte de la API REST. Si eso no es cierto, puede omitir la próxima parte sobre la creación de interfaces e ir directamente a "Crear el repositorio y la implementación del modelo de datos" a continuación.

Crear el repositorio y las interfaces del modelo de datos.

Crea las carpetas Api/Data/en tu módulo. Esto es solo una convención, podría usar una ubicación diferente, pero no debería.
El repositorio va a la Api/carpeta. El Data/subdirectorio es para más tarde.

En Api/, cree una interfaz PHP con los métodos que desea exponer. Según las convenciones de Magento 2, todos los nombres de interfaz terminan en el sufijo Interface.
Por ejemplo, para una Hamburgerentidad, crearía la interfaz Api/HamburgerRepositoryInterface.

Crear la interfaz del repositorio

Los repositorios de Magento 2 son parte de la lógica de dominio de un módulo. Eso significa que no hay un conjunto fijo de métodos que un repositorio tenga que implementar.
Depende completamente del propósito del módulo.

Sin embargo, en la práctica, todos los repositorios son bastante similares. Son envoltorios para la funcionalidad CRUD.
La mayoría tienen los métodos getById, save, deletey getList.
Puede haber más, por ejemplo, CustomerRepositorytiene un método get, que capta a un cliente por correo electrónico, mediante el cual getByIdse utiliza para recuperar un cliente por ID de entidad.

Aquí hay un ejemplo de interfaz de repositorio para una entidad de hamburguesas:

<?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! ¡Aquí están los sumideros!
Aquí hay algunas trampas que son difíciles de depurar si se equivocan:

  1. ¡NO use tipos de argumentos escalares PHP7 o tipos de retorno si desea conectar esto a la API REST!
  2. ¡Agregue anotaciones PHPDoc para todos los argumentos y el tipo de retorno a todos los métodos!
  3. Utilizar plenamente calificados nombres genéricos en el bloque PHPDoc!

Magento Framework analiza las anotaciones para determinar cómo convertir datos hacia y desde JSON o XML. ¡Las importaciones de clase (es decir, usedeclaraciones) no se aplican!

Cada método debe tener una anotación con cualquier tipo de argumento y el tipo de retorno. Incluso si un método no toma argumentos y no devuelve nada, debe tener la anotación:

/**
 * @return void
 */

Tipos escalares ( string, int, floaty bool) también tienen que ser especificados, tanto para los argumentos y como valor de retorno.

Tenga en cuenta que en el ejemplo anterior, las anotaciones para los métodos que devuelven objetos también se especifican como interfaces.
Las interfaces de tipo de retorno están todas en el Api\Dataespacio de nombres / directorio.
Esto es para indicar que no contienen ninguna lógica empresarial. Son simplemente bolsas de datos.
Tenemos que crear estas interfaces a continuación.

Crea la interfaz DTO

Creo que Magento llama a estas interfaces "modelos de datos", un nombre que no me gusta en absoluto.
Este tipo de clase se conoce comúnmente como objeto de transferencia de datos o DTO .
Estas clases de DTO solo tienen captadores y establecedores para todas sus propiedades.

La razón por la que prefiero usar DTO sobre el modelo de datos es que es menos fácil confundirlo con los modelos de datos ORM, los modelos de recursos o los modelos de vista ... demasiadas cosas ya son modelos en Magento.

Las mismas restricciones con respecto a la tipificación PHP7 que se aplican a los repositorios también se aplican a los DTO.
Además, cada método debe tener una anotación con todos los tipos de argumentos y el 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);
}

Si un método recupera o devuelve una matriz, el tipo de los elementos de la matriz debe especificarse en la anotación PHPDoc, seguido de un corchete de apertura y cierre [].
Esto es cierto tanto para los valores escalares (p int[]. Ej. ) Como para los objetos (p IngredientInterface[]. Ej .).

Tenga en cuenta que estoy usando un Api\Data\IngredientInterfacecomo ejemplo para un método que devuelve una matriz de objetos, no agregaré el código de los ingredientes a esta publicación difícil.

ExtensibleDataInterface?

En el ejemplo anterior, se HamburgerInterfaceextiende el ExtensibleDataInterface.
Técnicamente, esto solo es necesario si desea que otros módulos puedan agregar atributos a su entidad.
Si es así, también necesita agregar otro par getter / setter, por convención llamado getExtensionAttributes()y setExtensionAttributes().

¡El nombre del tipo de retorno de este método es muy importante!

El marco de trabajo de Magento 2 generará la interfaz, la implementación y la fábrica para la implementación si los nombra correctamente. Sin embargo, los detalles de estas mecánicas están fuera del alcance de esta publicación.
Solo sepa, si se llama a la interfaz del objeto que desea hacer extensible \VinaiKopp\Kitchen\Api\Data\HamburgerInterface, entonces el tipo de atributos de extensión debe ser \VinaiKopp\Kitchen\Api\Data\HamburgerExtensionInterface. Por lo tanto, la palabra Extensiondebe insertarse después del nombre de la entidad, justo antes del Interfacesufijo.

Si no desea que su entidad sea extensible, entonces la interfaz DTO no tiene que extender ninguna otra interfaz, y los métodos getExtensionAttributes()y setExtensionAttributes()pueden omitirse.

Suficiente sobre la interfaz DTO por ahora, es hora de volver a la interfaz del repositorio.

El tipo de retorno getList () SearchResults

El método del repositorio getListdevuelve otro tipo, es decir, una SearchResultsInterfaceinstancia.

El método getListpodría, por supuesto, devolver una matriz de objetos que coincida con la especificada SearchCriteria, pero devolver una SearchResultsinstancia permite agregar algunos metadatos útiles a los valores devueltos.

Puede ver cómo funciona eso a continuación en la getList()implementación del método de repositorio .

Aquí está la interfaz de resultados de búsqueda de hamburguesas de ejemplo:

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

Todo lo que hace esta interfaz es que anula los tipos de los dos métodos getItems()y setItems()de la interfaz principal.

Resumen de interfaces

Ahora tenemos las siguientes interfaces:

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

El repositorio no extiende nada,
el HamburgerInterfaceextiende el \Magento\Framework\Api\ExtensibleDataInterface,
y el HamburgerSearchResultInterfaceextiende el \Magento\Framework\Api\SearchResultsInterface.

Crear el repositorio y las implementaciones del modelo de datos.

El siguiente paso es crear las implementaciones de las tres interfaces.

El repositorio

En esencia, el repositorio usa el ORM para hacer su trabajo.

Las getById(), save() y los delete()métodos son bastante sencillo.
El HamburgerFactoryse inyecta en el repositorio como un argumento constructor, como se puede ver un poco más abajo.

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

Ahora a la parte más interesante de un repositorio, el getList()método.
El getList()método tiene que traducir las SerachCriteriacondiciones en llamadas a métodos en la colección.

La parte difícil de eso es obtener las condiciones ANDy ORpara los filtros correctos, especialmente porque la sintaxis para establecer las condiciones en la colección es diferente dependiendo de si se trata de una EAV o una entidad de tabla plana.

En la mayoría de los casos, getList()se puede implementar como se ilustra en el siguiente ejemplo.

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

Los filtros dentro de a FilterGroupdeben combinarse utilizando un operador OR .
Grupos de filtros separados se combinan utilizando el operador lógico AND .

Uf
Este fue el mayor trabajo. Las otras implementaciones de interfaz son más simples.

El DTO

Magento originalmente tenía la intención de que los desarrolladores implementaran el DTO como clases separadas, distintas del modelo de entidad.

Sin embargo, el equipo central solo hizo esto para el módulo del cliente ( \Magento\Customer\Api\Data\CustomerInterfacese implementa por \Magento\Customer\Model\Data\Customer, no \Magento\Customer\Model\Customer).
En todos los demás casos, el modelo de entidad implementa la interfaz DTO (por ejemplo, \Magento\Catalog\Api\Data\ProductInterfaceimplementada por \Magento\Catalog\Model\Product).

Le pregunté a los miembros del equipo central sobre esto en las conferencias, pero no obtuve una respuesta clara sobre lo que se considera una buena práctica.
Mi impresión es que esta recomendación ha sido abandonada. Sin embargo, sería bueno obtener una declaración oficial sobre esto.

Por ahora he tomado la decisión pragmática de utilizar el modelo como la implementación de la interfaz DTO. Si cree que es más limpio usar un modelo de datos separado, no dude en hacerlo. Ambos enfoques funcionan bien en la práctica.

Si la interfaz DTO extiende el Magento\Framework\Api\ExtensibleDataInterface, el modelo tiene que extender Magento\Framework\Model\AbstractExtensibleModel.
Si no le importa la extensibilidad, el modelo simplemente puede continuar extendiendo la clase base del modelo ORM Magento\Framework\Model\AbstractModel.

Como el ejemplo HamburgerInterfaceamplía el ExtensibleDataInterfacemodelo de hamburguesa AbstractExtensibleModel, como se puede ver aquí:

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

Extraer los nombres de las propiedades en constantes permite mantenerlos en un solo lugar. Pueden ser utilizados por el par getter / setter y también por el script de configuración que crea la tabla de la base de datos. De lo contrario, no hay beneficio en extraerlos en constantes.

El resultado de búsqueda

La SearchResultsInterfacees la más simple de las tres interfaces para implementar, ya que puede heredar toda su funcionalidad de una clase de marco.

<?php

namespace VinaiKopp\Kitchen\Model;

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

class HamburgerSearchResult extends SearchResults implements HamburgerSearchResultInterface
{

}

Configurar las preferencias de ObjectManager

Aunque las implementaciones están completas, todavía no podemos usar las interfaces como dependencias de otras clases, ya que el administrador de objetos de Magento Framework no sabe qué implementaciones usar. Necesitamos agregar una etc/di.xmlconfiguración con las preferencias.

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

¿Cómo se puede exponer el repositorio como un recurso API?

Esta parte es realmente simple, es la recompensa por pasar por todo el trabajo creando las interfaces, las implementaciones y conectándolas juntas.

Todo lo que necesitamos hacer es crear un etc/webapi.xmlarchivo.

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

Tenga en cuenta que esta configuración no solo permite el uso del repositorio como puntos finales REST, sino que también expone los métodos como parte de la API SOAP.

En el primer ejemplo de ruta, <route method="GET" url="/V1/vinaikopp_hamburgers/:id">el marcador de posición :idtiene que coincidir con el nombre del argumento al método de mapeado, public function getById($id).
Los dos nombres tienen que coincidir, por ejemplo /V1/vinaikopp_hamburgers/:hamburgerIdno funcionaría, ya que el nombre de la variable del argumento del método es $id.

Para este ejemplo, configuré la accesibilidad en <resource ref="anonymous"/>. ¡Esto significa que el recurso está expuesto públicamente sin ninguna restricción!
Para hacer que un recurso solo esté disponible para un cliente conectado, use <resource ref="self"/>. En este caso, la palabra especial meen la URL del punto final del recurso se utilizará para rellenar una variable de argumento $idcon el ID del cliente actualmente conectado.
Echa un vistazo al Cliente de Magento etc/webapi.xmly CustomerRepositoryInterfacesi lo necesitas.

Finalmente, <resources>también se puede usar para restringir el acceso a un recurso a una cuenta de usuario administrador. Para hacer esto, configure la <resource>referencia a un identificador definido en un etc/acl.xmlarchivo.
Por ejemplo, <resource ref="Magento_Customer::manage"/>restringiría el acceso a cualquier cuenta de administrador que tenga el privilegio de administrar clientes.

Un ejemplo de consulta API usando curl podría verse así:

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

Nota: escribir esto comenzó como una respuesta a https://github.com/astorm/pestle/issues/195
Echa un vistazo a la maja , compra Commercebug y conviértete en un patreon de @alanstorm

Vinaí
fuente
1
Gracias por esta gran respuesta. Lo siento, tal vez me estoy perdiendo algo, pero ¿cuál es el punto de tener una interfaz limpia para una entidad cuando al final tiene que extenderse desde AbstractModel que tiene el método setData, lo que significa que puede agregar cualquier cosa al objeto independientemente de la interfaz?
LDusan
Una clase puede implementar cualquier cantidad de interfaces y también agregar métodos adicionales. Lo importante es que cualquier otra clase solo depende de los métodos de interfaz y, por lo tanto, no conoce ninguno de los otros. Eso hace que los detalles de implementación de los métodos que no son de interfaz se puedan cambiar en cualquier momento sin romper las clases externas. Esa es la idea detrás de la inversión de dependencia. Tanto la clase como los clientes dependen de la interfaz y no conocen los detalles de implementación. ¿Eso aclara?
Vinai
Gracias por la respuesta, entiendo lo que quieres decir. La cuestión es que setData es un método público, por lo tanto, no estoy seguro de si puede considerarse como un detalle de implementación. Si está destinado a ser utilizado como método público, ¿cómo podemos estar seguros de que no romperá nada externo cuando se cambie?
LDusan
3
Me disculpo. Lo que estás describiendo es un punto de vista común. La mecánica de las dependencias no es intuitiva y, dado que PHP permite la invocación de métodos que no son parte de la interfaz dependiente, y además, dado que no es necesario compilarla, hace que la dependencia funcione aún más borrosa y difícil de ver con claridad. . Eso también se puede observar en el núcleo de Magento 2, donde hay muchos lugares donde se llaman métodos de implementación que no son parte de la interfaz dependiente. Estos sirven como malos ejemplos y hacen que sea aún más difícil obtener una comprensión clara y sólida.
Vinai
1
Continuemos esta discusión en el chat .
Vinai
35

@Raphael en Pianismo digital:

Consulte la siguiente estructura de módulo de muestra:

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. Crear interfaz de repositorio (Contrato de servicio)
    Namespace/Custom/Api/CustomRepositoryInterface.php: http://codepad.org/WognSKnH

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

  3. Crear interfaz personalizada (contenedor de datos)
    Namespace/Custom/Api/Data/CustomInterface.php: http://codepad.org/Ze53eT4o

  4. Crear repositorio personalizado (repositorio de concreto)
    Namespace/Custom/Model/CustomRepository.php: http://codepad.org/KNt5QAGZ
    Aquí es donde sucede la "magia". A través del constructor DI, pasa la fábrica de modelo / colección de recursos para su módulo personalizado; Con respecto al método guardar CRUD en este Repositorio, debido a su CustomRepositoryInterface, debe pasar un parámetro de CustomInterface. El di.xml de su módulo tiene preferencia para reemplazar una interfaz de este tipo con un modelo de entidad. El modelo de entidad se pasa al Modelo de recursos y se guarda.

  5. Establecer preferencia en
    Namespace/Custom/etc/di.xml: http://codepad.org/KmcoOUeV

  6. Modelo de entidad que implementa la interfaz personalizada (contenedor de datos)
    Namespace/Custom/Model/Custom.php: http://codepad.org/xQiBU7p7 .

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

Algunas cosas a tener en cuenta:

  • ¡¡¡Renuncia!!! He utilizado "espacio de nombres" en lugar de su nombre del proveedor de encargo, nombre de la agencia, etc ... sea cual sea el nombre que se utiliza para agrupar los módulos juntos ... el uso real de "nombre" es completamente no válida en PHP ... así conocimientos que hice esto por conveniencia, y que no creo que esto funcione, ni lo sugiero de ninguna manera.

  • @ Ryan Street me enseñó esto ... así que no quiero tomar todo el crédito

  • Cambie claramente la implementación del repositorio para satisfacer sus necesidades.

  • Implementa la interacción con sus modelos de entidad personalizados / modelos de recursos / colecciones en el Repositorio concreto ...

  • Sé que no abordé todos los métodos que enumeró en su pregunta, pero este es un gran comienzo y debería cerrar la brecha entre los documentos y la implementación real.

ryanF
fuente
Ryan, ¿son obligatorios los métodos mencionados en los Contratos de servicio para cualquier API de jabón personalizada que creamos, es decir, guardar (), eliminar (), etc.?
Sushivam
¿Podría darme una idea de cómo crear una API de jabón personalizada en magento 2?
Sushivam
@SachinS Desafortunadamente, no tengo ninguna idea que ofrecer sobre SOAP. No lo he investigado aún, ni lo he implementado todavía. Lo mejor que podría sugerir sería abrir una nueva pregunta aquí con respecto a eso. Yo diría que también revise los documentos, pero lamentablemente no siempre es el mejor curso de acción (pueden faltar). Siempre puede echar un vistazo a la base de código central o una extensión de terceros, y ver si hay alguna idea allí. ¡Buena suerte! Si encuentra su respuesta, sería bueno agregar el enlace aquí. Gracias
ryanF
Gracias por la respuesta @ryan, de todos modos implementé mi módulo usando REST, ya que es ligero comparado con SOAP ... Si implemento lo mismo en SOAP, lo publicaré
Sushivam
3
@ryanF Gracias por esta respuesta muy útil. Sé que no se supone que sea un código de trabajo para copiar / pegar, pero aquí hay algunos errores tipográficos para el beneficio de otros que lo siguen. En el repositorio, CustomSearchResultsInterfaceFactory debería ser CustomSearchResultsFactory. $ searchResults-> setCriteria debe ser $ searchResults-> setSearchCriteria. $ Aduanas [] en el foreach debe ser $ costumbres []. Creo que eso es todo.
tetranz
3

archivos completos de uso de contratos de servicio

Personalizado / Módulo / registro.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
fuente