Jak wdrożyć umowę serwisową dla niestandardowego modułu w Magento 2?

42

Jak widać w tym poście: Nieaktualne metody zapisywania i ładowania w abstrakcyjny modelsave i loadmetod są przestarzałe w Magento 2 rozwinąć gałąź.

Dlatego dobrą praktyką jest teraz wdrażanie umów o świadczenie usług w odniesieniu do podmiotów CRUD.

Jaki krok po kroku muszę wykonać, aby wdrożyć umowy serwisowe dla moich niestandardowych modułów?

NB: Wiem, że w moich modelach CRUD mogą być tysiące metod, po prostu proszę o oczywiste metody, jak podano tutaj: http://devdocs.magento.com/guides/v2.0/extension-dev-guide /service-contracts/design-patterns.html :

  • get
  • save
  • getList
  • delete
  • deleteById
Raphael at Digital Pianism
źródło

Odpowiedzi:

90

Chciałbym podać nieco więcej szczegółów oprócz doskonałej odpowiedzi @ryanF.

Chciałbym podsumować powody dodania repozytorium dla encji niestandardowych, podać przykłady, jak to zrobić, a także wyjaśnić, jak udostępnić te metody repozytorium jako część interfejsu API sieci Web.

Oświadczenie: opisuję pragmatyczne podejście do tego, jak to zrobić w przypadku modułów stron trzecich - zespoły podstawowe mają własne standardy, których przestrzegają (lub nie).

Ogólnie rzecz biorąc, celem repozytorium jest ukrycie logiki związanej z pamięcią masową.
Klient repozytorium nie powinien dbać o to, czy zwrócony obiekt jest przechowywany w pamięci w tablicy, czy jest pobierany z bazy danych MySQL, pobierany ze zdalnego interfejsu API czy z pliku.
Zakładam, że główny zespół Magento to zrobił, aby mogli zmienić lub wymienić ORM w przyszłości. W Magento ORM składa się obecnie z modeli, modeli zasobów i kolekcji.
Jeśli moduł innej firmy korzysta tylko z repozytoriów, Magento może zmienić sposób i miejsce przechowywania danych, a moduł będzie kontynuował pracę, pomimo tych głębokich zmian.

Repozytoria mają na ogół metod, takich jak findById(), findByName(), put()lub remove().
W Magento te powszechnie nazywane są getbyId(), save()i delete()nawet nie udaje, że robią coś innego, ale operacje CRUD DB.

Metody repozytorium Magento 2 można łatwo ujawnić jako zasoby API, co czyni je cennymi do integracji z systemami stron trzecich lub bezgłowymi instancjami Magento.

„Czy powinienem dodać repozytorium dla mojej encji niestandardowej?”.

Jak zawsze odpowiedź brzmi

"To zależy".

Krótko mówiąc, jeśli twoje podmioty będą używane przez inne moduły, to tak, prawdopodobnie chcesz dodać repozytorium.

Tutaj bierze się pod uwagę jeszcze jeden czynnik: w Magento 2 repozytoria można łatwo ujawnić jako zasoby Web API - czyli REST i SOAP.

Jeśli jest to dla ciebie interesujące z powodu integracji systemów stron trzecich lub bezgłowej konfiguracji Magento, to znowu, tak, prawdopodobnie chcesz dodać repozytorium dla swojej jednostki.

Jak dodać repozytorium dla mojej encji niestandardowej?

Załóżmy, że chcesz ujawnić swoją jednostkę jako część interfejsu API REST. Jeśli nie jest to prawdą, możesz pominąć nadchodzącą część dotyczącą tworzenia interfejsów i przejść bezpośrednio do „Utwórz repozytorium i implementację modelu danych” poniżej.

Utwórz interfejsy repozytorium i modelu danych

Utwórz foldery Api/Data/w swoim module. To tylko konwencja, możesz użyć innej lokalizacji, ale nie powinieneś.
Repozytorium przechodzi do Api/folderu. Data/Podkatalogu jest na później.

W Api/utwórz interfejs PHP metodami, które chcesz ujawnić. Zgodnie z konwencjami Magento 2 wszystkie nazwy interfejsów kończą się przyrostkiem Interface.
Na przykład dla Hamburgerencji stworzyłbym interfejs Api/HamburgerRepositoryInterface.

Utwórz interfejs repozytorium

Repozytoria Magento 2 są częścią logiki domeny modułu. Oznacza to, że repozytorium nie ma ustalonego zestawu metod.
Zależy to całkowicie od celu modułu.

