Magento 2:プロキシクラスとは何かの実用的な説明


17

だから、Magento 2のプロキシクラスとは理論的には知っています。それについての素晴らしいAlan Stormの記事を読みそれらのクラスがどのように生成されるかを完全に理解しています。

しかし、それは私が英語を母国語としないからなのか、アランの説明が非常に抽象的な非コアクラスを使用しているのかわからないが、それがどのように機能するのか、特に使用するタイミングを理解するのに苦労している開発中。

それでは、次のコアからこの例を見てみましょうapp/code/Magento/GoogleAdwords/etc/di.xml

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2016 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\GoogleAdwords\Observer\SetConversionValueObserver">
        <arguments>
            <argument name="collection" xsi:type="object">Magento\Sales\Model\ResourceModel\Order\Collection\Proxy</argument>
        </arguments>
    </type>
</config>

私が知りたいのですが:

  • なぜ特定のケースでプロキシクラスが使用されるのですか?
  • 一般に、プロキシクラスを使用する必要がある場合

回答:


17

この特定の使用法は、プロキシパターンを使用する良い例ではありません。loadメソッドが呼び出されない限り、コレクションはDB操作を実行しないため、その特定のコードでは役に立たないと思います。それらのオブザーバーが依存関係としてコンソールコマンドクラスで使用される場合、プロキシを使用することは理にかなっています。

プロキシクラスは、オブジェクトの構築中に高価な操作を実行する場合にのみ使用してください。良い例はSymfonyコンソールコマンドです:

コンソールコマンドが依存関係としてProductRepositoryを使用していると想像してください。製品リポジトリコンストラクターは、カタログデータベースへのMySQL接続を確立します。

つまり、bin/magentoどのコマンドを実行しても、どのコマンドを実行しても、リポジトリの依存関係がインスタンス化されます。したがって、それを回避する唯一の方法は、プロキシを作成して、元のオブジェクトの遅延インスタンス化を使用することです。この場合、データベースは、リポジトリメソッドを呼び出すときにのみカタログデータベースへの接続が確立されます。

プロキシのアイデアをよりよく理解するのに役立つことを願っています。


1
私が選んだ例が役に立たないという事実は、私をさらに混乱させました。理論的にも、概念を理解しています。しかし、私が取得できないのは、すべてのコマンドに使用しないのに、なぜコンソールコマンドへの依存関係としてProductRepositoryを追加するのでしょうか。使用するコマンドだけの依存関係ではありませんか?あなたが言ったことによると、プロキシは依存関係を「スキップ」する方法ですか?しかし、その場合、そもそもなぜ依存関係なのでしょうか?
デジタルピアニズムのラファエル

1
Symfonyコンソールコマンドはすばらしい例であると思います。Magentoから話をする必要があり、そのための唯一の方法はコンストラクターで依存関係を指定することです。Symfonyコンソールコンポーネントでは、コマンドごとにクラスを作成する必要があります。このクラスには、設定メソッドと実行メソッドがあります。Configureは名前と引数を設定しますが、executeは実際には高価な操作を実行しています。configureでコストのかかる操作が実行されると、ねじ込みよりも、プロキシがこの問題の答えになる理由です。
イワン・チェプルニー

13

プロキシクラスを使用すると、必ずしも必要ではないクラスに依存関係を挿入できます。このクラスは、そうすることに関連するコストが高くなります。

Magentoが生成したプロキシ(たとえば)を見る\Magento\Framework\View\Layout\Proxyと、元のクラスと同じメソッドがすべてあることがわかります。違いは、これらのいずれかが呼び出されるたびに、プロキシであるクラスが実際にインスタンス化されているかどうかをチェックし、そうでない場合はオブジェクトを作成することです。(これは_getSubject()or _getCache()メソッドで発生します。)

依存性注入の遅延読み込みです。

