如何在Magento 2中为自定义模块实施服务合同?


42

正如所看到的这篇文章:过时的保存和抽象模型load方法saveload方法,Magento的2已被弃用发展分支。

因此,现在的优良作法是执行服务合同以处理CRUD实体。

为自定义模块实体实施服务合同时,我需要遵循哪些逐步过程?

注意:我知道我的CRUD模型中可能有成千上万种方法,我只是在要求以下明显的方法:http : //devdocs.magento.com/guides/v2.0/extension-dev-guide /service-contracts/design-patterns.html

  • get
  • save
  • getList
  • delete
  • deleteById

Answers:


89

除了@ryanF的出色回答,我想提供更多细节。

我想总结一下为自定义实体添加存储库的原因,提供示例说明,并说明如何将这些存储库方法作为Web API的一部分公开。

免责声明:我仅描述一种实用的方法,如何对第三方模块执行此操作-核心团队具有(或不遵循)自己的标准。

通常,存储库的目的是隐藏与存储相关的逻辑。
存储库的客户端不应该关心返回的实体是否保存在数组的内存中,是否从MySQL数据库检索,是从远程API还是从文件中获取。
我认为Magento核心团队是这样做的,因此他们将来可以更改或替换ORM。在Magento中,ORM当前由模型,资源模型和集合组成。
如果第三方模块仅使用存储库,Magento可以更改数据的存储方式和存储位置,尽管进行了这些深层更改,该模块仍将继续工作。

库一般都有类似的方法findById()findByName()put()remove()
在Magento的这些常用称为getbyId()save()delete(),甚至没有假装他们正在做别的什么,但CRUD操作数据库。

Magento 2存储库方法可以轻松地作为API资源公开,使其对于与第三方系统或无头Magento实例集成非常有价值。

“我应该为自定义实体添加存储库吗?”。

与往常一样,答案是

“这取决于”。

简而言之,如果您的实体将被其他模块使用,那么可以,您可能想要添加一个存储库。

这里还有一个重要的因素:在Magento 2中,存储库可以轻松地作为Web API(即REST和SOAP)资源公开。

如果由于第三方系统集成或无用的Magento设置而使您感兴趣,那么再次,是的,您可能想为实体添加存储库。

如何为自定义实体添加存储库?

假设您想将您的实体公开为REST API的一部分。如果不是这样,则可以跳过接下来创建接口的部分,直接转到下面的“创建存储库和数据模型实现”。

创建存储库和数据模型接口

Api/Data/在模块中创建文件夹。这只是约定,您可以使用其他位置,但不可以。
存储库进入Api/文件夹。该Data/子目录供以后使用。

在中Api/,使用您要公开的方法创建一个PHP接口。根据Magento 2约定,所有接口名称均以后缀结尾Interface
例如,对于一个Hamburger实体,我将创建interface Api/HamburgerRepositoryInterface

创建存储库界面

Magento 2存储库是模块域逻辑的一部分。这意味着,存储库没有必须实现的固定方法集。
这完全取决于模块的目的。

但是,实际上所有存储库都非常相似。它们是CRUD功能的包装。
最有方法getByIdsavedeletegetList
可能会有更多,例如CustomerRepository具有方法get,该方法通过电子邮件获取客户,从而getById用于通过实体ID检索客户。

这是汉堡实体的示例存储库接口:

<?php

namespace VinaiKopp\Kitchen\Api;

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

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

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

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

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

}

重要!这里是时间的迷!
如果您弄错了这里的一些陷阱,则很难调试:

  1. 如果要将其挂接到REST API中,请勿使用PHP7标量参数类型或返回类型!
  2. 为所有参数添加PHPDoc注释,并为所有方法添加返回类型!
  3. 在PHPDoc块中使用完全合格的类名

Magento框架会解析注释,以确定如何将数据与JSON或XML相互转换。类导入(即use语句)不适用!

每个方法都必须具有带任何参数类型和返回类型的注释。即使一个方法不带参数也不返回任何内容,它也必须具有注释:

/**
 * @return void
 */

标量类型(stringintfloatbool)也必须指定,无论是参数和返回值。

请注意,在上面的示例中,用于返回对象的方法的注释也被指定为接口。
返回类型的接口都在Api\Data名称空间/目录中。
这表明它们不包含任何业务逻辑。它们只是数据包。
接下来,我们必须创建这些接口。

创建DTO界面

我认为Magento将这些接口称为“数据模型”,这个名字我一点都不喜欢。
这种类型的类通常称为数据传输对象DTO
这些DTO类的所有属性仅具有getter和setter。