Jednak w praktyce wszystkie repozytoria są dość podobne. Są opakowaniami dla funkcjonalności CRUD.
Większość z nich ma metody getById, save, deletei getList.
Może istnieć więcej, na przykład CustomerRepositoryma metodę get, która pobiera klienta pocztą e-mail, przy czym getByIdsłuży do wyszukiwania klienta według identyfikatora podmiotu.

Oto przykładowy interfejs repozytorium dla jednostki hamburgerowej:

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

}

Ważny! Poświęć czas!
Jest tu kilka błędów, które trudno debugować, jeśli się pomylicie:

  1. NIE używaj skalarnych typów argumentów PHP7 ani typów zwracanych, jeśli chcesz podłączyć to do interfejsu API REST!
  2. Dodaj adnotacje PHPDoc dla wszystkich argumentów i typ zwracany do wszystkich metod!
  3. Używaj w pełni kwalifikowanych nazw klas w bloku PHPDoc!

Adnotacje są analizowane przez środowisko Magento w celu ustalenia, jak konwertować dane do i z JSON lub XML. Importowane klasy (to znaczy useinstrukcje) nie są stosowane!

Każda metoda musi mieć adnotację z dowolnymi typami argumentów i typem zwracanym. Nawet jeśli metoda nie przyjmuje argumentów i nic nie zwraca, musi mieć adnotację:

/**
 * @return void
 */

Typy skalarne ( string, int, floati bool) muszą również zostać określone, zarówno dla argumentów i jako wartość zwracana.

Zauważ, że w powyższym przykładzie adnotacje do metod zwracających obiekty są również określone jako interfejsy.
Interfejsy typu powrotu znajdują się w katalogu Api\Datanamespace /.
Oznacza to, że nie zawierają żadnej logiki biznesowej. Są po prostu workami danych.
Następnie musimy utworzyć te interfejsy.

Utwórz interfejs DTO

Myślę, że Magento nazywa te interfejsy „modelami danych”, a nazwa wcale mi się nie podoba.
Ten typ klasy jest powszechnie znany jako obiekt transferu danych lub DTO .
Te klasy DTO mają tylko metody pobierające i ustawiające dla wszystkich swoich właściwości.

Powodem, dla którego wolę używać DTO zamiast modelu danych, jest to, że łatwiej jest pomylić je z modelami danych ORM, modelami zasobów lub modelami przeglądania ... zbyt wiele rzeczy jest już modelami w Magento.

Te same ograniczenia dotyczące typowania PHP7, które dotyczą repozytoriów, dotyczą również DTO.
Ponadto każda metoda musi mieć adnotację ze wszystkimi typami argumentów i typem zwracanym.

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

Jeśli metoda pobiera lub zwraca tablicę, typ elementów w tablicy musi być określony w adnotacji PHPDoc, a następnie otwierający i zamykający nawias kwadratowy [].
Dotyczy to zarówno wartości skalarnych (np. int[]), Jak i obiektów (np IngredientInterface[].).

Zauważ, że używam Api\Data\IngredientInterfacejako przykładu metody zwracającej tablicę obiektów, nie dodam kodu składników do tego postu.

ExtensibleDataInterface?

W powyższym przykładzie HamburgerInterfacerozszerza ExtensibleDataInterface.
Technicznie jest to wymagane tylko wtedy, gdy chcesz, aby inne moduły mogły dodawać atrybuty do twojej jednostki.
Jeśli tak, musisz także dodać kolejną parę getter / setter, zgodnie z konwencją o nazwie getExtensionAttributes()i setExtensionAttributes().

Nazywanie typu zwracanego przez tę metodę jest bardzo ważne!

Framework Magento 2 wygeneruje interfejs, implementację i fabrykę implementacji, jeśli odpowiednio je nazwiesz. Szczegóły tych mechanizmów są jednak poza zakresem tego postu.
Tylko wiedz, że jeśli wywoływany jest interfejs obiektu, który chcesz rozszerzyć \VinaiKopp\Kitchen\Api\Data\HamburgerInterface, to typ atrybutów rozszerzenia musi być \VinaiKopp\Kitchen\Api\Data\HamburgerExtensionInterface. Dlatego słowo Extensionnależy wstawić po nazwie encji, tuż przed Interfacesufiksem.

Jeśli nie chcesz, aby twoja jednostka była rozszerzalna, wówczas interfejs DTO nie musi rozszerzać żadnego innego interfejsu, getExtensionAttributes()a setExtensionAttributes()metody i można pominąć.

Dość na razie o interfejsie DTO, czas wrócić do interfejsu repozytorium.