クラスの依存関係が常にクラスで使用されるわけではない場合、プロキシを使用する必要があります。

  • 独自の多くの依存関係を持っている、または
  • そのコンストラクターは、リソースを集中的に使用するコード、または
  • 注入すると副作用があります

この好例の1つがセッションです。ObjectManagerを介してセッションを取得するのは悪い習慣ですが、次のようなセッションクラスを注入します\Magento\Customer\Model\Session、クラスがそのセッションの範囲外で実行されると、セッションクラスを挿入すると問題が発生する可能性があります(たとえば、管理ページでフロントエンドカスタマーセッションを挿入します)。\Magento\Customer\Model\Session\Proxy代わりにセッションのプロキシを挿入し、有効であることがわかっている場合にのみそれを参照することで回避できます。参照しない限り、セッションはインスタンス化されず、何も中断しません。

の特定の例ではdi.xml、プロキシを使用して、コントローラのファクトリではなくコントローラの注入を正当化するように見えます。いずれにせよ、それはプロキシが使用されることを意図したものではなく、その状況でのプロキシの利点はおそらく最小です。


7

Magento 2タイプの自動生成プロキシは、設計ミスを「修正」するために使用できます。それは非常に便利です。2つの使用例があります。

  1. 依存先が毎回必要としない可能性がある高価なオブジェクトグラフをラップします。

  2. クラスがA依存しB、クラスがB依存する循環依存関係を解除しAます。
    注入B\ProxyAあなたはインスタンス化できA、その後順番にインスタンス化するために使用することができ、Bそれが実際に本当で使用されたときにAオブジェクト。

1.の場合、常に使用されるとは限らない依存関係は、依存先クラスが多くのことを行うか、1つのメソッドで多くのことを行うことを示します。@ivanが言及したコンソールコマンドは、その良い例です。

以下の場合2.私はその依存関係を壊すための一般的な方法を知りません。時間があれば書き直す傾向がありますが、それは選択肢ではないかもしれません。

補足として、Magento 2が使用する自動生成された遅延インスタンス化(リモートプロキシなど)よりも多くの種類のプロキシがOOPに存在することを付け加えます。


@vinaiさん、__ constructor()メソッドまたはdi.xml経由でプロキシクラスを使用する方法は何ですか?
akgola

1
Magentoコーディングガイドラインセクション2.5によれば、クラスコンストラクターでプロキシを宣言してはなりません。di.xmlでプロキシを宣言する必要があります。devdocs.magento.com/guides/v2.3/coding-standards/…を
Vinai

1

ここに答えがあります

なぜ特定のケースでプロキシクラスが使用されるのですか?

クラス「SetConversionValueObserver」用に記述されたコードの下をよく見ると、Google adwardsがアクティブでない場合は「return」で、注文がない場合は「return」です。つまり、注文IDが存在し、Googleアドワーズ広告がアクティブな場合にのみ、注文コレクションオブジェクトが作成されます。実際のOrderコレクションクラスを挿入すると、オブジェクトマネージャーは、Google adwordsがアクティブでないことを認識せずに親クラスオブジェクトを使用してコレクションオブジェクトを作成し、オーダー成功ページを遅くします。そのため、プロキシを使用するオブジェクトをオンデマンドで作成する方が適切です。/vendor/magento/module-google-adwords/Observer/SetConversionValueObserver.php

 /**
 * Set base grand total of order to registry
 *
 * @param \Magento\Framework\Event\Observer $observer
 * @return \Magento\GoogleAdwords\Observer\SetConversionValueObserver
 */
