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

Это конкретное использование не является хорошим примером использования шаблона Proxy. Я думаю, что это даже бесполезно в этом конкретном фрагменте кода, поскольку коллекция не выполняет никаких операций с БД, если не вызывается метод load. Если их наблюдатель будет использоваться в консольном классе команд в качестве зависимости, то имеет смысл использовать прокси.

Прокси-класс следует использовать только тогда, когда во время построения объекта вы выполняете дорогостоящую операцию. Хороший пример - консольные команды Symfony:

Представьте, что ваша консольная команда использует ProductRepository в качестве зависимости. Конструктор репозитория продукта устанавливает соединение MySQL с базой данных каталога.

Это означает, что при каждом bin/magentoвызове, независимо от того, какую команду вы выполняете, создаются зависимости хранилища. Поэтому единственный способ избежать этого - использовать ленивую реализацию исходного объекта путем создания прокси. В этом случае база данных, соединение с базой данных каталога будет установлено только при вызове метода репозитория.

Надеюсь, это поможет лучше понять идею прокси.

Иван Чепурный
источник
1
Тот факт, что выбранный мной пример бесполезен, еще больше запутал меня. Опять теоретически я понимаю концепцию. Но чего я не понимаю: зачем добавлять ProductRepository в качестве зависимости к консольной команде, если вы не используете ее для каждой команды. Разве это не должно зависеть только от команд, которые вы используете? Согласно тому, что вы сказали, Proxy - это способ «пропустить» зависимость? Но в таком случае, почему это зависимость в первую очередь?
Рафаэль на цифровом пианизме
1
Я думаю, что консольная команда Symfony является отличным примером, поскольку вы должны поговорить с Magento из нее, и единственный способ сделать это - указать зависимость в конструкторе. В компоненте консоли Symfony вы должны создать класс для каждой отдельной команды. Этот класс имеет методы настройки и выполнения. Configure устанавливает его имя и аргументы, в то время как execute фактически выполняет дорогостоящую операцию. Если при конфигурировании выполняется дорогостоящая операция, то вы ввернуты, поэтому прокси является ответом на эту проблему.
Иван Чепурный
13

Прокси-класс позволяет вам вводить зависимые классы, которые вам не обязательно нужны, и это связано с большими затратами.

Если вы посмотрите на прокси, сгенерированный Magento \Magento\Framework\View\Layout\Proxy, вы увидите, что он имеет все те же методы, что и исходный класс. Разница в том, что каждый раз, когда вызывается какой-либо из них, он проверяет, был ли создан экземпляр класса, для которого он является прокси, и создает объект, если нет. (Это происходит в _getSubject()или _getCache()метод.)

Это ленивая загрузка для внедрения зависимости.

Вы должны использовать прокси, если зависимость класса не всегда используется вашим классом, и:

  • Имеет много собственных зависимостей или
  • Его конструктор использует ресурсоемкий код, или
  • Инъекция имеет побочные эффекты

Один хороший пример этого - сессии. Получение сессий с помощью ObjectManager - плохая практика, но внедрение такого сессионного класса \Magento\Customer\Model\Sessionможет привести к поломке, если ваш класс когда-либо выйдет за пределы области действия этого сеанса (скажем, вы внедрите сеанс клиента внешнего интерфейса на странице администратора). Вы можете обойти это, введя \Magento\Customer\Model\Session\Proxyвместо этого прокси-сервер сеанса и ссылаясь на него, только когда знаете, что он действителен. Если вы не ссылаетесь на него, сеанс никогда не создается, и ничего не нарушается.

В вашем конкретном примере di.xmlпохоже, что они использовали прокси-сервер для оправдания введения контроллера, а не для фабрики этого контроллера. В любом случае, это не то, для чего предназначены прокси, и выгода от этого в этой ситуации, вероятно, минимальна.

Райан Херр
источник
7

Автоматически сгенерированные прокси типа Magento 2 можно использовать для «исправления» ошибок проектирования. Это может быть очень удобно. Есть 2 варианта использования:

  1. Оберните дорогой объектный граф, который может не понадобиться каждый раз зависимому лицу.

  2. Прервите циклическую зависимость, где класс Aзависит от, Bа класс Bзависит от A.
    Инъекция B\Proxyв Aпозволяет вам создавать экземпляры A, которые затем, в свою очередь, могут использоваться для создания экземпляров, Bкогда они фактически используются с реальным Aобъектом.

В случае 1. зависимость, которая не всегда используется, является признаком того, что класс зависимых делает много или, возможно, делает много одним методом. Упомянутая консольная команда @ivan - хороший тому пример.

В случае 2. Я не знаю общего способа сломать эту зависимость. Я склонен переписывать, если есть время, но это не может быть вариантом.

В качестве дополнительного примечания я хотел бы добавить, что в ООП существует намного больше типов прокси, чем автоматическое создание ленивых экземпляров, которое использует Magento 2 (например, удаленный прокси).

Vinai
источник
Здравствуйте @ vinai, как использовать прокси-классы с помощью метода __constructor () или di.xml.?
Акгола
1
Согласно разделу 2.5 руководств по кодированию в Magento, НЕ ДОЛЖНЫ быть объявлены прокси в конструкторах классов. Прокси ДОЛЖНЫ быть объявлены в di.xml. См devdocs.magento.com/guides/v2.3/coding-standards/...
Vinai
1

Вот ответы

почему прокси-класс используется в этом конкретном случае?

Если вы внимательно посмотрите ниже код, который написан для класса «SetConversionValueObserver», если Google Adwards не активен «возврат» и если нет порядка «возврат». Значит, Объект сбора заказов будет создан только тогда, когда существуют идентификаторы заказов и активны Google AdWords. если мы добавим фактический класс коллекции 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;
}

когда вообще следует использовать прокси-класс? - Введите класс Proxy, когда вы чувствуете, что создание объекта будет дорогостоящим, а конструктор класса особенно ресурсоемким. - когда вы не хотите ненужного влияния на производительность из-за создания объекта. - когда вы чувствуете, что создание объекта должно происходить, когда вы не всегда вызываете определенный метод в определенном состоянии. Например, конструктор Layout является ресурсоемким.

Фактический конструктор макетов против макета / прокси

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);
}
MukeshphpMysql
источник