了解 Magento 2 中的对象管理器

为什么不鼓励在 Magento 2 中直接使用对象管理器,以及为什么应该使用适当的依赖注入。

当我第一次开始使用 Magento 2 时,最令人困惑的方面之一就是如何创建和管理对象。

Stack Overflow 和其他论坛上的帖子经常建议直接使用 Magento 的对象管理器来创建对象。这种方法看起来更简单直接,我早期的许多编程尝试都遵循了这种方法。

但随着时间的推移,我意识到这是一个非常糟糕的想法——主要是因为它绕过了 Magento 的依赖注入层。

还有一些其他很好的理由可以解释为什么您应该避免在 Magento 2 代码中直接使用对象管理器,所以让我们深入探讨一下为什么会这样。

什么是对象管理器?

对象管理器是 Magento 的依赖注入容器。它是负责管理框架中对象创建的中心类。

它不仅创建对象,还管理对象之间的依赖关系。当你从对象管理器请求一个对象时,它会自动创建该对象所依赖的所有其他对象。

使用对象管理器创建对象(错误方法)

可以通过编写如下代码直接使用对象管理器创建对象,并将其放置在 Magento 代码库中的任何位置:

// Get an instance of Object Manager
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();

// Create an instance of a Product model
$product = $objectManager->create('Magento\Catalog\Model\Product');

这确实有效!但也带来了一些严重的问题。

首先,它绕过了Magento 的依赖注入系统,这可能会导致运行时问题或不兼容,尤其是在 Adobe Commerce Marketplace 代码审查等严格的环境中。虽然它不会直接破坏编译过程(使用 执行bin/magento setup:di:compile),但它确实会阻止 Magento 优化对象创建并有效地管理依赖项。

其次,它破坏了与 Adobe Commerce Marketplace 的兼容性。如果您计划在此市场上分发您的扩展程序并使用此类代码,则不可行,因为它违反了他们的编码标准。

而且,当你像这样直接在代码中使用对象管理器时,你的类和你正在创建的对象之间会形成紧密耦合。这会使代码难以调试和测试,因为你无法轻松地模拟对象进行测试。

注入对象管理器(仍然不太好)

一个更好的方法是将对象管理器接口作为依赖项注入到类构造函数中,然后使用它来创建所需的对象:

<?php

namespace Vendor\Module\Service;

use Magento\Framework\ObjectManagerInterface;

class Foo
{
    public function __construct(
        private ObjectManagerInterface $objectManager,
    ) {
    }

    public function someCode()
    {
        $product = $this->objectManager->create('Magento\Catalog\Model\Product');
    }
}

虽然这避免了直接调用的一些直接陷阱ObjectManager::getInstance()(比如编译环境中的潜在问题),但这仍然是一个坏主意。

您的代码仍然与对象管理器紧密耦合,这使得测试和维护变得困难。由于您在代码中动态创建对象,它还会掩盖类的实际依赖关系。这使得阅读您代码的人更难理解您正在使用的具体对象。

它还创建了实现依赖,而不是接口依赖。这意味着你的类依赖于对象管理器的具体实现,而不是你所需依赖项的特定接口。

在面向对象设计中使用SOLID原则有特定的原因,这种方法违反了“D”原则,即依赖倒置原则

通过注入对象管理器而不是您想要使用的类,您就不会将对象创建的责任委托给 Magento 的依赖注入系统,而这正是它存在的原因。

适当的依赖注入(正确的方法)

在 Magento 2 中创建对象的正确方法是使用适当的依赖注入。

这涉及将您需要的对象直接注入到类构造函数中,而不是动态创建它们:

<?php

namespace Vendor\Module\Service;

use Magento\Catalog\Model\Product;

class Foo
{
    public function __construct(
        private Product $product,
    ) {
    }

    public function someCode()
    {
        // Use $this->product directly
    }
}

在这个例子中,我们将Magento\Catalog\Model\Product对象直接注入到类的构造函数中。这使得依赖关系明确,并允许 Magento 管理对象的创建过程。

当你使用依赖注入正确地创建此类对象时,Magento 的对象管理器会自动创建该Product对象,并在实例化类时将其注入到类中。这使得 Magento 能够优化对象的创建过程,并更有效地管理其依赖关系。

依赖关系的依赖关系

Magento 依赖注入系统最强大的功能之一,就是它能够管理依赖项的依赖。这意味着,如果你注入一个有自身依赖项的类,Magento 也会自动创建这些依赖项。

我们在上面的例子中注入的类Product实际上有它自己的依赖项(而且数量多得惊人)。让我们看看它的构造函数:

public function __construct(
    \Magento\Framework\Model\Context $context,
    \Magento\Framework\Registry $registry,
    \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory,
    AttributeValueFactory $customAttributeFactory,
    \Magento\Store\Model\StoreManagerInterface $storeManager,
    \Magento\Catalog\Api\ProductAttributeRepositoryInterface $metadataService,
    Product\Url $url,
    Product\Link $productLink,
    \Magento\Catalog\Model\Product\Configuration\Item\OptionFactory $itemOptionFactory,
    \Magento\CatalogInventory\Api\Data\StockItemInterfaceFactory $stockItemFactory,
    \Magento\Catalog\Model\Product\OptionFactory $catalogProductOptionFactory,
    \Magento\Catalog\Model\Product\Visibility $catalogProductVisibility,
    Status $catalogProductStatus,
    \Magento\Catalog\Model\Product\Media\Config $catalogProductMediaConfig,
    Product\Type $catalogProductType,
    \Magento\Framework\Module\Manager $moduleManager,
    \Magento\Catalog\Helper\Product $catalogProduct,
    \Magento\Catalog\Model\ResourceModel\Product $resource,
    \Magento\Catalog\Model\ResourceModel\Product\Collection $resourceCollection,
    \Magento\Framework\Data\CollectionFactory $collectionFactory,
    \Magento\Framework\Filesystem $filesystem,
    \Magento\Framework\Indexer\IndexerRegistry $indexerRegistry,
    \Magento\Catalog\Model\Indexer\Product\Flat\Processor $productFlatIndexerProcessor,
    \Magento\Catalog\Model\Indexer\Product\Price\Processor $productPriceIndexerProcessor,
    \Magento\Catalog\Model\Indexer\Product\Eav\Processor $productEavIndexerProcessor,
    CategoryRepositoryInterface $categoryRepository,
    Product\Image\CacheFactory $imageCacheFactory,
    \Magento\Catalog\Model\ProductLink\CollectionProvider $entityCollectionProvider,
    \Magento\Catalog\Model\Product\LinkTypeProvider $linkTypeProvider,
    \Magento\Catalog\Api\Data\ProductLinkInterfaceFactory $productLinkFactory,
    \Magento\Catalog\Api\Data\ProductLinkExtensionFactory $productLinkExtensionFactory,
    EntryConverterPool $mediaGalleryEntryConverterPool,
    \Magento\Framework\Api\DataObjectHelper $dataObjectHelper,
    \Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface $joinProcessor,
    array $data = [],
    \Magento\Eav\Model\Config $config = null,
    FilterProductCustomAttribute $filterCustomAttribute = null
) {
    // ...
}

我的天哪——依赖项好多啊!不过,由于所有这些依赖项都已注入到
Product类中,我们无需担心它们是如何创建或管理的。当我们将
Product类注入到我们自己的类中时,Magento 会自动为我们创建所有这些依赖项。

为什么正确的依赖注入很重要

希望我已经充分说明了为什么我们应该在 Magento 2 中使用适当的依赖注入。但这里还有一些应该避免直接使用对象管理器的原因:

  1. 您的代码更易于测试– 您可以轻松地模拟依赖项以进行单元测试。例如,使用 PHPUnit,您可以模拟Product类并在测试期间将其传递给您的类,从而确保您在独立测试逻辑。虽然大多数开发人员不会为他们的 Magento 代码编写测试,但这仍然是一个值得遵循的好习惯,因为它使您的代码更易于推理。
  2. Magento 可以优化对象创建——系统可以确定创建和管理对象的最有效方式。例如,Magento 的 DI 系统默认使用共享实例,并生成工厂和代理来提高性能,而直接使用对象管理器可能会不必要地创建新实例。
  3. 您的代码与所有 Magento 环境兼容。这包括生产模式(通常与编译一起使用以提高性能)、在 Adobe Commerce Cloud 等平台上的部署,以及符合 Adobe Commerce Marketplace 标准的代码。
  4. 您的依赖关系清晰明确– 任何阅读您代码的人都能立即看到您的类所依赖的其他组件。这使得您的代码更易于理解和维护,也方便其他开发者使用您的代码。
  5. 遵循 Magento 的编码标准– 让您的扩展程序更有可能通过代码审查。如果您计划在 Adobe Commerce Marketplace 上分发扩展程序,这一点尤为重要,但对于确保您的代码兼容 Magento 的未来版本也至关重要。

何时可以接受直接对象管理器的使用

我总是喜欢从“规则”的反面来理解何时可以打破规则。事实上,在某些情况下,直接使用对象管理器是可以接受的,同时仍然符合 Magento 的最佳实践:

  1. 在工厂中– 在运行时确定要实例化的类。例如,工厂可以使用对象管理器根据配置值创建特定的产品类型。
  2. 在代理中– 实现延迟加载的地方。代理类用于将对象的创建推迟到实际需要时,并且它们通常在内部涉及对象管理器的使用。
  3. 在引导代码或一次性脚本中– 在依赖注入系统完全初始化之前,或者在极少数情况下(例如自定义命令行脚本),DI 并不实用。然而,即使在这些情况下,您也应该有充分的理由解释为什么您编写了一次性 CLI 脚本,而不是将其创建为标准的可执行 CLI 命令

发表评论