我更喜欢使用DTO而不是数据模型的原因是,它不容易与ORM数据模型,资源模型或视图模型混淆……在Magento中,已经有太多东西成为模型了。

适用于存储库的关于PHP7类型的相同限制也适用于DTO。
同样,每个方法都必须具有所有参数类型和返回类型的注释。

<?php

namespace VinaiKopp\Kitchen\Api\Data;

use Magento\Framework\Api\ExtensibleDataInterface;

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

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

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

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

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

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

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

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

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

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

如果方法检索或返回一个数组,则必须在PHPDoc批注中指定数组中各项的类型,然后在其前后加一个方括号[]
标量值(例如int[])和对象(例如IngredientInterface[])都是如此。

请注意,我Api\Data\IngredientInterface以一个示例为例,该方法返回一个对象数组,我不会在这篇文章中添加成分代码。

可扩展数据接口?

在上述示例中,HamburgerInterface扩展了ExtensibleDataInterface
从技术上讲,仅当您希望其他模块能够向实体添加属性时才需要这样做。
如果是这样,您还需要按照惯例称为getExtensionAttributes()和来添加另一个getter / setter对setExtensionAttributes()

此方法的返回类型的命名非常重要!

如果您正确地命名它们,Magento 2框架将生成接口,实现以及实现的工厂。这些机制的细节不在本文的讨论范围之内。
只要知道,如果调用了要使其可扩展的对象的接口\VinaiKopp\Kitchen\Api\Data\HamburgerInterface,则扩展属性类型必须为\VinaiKopp\Kitchen\Api\Data\HamburgerExtensionInterface。因此Extension,必须在实体名称之后,Interface后缀之前插入单词。

如果您不希望您的实体具有可扩展性,则DTO接口不必扩展任何其他接口,可以省略getExtensionAttributes()setExtensionAttributes()方法。

现在,关于DTO界面已经足够了,该回到存储库界面了。

getList()返回类型SearchResults

存储库方法getList返回另一种类型,即SearchResultsInterface实例。

该方法getList当然可以只返回与指定匹配的对象数组SearchCriteria,但是返回SearchResults实例可以向返回的值添加一些有用的元数据。

您可以在存储库getList()方法实现中的下面看到它的工作方式。

这是示例汉堡搜索结果界面:

<?php

namespace VinaiKopp\Kitchen\Api\Data;

use Magento\Framework\Api\SearchResultsInterface;

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

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

该接口所做的只是重写两个方法getItems()setItems()父接口的类型。

接口汇总

现在,我们有以下接口:

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

存储库不进行任何扩展,
对进行HamburgerInterface扩展\Magento\Framework\Api\ExtensibleDataInterface
对进行HamburgerSearchResultInterface扩展\Magento\Framework\Api\SearchResultsInterface

创建存储库和数据模型实现

下一步是创建三个接口的实现。

仓库

本质上,存储库使用ORM来完成其工作。

getById()save()delete()方法是相当简单。
HamburgerFactory其作为构造函数参数注入到存储库中,如下所示。

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

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

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

现在到存储库中最有趣的部分,即getList()方法。
getList()方法必须将SerachCriteria条件转换为集合上的方法调用。

棘手的部分是正确设置过滤器的ANDOR条件,特别是因为在集合上设置条件的语法取决于它是EAV还是平面表实体而有所不同。

在大多数情况下,getList()可以按照以下示例中的说明实施。

<?php

namespace VinaiKopp\Kitchen\Model;

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

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

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

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

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

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

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

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

        $collection->load();

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

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

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

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

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

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

        return $searchResults;
    }
}

内的过滤器FilterGroup必须使用OR运算符组合。
使用逻辑AND运算符组合单独的过滤器组。

ew
这是最大的工作。其他接口实现更为简单。

DTO

Magento最初打算让开发人员将DTO作为独立的类实现,与实体模型不同。

核心团队只为客户模块\Magento\Customer\Api\Data\CustomerInterface执行了此操作(是由实现\Magento\Customer\Model\Data\Customer,不是\Magento\Customer\Model\Customer)。
在所有其他情况下,实体模型都实现DTO接口(例如\Magento\Catalog\Api\Data\ProductInterface通过实现\Magento\Catalog\Model\Product)。

我已经在会议上向核心团队成员询问了有关此内容的信息,但没有得到明确的答复,即什么是好的做法。
我的印象是该建议已被放弃。最好对此发表正式声明。