Typ zwracany przez getList () SearchResults

Metoda repozytorium getListzwraca jeszcze inny typ, czyli SearchResultsInterfaceinstancję.

Metoda getListmoże oczywiście po prostu zwrócić tablicę obiektów pasujących do podanego SearchCriteria, ale zwrócenie SearchResultsinstancji pozwala na dodanie użytecznych metadanych do zwracanych wartości.

Poniżej możesz zobaczyć, jak to działa w getList()implementacji metody repozytorium .

Oto przykładowy interfejs wyników wyszukiwania hamburgera:

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

Jedyne, co robi ten interfejs, to zastępuje typy dwóch metod getItems()i setItems()interfejsu nadrzędnego.

Podsumowanie interfejsów

Mamy teraz następujące interfejsy:

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

Repozytorium rozciąga nic rozszerza , a rozszerza .
HamburgerInterface\Magento\Framework\Api\ExtensibleDataInterface
HamburgerSearchResultInterface\Magento\Framework\Api\SearchResultsInterface

Utwórz repozytorium i implementacje modelu danych

Następnym krokiem jest stworzenie implementacji trzech interfejsów.

Repozytorium

Zasadniczo repozytorium używa ORM do wykonywania swojej pracy.

Te getById(), save() i delete()metody są dość proste.
Jest HamburgerFactoryon wstrzykiwany do repozytorium jako argument konstruktora, co można zobaczyć nieco poniżej.

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

Teraz najciekawsza część repozytorium, getList()metoda. Metoda ma przełożyć warunki do zaproszeń metody w kolekcji.
getList()SerachCriteria

Trudność polega na tym , aby poprawnie ustawić warunki ANDi ORdla filtrów, zwłaszcza, że ​​składnia do ustawiania warunków w kolekcji jest różna w zależności od tego, czy jest to jednostka EAV czy jednostka o płaskiej tabeli.

W większości przypadków getList()można je zaimplementować, jak pokazano w poniższym przykładzie.

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

Filtry w obrębie FilterGroupmuszą być połączone za pomocą operatora OR .
Oddzielne grupy filtrów są łączone za pomocą logicznego operatora AND .

Uff
To była największa praca. Inne implementacje interfejsu są prostsze.

DTO

Magento pierwotnie zamierzał programistom wdrożyć DTO jako osobne klasy, różne od modelu encji.

Zespół podstawowy zrobił to jednak tylko dla modułu klienta ( \Magento\Customer\Api\Data\CustomerInterfacejest wdrażany przez \Magento\Customer\Model\Data\Customer, a nie \Magento\Customer\Model\Customer).
We wszystkich innych przypadkach model encji implementuje interfejs DTO (na przykład \Magento\Catalog\Api\Data\ProductInterfacejest implementowany przez \Magento\Catalog\Model\Product).

Pytałem o to członków zespołu podstawowego na konferencjach, ale nie otrzymałem jednoznacznej odpowiedzi na pytanie, co należy uznać za dobrą praktykę.
Mam wrażenie, że to zalecenie zostało porzucone. Byłoby miło uzyskać oficjalne oświadczenie w tej sprawie.

Na razie podjąłem pragmatyczną decyzję o zastosowaniu modelu jako implementacji interfejsu DTO. Jeśli uważasz, że użycie oddzielnego modelu danych jest czystsze, możesz to zrobić. Oba podejścia sprawdzają się w praktyce.

Jeśli interfejs DTO rozszerza Magento\Framework\Api\ExtensibleDataInterface, model musi zostać rozszerzony Magento\Framework\Model\AbstractExtensibleModel.
Jeśli nie zależy ci na rozszerzalności, model może po prostu kontynuować rozszerzanie podstawowej klasy modelu ORM Magento\Framework\Model\AbstractModel.

Ponieważ przykład HamburgerInterfacerozszerza ExtensibleDataInterfacemodel hamburgera rozszerza AbstractExtensibleModel, jak widać tutaj:

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

Wyodrębnienie nazw właściwości do stałych pozwala zachować je w jednym miejscu. Mogą być używane przez parę getter / setter, a także przez skrypt Setup, który tworzy tabelę bazy danych. W przeciwnym razie wyodrębnienie ich do stałych nie przyniesie korzyści.

SearchResult

Jest SearchResultsInterfaceto najprostszy z trzech interfejsów do zaimplementowania, ponieważ może odziedziczyć całą swoją funkcjonalność z klasy frameworka.

<?php

namespace VinaiKopp\Kitchen\Model;

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