public function execute(\Magento\Framework\Event\Observer $observer)
{
    if (!($this->_helper->isGoogleAdwordsActive() && $this->_helper->isDynamicConversionValue())) {
        return $this;
    }
    $orderIds = $observer->getEvent()->getOrderIds();
    if (!$orderIds || !is_array($orderIds)) {
        return $this;
    }
    $this->_collection->addFieldToFilter('entity_id', ['in' => $orderIds]);
    $conversionValue = 0;
    /** @var $order \Magento\Sales\Model\Order */
    foreach ($this->_collection as $order) {
        $conversionValue += $order->getBaseGrandTotal();
    }
    $this->_registry->register(
        \Magento\GoogleAdwords\Helper\Data::CONVERSION_VALUE_REGISTRY_NAME,
        $conversionValue
    );
    return $this;
}

一般に、プロキシクラスを使用する必要がある場合 -オブジェクトの作成に費用がかかり、クラスのコンストラクターが特にリソースを消費する場合、プロキシクラスを挿入します。-オブジェクトの作成によるパフォーマンスへの不必要な影響を望まない場合。-特定の条件で特定のメソッドを常に呼び出すとは限らないときにオブジェクトの作成が発生する必要があると感じる場合。たとえば、レイアウトコンストラクターはリソースを大量に消費します。

実際のレイアウトコンストラクター対レイアウト/プロキシ

public function __construct(
    Layout\ProcessorFactory $processorFactory,
    ManagerInterface $eventManager,
    Layout\Data\Structure $structure,
    MessageManagerInterface $messageManager,
    Design\Theme\ResolverInterface $themeResolver,
    Layout\ReaderPool $readerPool,
    Layout\GeneratorPool $generatorPool,
    FrontendInterface $cache,
    Layout\Reader\ContextFactory $readerContextFactory,
    Layout\Generator\ContextFactory $generatorContextFactory,
    AppState $appState,
    Logger $logger,
    $cacheable = true,
    SerializerInterface $serializer = null
) {
    $this->_elementClass = \Magento\Framework\View\Layout\Element::class;
    $this->_renderingOutput = new \Magento\Framework\DataObject();
    $this->serializer = $serializer ?: ObjectManager::getInstance()->get(SerializerInterface::class);

    $this->_processorFactory = $processorFactory;
    $this->_eventManager = $eventManager;
    $this->structure = $structure;
    $this->messageManager = $messageManager;
    $this->themeResolver = $themeResolver;
    $this->readerPool = $readerPool;
    $this->generatorPool = $generatorPool;
    $this->cacheable = $cacheable;
    $this->cache = $cache;
    $this->readerContextFactory = $readerContextFactory;
    $this->generatorContextFactory = $generatorContextFactory;
    $this->appState = $appState;
    $this->logger = $logger;
}

プロキシコンストラクターを見てみましょう。親コンストラクターは呼び出されません。また、メソッドが呼び出されたときに実際のオブジェクト作成が行われるように、単にレイアウトクラス名を渡します。

 /**
 * Proxy constructor
 *
 * @param \Magento\Framework\ObjectManagerInterface $objectManager
 * @param string $instanceName
 * @param bool $shared
 */
public function __construct(
    \Magento\Framework\ObjectManagerInterface $objectManager,
    $instanceName = \Magento\Framework\View\Layout::class,
    $shared = true
) {
    $this->_objectManager = $objectManager;
    $this->_instanceName = $instanceName;
    $this->_isShared = $shared;
}

プロキシクラスにはオンデマンドでオブジェクトを作成するメソッドがあり、_subjectは渡されたクラスのオブジェクトです。

/**
 * Get proxied instance
 *
 * @return \Magento\Framework\View\Layout
 */
protected function _getSubject()
{
    if (!$this->_subject) {
        $this->_subject = true === $this->_isShared
            ? $this->_objectManager->get($this->_instanceName)
            : $this->_objectManager->create($this->_instanceName);
    }
    return $this->_subject;
}

そして、_subjectを使用して呼び出されるメソッド。

/**
 * {@inheritdoc}
 */
public function setGeneratorPool(\Magento\Framework\View\Layout\GeneratorPool $generatorPool)
{
    return $this->_getSubject()->setGeneratorPool($generatorPool);
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.