Magento 2: практичне пояснення, що таке клас проксі?


17

Отже, я теоретично знаю, що таке клас проксі в Magento 2. Я прочитав про це дивовижну статтю про Алана Шторма, і я повністю розумію, як створюються ці класи.

Однак я не знаю, чи це тому, що я не є носієм англійської мови, чи якщо в поясненнях Алана використовуються неосновні класи, які є дуже абстрактними, але мені важко зрозуміти, як це працює, і особливо, коли використовувати це під час розвитку.

Тож візьмемо цей приклад із основної у 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

Це використання не є хорошим прикладом використання шаблону проксі. Я думаю, що це навіть марно в цьому конкретному фрагменті коду, оскільки колекція не виконує жодних операцій з БД, якщо не буде викликаний метод завантаження. Якщо їхній спостерігач буде використовуватися в консольному командному класі як залежність, то має сенс використовувати проксі.

Клас проксі слід використовувати лише тоді, коли під час побудови об'єкта ви виконуєте дорогу операцію. Хороший приклад - команди консолі Symfony:

Уявіть, що ваша команда консолі використовує ProductRepository як залежність. Конструктор продуктового сховища встановлює з'єднання MySQL з базою даних каталогу.

Це означає, що при кожному bin/magentoвиклику, незалежно від того, яку команду ви виконуєте, залежність сховища буде створена. Тож єдиний спосіб уникнути цього - використовувати ледачу інстанцію оригінального об'єкта, створивши проксі. У цьому випадку база даних підключення до бази даних каталогу буде встановлено лише тоді, коли ви викликаєте метод сховища.

Сподіваємось, що допоможе краще зрозуміти ідею проксі.


1
Те, що обраний нами приклад є марним, ще більше збентежило мене. Знову теоретично я розумію концепцію. Але чого я не отримую: навіщо ви додаєте ProductRepository як залежність до команди консолі, якщо ви не використовуєте її для кожної команди. Чи не може це бути залежність лише від команд, які ви використовуєте? Згідно з тим, що ви сказали, проксі - це спосіб "пропустити" залежність? Але в такому випадку, чому це в першу чергу залежність?
Рафаель у Digital Pianism

1
Я думаю, що команда консолі Symfony є прекрасним прикладом, оскільки ви повинні з нею поговорити з Magento, і єдиний спосіб зробити це - вказати залежність у конструкторі. У компоненті консолі Symfony ви повинні створити клас для кожної команди. Цей клас має методи налаштування та виконання. Налаштування задає своє ім’я та аргументи, а 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. Я не знаю загального способу подолання цієї залежності. Я схильний переписувати, якщо є час, але це може бути не варіантом.

Як бічну зауваження, я хотів би додати, що в OOP існує набагато більше типів проксі-серверів, ніж автогенерована лінива інстанція, яку використовує Magento 2 (наприклад, віддалений проксі).


Привіт @ vinai, яким чином можна використовувати проксі-класи за допомогою методу __constructor () або через di.xml.?
akgola

1
Згідно з правилами кодування Magento, розділ 2.5 проксі не повинен бути оголошений у конструкторах класів. Проксі повинні бути оголошені у di.xml. Див devdocs.magento.com/guides/v2.3/coding-standards / ...
Vinai

1

Ось відповіді

чому клас проксі використовується саме в цьому випадку?

Якщо ви уважно подивіться нижче коду, який написаний для класу "SetConversionValueObserver", якщо Google adwards не активний, "повернутися" і якщо немає замовлення "повернути". Значить, Об'єкт колекції замовлень буде створений лише тоді, коли існують ідентифікатори замовлення та активовано Google adwords. якщо ми вводимо фактичний клас колекції замовлень, тоді менеджер об'єктів створює об’єкт колекції з об'єктами свого батьківського класу, не знаючи, що 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);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.