现在,我已经做出务实的决定,将模型用作DTO接口实现。如果您觉得使用单独的数据模型更干净,请随时使用。两种方法在实践中都行之有效。

如果DTO接口扩展Magento\Framework\Api\ExtensibleDataInterface,则模型必须扩展Magento\Framework\Model\AbstractExtensibleModel
如果您不关心可扩展性,则该模型可以简单地继续扩展ORM模型基类Magento\Framework\Model\AbstractModel

由于该示例HamburgerInterface扩展了ExtensibleDataInterface汉堡模型扩展了AbstractExtensibleModel,因此可以在此处看到:

<?php

namespace VinaiKopp\Kitchen\Model;

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

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

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

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

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

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

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

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

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

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

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

将属性名称提取为常量可以将它们保留在一个位置。getter / setter对以及创建数据库表的Setup脚本都可以使用它们。否则,将它们提取为常量没有任何好处。

搜索结果

SearchResultsInterface是要实现的三个接口中最简单的一个,因为它可以从框架类继承其所有功能。

<?php

namespace VinaiKopp\Kitchen\Model;

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

class HamburgerSearchResult extends SearchResults implements HamburgerSearchResultInterface
{

}

配置ObjectManager首选项

即使实现是完整的,我们仍然不能将接口用作其他类的依赖项,因为Magento Framework对象管理器不知道要使用什么实现。我们需要etc/di.xml使用首选项添加配置。

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

如何将资源库公开为API资源?

这部分非常简单,这是完成所有创建接口,实现并将它们连接在一起的工作的回报。

我们需要做的就是创建一个etc/webapi.xml文件。

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

请注意,此配置不仅允许将存储库用作REST端点,而且还将方法公开为SOAP API的一部分。

在第一个示例路由中,<route method="GET" url="/V1/vinaikopp_hamburgers/:id">占位符:id必须将参数名称与映射方法相匹配public function getById($id)
这两个名称必须匹配,例如/V1/vinaikopp_hamburgers/:hamburgerId将不起作用,因为方法参数变量名称为$id

在此示例中,我将可访问性设置为<resource ref="anonymous"/>。这意味着该资源是公开公开的,没有任何限制!
要使资源仅对已登录的客户可用,请使用<resource ref="self"/>。在这种情况下,me资源端点URL中的特殊词将用于使用$id当前登录客户的ID 填充参数变量。
看一下Magento客户etc/webapi.xmlCustomerRepositoryInterface如果需要的话。

最后,<resources>还可以使用来将对资源的访问限制为管理员用户帐户。为此,将<resource>ref 设置为etc/acl.xml文件中定义的标识符。
例如,<resource ref="Magento_Customer::manage"/>将访问权限限制为有权管理客户的任何管理员帐户。

使用curl的示例API查询可能如下所示:

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

注:写这篇开始作为一个答案https://github.com/astorm/pestle/issues/195
退房,买Commercebug并成为patreon @alanstorm的


1
感谢您的出色回答。抱歉,我可能遗漏了一些东西,但是,当一个实体最终必须从具有setData方法的AbstractModel扩展而来时,为该实体提供一个干净的接口有什么意义呢?这意味着无论接口如何,您都可以向该对象添加任何东西?
LDusan

一个类可以实现任何数量的接口,也可以添加其他方法。重要的是,任何其他类仅依赖于接口方法,因此不了解其他任何一个。这使得非接口方法的实现细节可以在不破坏外部类的情况下随时更改。那就是依赖倒置背后的想法。类和任何客户端都依赖于接口,并且不了解实现细节。这澄清了吗?
Vinai

感谢您的回答,我明白您的意思。问题是setData是一个公共方法,因此我不确定是否可以将其视为实现细节。如果要像公用方法一样使用它,我们如何确定更改后它不会破坏任何外部?
LDusan

3
我道歉。您所描述的是一个普遍的观点。依赖关系的机制不直观,并且由于PHP允许调用不依赖于接口的部分的方法,并且由于不需要对其进行编译,因此使依赖关系的工作方式更加模糊并且难以看清。在Magento 2内核中也可以观察到这一点,在该内核中有很多地方都调用了实现方法,这些方法并不依赖于接口。这些都是不好的例子,甚至使人们很难清楚地理解。
维奈


35

@Raphael在数字钢琴家:

请参考以下示例模块结构:

app/
   code/
  |    Namespace/
  |   |    Custom/
  |   |   |    Api/
  |   |   |   |    CustomRepositoryInterface.php
  |   |   |   |    Data/
  |   |   |   |   |    CustomInterface.php
  |   |   |   |   |    CustomSearchResultsInterface.php
  |   |   |    etc/
  |   |   |   |    di.xml
  |   |   |   |    module.xml
  |   |   |    Model/
  |   |   |   |    Custom.php
  |   |   |   |    CustomRepository.php
  |   |   |   |    ResourceModel/
  |   |   |   |   |    Custom.php
  1. 创建存储库界面(服务合同)
    Namespace/Custom/Api/CustomRepositoryInterface.phphttp : //codepad.org/WognSKnH

  2. 创建SearchResultsInterface
    Namespace/Custom/Api/Data/CustomSearchResultsInterface.phphttp : //codepad.org/zcbi8X4Z

  3. 创建CustomInterface(数据容器)
    Namespace/Custom/Api/Data/CustomInterface.phphttp : //codepad.org/Ze53eT4o

  4. 创建CustomRepository(具体存储库)
    Namespace/Custom/Model/CustomRepository.phphttp : //codepad.org/KNt5QAGZ
    这就是发生“魔术”的地方。通过构造函数DI,您可以为自定义模块传入资源模型/集合工厂。关于此存储库中的save CRUD方法,由于CustomRepositoryInterface的原因,您必须传入CustomInterface的参数。您模块的di.xml可以使用实体模型替换此类型的接口。实体模型被传递到资源模型中并被保存。


  5. Namespace/Custom/etc/di.xml以下位置设置首选项:http : //codepad.org/KmcoOUeV

  6. 实现自定义接口(数据容器)的实体模型
    Namespace/Custom/Model/Custom.phphttp : //codepad.org/xQiBU7p7

  7. 资源模型
    Namespace/Custom/Model/ResourceModel/Custom.phphttp//codepad.org/IOsxm9qW

注意事项:

  • 免责声明!!!用我“命名空间”的地方自定义的供应商名称,机构名称等......无论您使用的名称,以便将模块组合在一起......实际使用“命名空间”,完全是合法的PHP的...所以知道我这样做是为了方便起见,而我认为这会工作,我也不建议以任何方式。

  • @Ryan Street教了我这个...所以我不想承担所有的荣誉

  • 明确更改存储库的实现以适合您的需求

  • 您可以在具体存储库中实现与自定义实体模型/资源模型/集合的交互...

  • 我知道我并没有解决您在问题中列出的所有方法,但这是一个很好的开始,应该弥补文档和实际实现之间的差距。


瑞安(Ryan),服务合同中提及的方法是否对我们创建的任何自定义肥皂api(例如save(),delete()等)强制执行?
Sushivam,2013年

您能否给我一个关于如何在magento 2中创建自定义soap api的想法?
Sushivam,2013年

@SachinS不幸的是,我对SOAP没有任何见识。我还没有研究它,也没有实现它。我能建议的最好的办法是在此处提出一个新的问题。我也想说检查一下文档,但是可悲的是,这并不总是最好的方法(他们可能会缺少)。您可以随时查看核心代码库或第三方扩展,并查看是否有任何见解。祝好运!如果您找到答案,则可以在此处添加链接。谢谢
ryanF

感谢@ryan的回复,无论如何,我还是使用REST实现了我的模块,因为与SOAP相比,它的权重很轻...如果我在SOAP中实现相同的模块,请病假
Sushivam,2016年

3
@ryanF感谢您提供非常有用的答案。我知道这不应该是复制/粘贴工作代码,但是这里有一些错别字,以便其他人继续学习。在存储库中,CustomSearchResultsInterfaceFactory应该是CustomSearchResultsFactory。$ searchResults-> setCriteria应该是$ searchResults-> setSearchCriteria。foreach中的$ Customs []应该是$ customs []。我想就是这样。
trinz '16

3

使用服务合同的完整文件

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" => "karina@kapoor.com",
            "spouse" => 1
        ];

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


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

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

../Controller/Index/Getlist.php

<?php

namespace Custom\Module\Controller\Index;

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

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

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


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

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




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

../Controller/Index/Getbyid.php

<?php

namespace Custom\Module\Controller\Index;

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

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

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

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

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

    public function execute()
    {

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

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

../Controller/Index/Deletebyid.php

<?php

namespace Custom\Module\Controller\Index;

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

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

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

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

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

    public function execute()
    {

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

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

../Controller/Index/Del.php

<?php

namespace Custom\Module\Controller\Index;

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

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

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

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

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

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

        $this->resultFactory->create("raw");
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.