class HamburgerSearchResult extends SearchResults implements HamburgerSearchResultInterface
{

}

Skonfiguruj preferencje ObjectManager

Mimo że implementacje są kompletne, nadal nie możemy używać interfejsów jako zależności innych klas, ponieważ menedżer obiektów Magento Framework nie wie, jakich implementacji użyć. Musimy dodać etc/di.xmlkonfigurację z preferencjami.

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

Jak można ujawnić repozytorium jako zasób API?

Ta część jest naprawdę prosta, to nagroda za przejrzenie wszystkich prac związanych z tworzeniem interfejsów, implementacjami i łączeniem ich razem.

Wszystko, co musimy zrobić, to utworzyć etc/webapi.xmlplik.

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

Należy pamiętać, że ta konfiguracja umożliwia nie tylko użycie repozytorium jako punktów końcowych REST, ale także udostępnia metody jako część interfejsu API SOAP.

W pierwszym przykładzie trasy <route method="GET" url="/V1/vinaikopp_hamburgers/:id">, symbol zastępczy :idmusi być zgodna z nazwą argumentu odwzorowanym metodzie public function getById($id).
Te dwie nazwy muszą się zgadzać, na przykład /V1/vinaikopp_hamburgers/:hamburgerIdnie działałoby, ponieważ nazwa zmiennej argumentu metody to $id.

W tym przykładzie ustawiłem dostępność na <resource ref="anonymous"/>. Oznacza to, że zasób jest publicznie dostępny bez żadnych ograniczeń!
Aby udostępnić zasób tylko zalogowanemu klientowi, użyj <resource ref="self"/>. W takim przypadku medo wypełnienia zmiennej argumentu $ididentyfikatorem aktualnie zalogowanego klienta zostanie użyte słowo specjalne w adresie URL punktu końcowego zasobu .
Spójrz na klienta Magento etc/webapi.xmli CustomerRepositoryInterfacejeśli tego potrzebujesz.

Na koniec <resources>można również użyć do ograniczenia dostępu do zasobu do konta administratora. W tym celu ustaw <resource>odnośnik na identyfikator zdefiniowany w etc/acl.xmlpliku.
Na przykład <resource ref="Magento_Customer::manage"/>ograniczy dostęp do dowolnego konta administratora, które ma uprawnienia do zarządzania klientami.

Przykładowa kwerenda API wykorzystująca curl może wyglądać następująco:

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

Uwaga: pisanie tego zaczęło się jako odpowiedź na https://github.com/astorm/pestle/issues/195
Sprawdź tłuczek , kup Commercebug i zostań mecenasem @alanstorm

Vinai
źródło
1
Dzięki za tę świetną odpowiedź. Przepraszam, może coś mi brakuje, ale jaki jest sens posiadania czystego interfejsu dla encji, kiedy na końcu musi ona rozszerzać się z AbstractModel, który ma metodę setData, co oznacza, że ​​możesz dodać cokolwiek do obiektu niezależnie od interfejsu?
LDusan
Klasa może implementować dowolną liczbę interfejsów i dodawać również dodatkowe metody. Ważne jest to, że każda inna klasa zależy tylko od metod interfejsu, a zatem nie wie o żadnej z pozostałych. To sprawia, że ​​szczegóły implementacji metod nieinterfejsowych można zmienić w dowolnym momencie bez rozbijania klas zewnętrznych. Na tym polega odwrócenie zależności. Zarówno klasa, jak i klienci zależą od interfejsu i nie wiedzą o szczegółach implementacji. Czy to wyjaśnia?
Vinai
Dzięki za odpowiedź, rozumiem o co ci chodzi. Chodzi o to, że setData jest metodą publiczną, dlatego nie jestem pewien, czy można ją uznać za szczegół implementacji. Jeśli ma to być metoda publiczna, to skąd możemy mieć pewność, że nie zmieni niczego zewnętrznego po zmianie?
LDusan
3
Przepraszam. To, co opisujesz, to wspólny punkt widzenia. Mechanika zależności nie jest intuicyjna, a ponieważ PHP pozwala na wywoływanie metod, które nie są częścią zależności od interfejsu, a także ponieważ nie trzeba go kompilować, sprawia, że ​​zależności działają jeszcze bardziej rozmazane i trudno je zobaczyć . Można to również zaobserwować w rdzeniu Magento 2, gdzie istnieje wiele miejsc, w których wywoływane są metody implementacji, które nie są częścią zależności od interfejsu. Służą one jako złe przykłady i jeszcze trudniej uzyskać jasne i solidne zrozumienie.
Vinai
35

@Raphael at Digital Pianism:

Proszę zapoznać się z następującą przykładową strukturą modułu:

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. Utwórz interfejs repozytorium (umowa serwisowa)
    Namespace/Custom/Api/CustomRepositoryInterface.php: http://codepad.org/WognSKnH

  2. Utwórz interfejs SearchResultsInterface
    Namespace/Custom/Api/Data/CustomSearchResultsInterface.php: http://codepad.org/zcbi8X4Z

  3. Utwórz niestandardowy interfejs (kontener danych)
    Namespace/Custom/Api/Data/CustomInterface.php: http://codepad.org/Ze53eT4o

  4. Utwórz CustomRepository (betonowe repozytorium)
    Namespace/Custom/Model/CustomRepository.php: http://codepad.org/KNt5QAGZ
    To tutaj dzieje się „magia”. Za pośrednictwem konstruktora DI przekazujesz w fabryce model / kolekcję zasobów dla własnego modułu; Jeśli chodzi o metodę save CRUD w tym repozytorium, ze względu na interfejs CustomRepositoryInterface należy przekazać parametr CustomInterface. Di.xml modułu preferuje zamianę interfejsu tego typu na model encji. Model encji jest przekazywany do modelu zasobów i zapisywany.

  5. Ustaw preferencje w
    Namespace/Custom/etc/di.xml: http://codepad.org/KmcoOUeV

  6. Model jednostki implementujący niestandardowy interfejs (kontener danych)
    Namespace/Custom/Model/Custom.php: http://codepad.org/xQiBU7p7 .

  7. Model zasobu
    Namespace/Custom/Model/ResourceModel/Custom.php: http://codepad.org/IOsxm9qW

Kilka rzeczy do zapamiętania:

  • Zrzeczenie się!!! Kiedyś „nazw” w miejsce niestandardowej nazwy producenta, nazwy agencji, itp ... cokolwiek nazwa używana do grupy moduły razem ... rzeczywistego wykorzystania „nazw” jest zupełnie nie ważne w PHP ... więc wiedza że zrobiłem to dla wygody i nie sądzę, aby to zadziałało, ani też nie sugeruję tego w żaden sposób.

  • @Ryan Street nauczył mnie tego ... więc nie chcę brać całego uznania

  • Wyraźnie zmień implementację repozytorium w zależności od potrzeb

  • Wdrażasz interakcję z niestandardowymi modelami encji / modelami zasobów / kolekcjami w konkretnym repozytorium ...

  • Wiem, że nie omówiłem wszystkich metod wymienionych w pytaniu, ale to dobry początek i powinien wypełnić lukę między dokumentami a faktyczną implementacją.

ryanF
źródło
Ryan, czy metody wymienione w umowach serwisowych są obowiązkowe dla każdego niestandardowego interfejsu API mydła, który tworzymy, tj. Save (), delete () itp.?
Sushivam
Czy mógłbyś mi powiedzieć, jak stworzyć niestandardowe API mydła w Magento 2?
Sushivam
@SachinS Niestety nie mam żadnych informacji na temat SOAP. Jeszcze go nie zbadałem, ani jeszcze go nie wdrożyłem. Najlepsze, co mógłbym zasugerować, to otworzyć tutaj nowe pytanie na ten temat. Powiedziałbym, że sprawdź też dokumenty, ale niestety nie zawsze jest to najlepszy sposób działania (mogą ich brakować). Zawsze możesz rzucić okiem na podstawową bazę kodu lub rozszerzenie innej firmy i sprawdzić, czy jest tam jakiś wgląd. Powodzenia! Jeśli znajdziesz odpowiedź, możesz dodać link tutaj. Dzięki
ryanF,
Dzięki za odpowiedź @ryan, w każdym razie zaimplementowałem mój moduł za pomocą REST, ponieważ jest on lekki w porównaniu do SOAP ... Jeśli zaimplementuję to samo w SOAP,
zgłoś
3
@ryanF Dziękujemy za tę bardzo przydatną odpowiedź. Wiem, że nie powinien to być działający kod do kopiowania / wklejania, ale oto kilka literówek z korzyścią dla innych. W repozytorium CustomSearchResultsInterfaceFactory powinien mieć nazwę CustomSearchResultsFactory. $ searchResults-> setCriteria powinno być $ searchResults-> setSearchCriteria. $ Customs [] w foreach powinno być $ Custom []. Myślę, że o to chodzi.
tetranz
3

pełne pliki dotyczące korzystania z umów serwisowych

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
źródło