Иногда по тем или иным причинам контент менеджерам необходимо конвертировать тот или иной продукт из Simple в Virtual или наоборот, сменить ему атрибут сет и так далее… Чаще всего это ложится на плечи разработчиков. И тут я хотел бы описать 1 новую и очень интересную особенность Magento 2, которая никак не описана в официальной документации.
Для изменения типа продукта в Magento 1.* контент менеджерам пришлось бы создавать новые продукты вручную, делать их копиями оригинальных товаров и так далее или просить разработчиков сделать это через код. В Magento 2.* внедрена новая «особенность» для достижения этой цели. Прежде же чем разобраться с ней (я назову ее «авто конверсия») вам следует иметь в виду несколько моментов, для получения правильных результатов.
Давайте посмотрим в админ панель, вернее как это работает с точки зрения контент менеджера. Как и прежде вы можете выбрать тип нового товара до того, как вы его создадите.
В Magento 1 это отображается на Admin > Catalog > Manage Products > Страница выбора атрибут сета и типа продукта после нажатия на “Add Product”.

В Magento 2 для этого на странице Admin > Products > Catalog нажмите на стрелку с права от кнопки «Add Product».

После выбора необходимого типа Magento покажет вам страницу редактирования нового товара с предопределенными опциями и/или новыми секциями для выбранного типа. Если вы выберите тип продукта Simple product или просто нажмете на “Add Product” то увидите страницу редактирования Simple продукта, но обратите внимание на следующие 3 вещи:

Эти 3 секции определяют тип продукта.
Magento 2 может автоматически (при сохранении) изменять типы продуктов как у новых так и уже существующих продуктов следующих товаров:
Рассмотрим, что для этого необходимо сделать.
Simple и Virtual продукты

Если вы установите вес «Weight» = «The item has no weight» (именно нет веса, а не удалите просто значение из инпут поля) то продукт будет сохранен как Virtual продукт, но как только вы вернетесь на страницу редактирования товара и укажите, что у товара есть вес, то он будет конвертирован в Simple продукт сразу же после сохранения.
Downloadable продукт

Для того, что превратить Simple продукт в Downloadable вам нужно установить «Weight» = «The item has no weight» и ниже в секции «Downloadable Information» добавить 1 или более ссылок. Чтобы превратить этот товар обратно в Simple продукт достаточно установить «Weight» = «The item has weight» и сохранить продукт.
Configurable продукт

Для превращения Simple продукта в Configurable, достаточно добавить любой дочерний товар к нему как указано выше. А для того, чтобы сделать его снова Simple продуктом достаточно удалить все конфигурации и нажать на save. Как вы уже поняли, это значит, что в Magento 2 не может быть Configurable продукта без детей ( как это было в Magento 1).
Вот собственно и все.
Перед тем как использовать авто конверсию в конфигурируемый товар надо иметь в виду, что в Magento 2 изменен принцип получения цены от Configurable продукта. В Magento 1 Configurable продукты имели свою собственную цену и полностью игнорировали цену связанных с ними продуктов. В Magento 2 применен другой подход. Сейчас Configurable продукты используют рассчитанную цену из привязанных детей. Обратной стороной медали является то, что при удалении всех привязанных продуктов товар конвертируется в Simple продукт, но если удалить все связанные с ним продукты с Grid страницы в админ панели, то продукт не конвертируется ( почему это так, я опишу позже ). Учитывая новую логику, то что товар является Configurable только если у него есть дети, в противном случае он «конвертируется» в Simple продукт, это может привести к некоторым не предвидимым ошибкам в коде, особенно если вы работаете с импортом товаров.
* В Magento 2.1 некоторые из этих ошибок исправлены и при прямом обращении к методу $product->getFinalPrice() теперь выбрасывается исключение
Теперь давайте поговорим о том, как именно все это работает. Я бы хотел начать с самого главного, с класса Magento\Catalog\Model\Product\TypeTransitionManager.
Это достаточно простой класс, который обладает 2мя методами __construct и processProduct. Вся магия заключена во втором методе, processProduct($product). Как вы видите, он проверяет есть ли тип переданного продукта в массиве совместимых типов и если продукт имеет вес то это Simple продукт, если нет то Virtual продукт.
В Magento 2.1. как вы уже догадались, совместимы следующие типы:
Все они передаются через di.xml
Это значит, что только эти типы могут быть конвертированы в Simple или Virtual продукты.
Как было сказано выше, Simple продукт может быть конвертирован в конфигурируемый и загружаемый товар так же. Вся магия в следующих классах (плагинах):
Magento\Downloadable\Model\Product\TypeTransitionManager\Plugin\Downloadable::aroundProcessProduct() проверяет, что тип продукта Simple, Virtual, Downloadable, с фронтенда были постом отправлены данные с ключем downloadable и то, что этот продукт не имеет веса. Если это так, то он Downloadable, в противном случае запускается оригинальный метод.
Magento\ConfigurableProduct\Model\Product\TypeTransitionManager\Plugin\Configurable::aroundProcessProduct() плагин меняет тип продукта на “configurable” если в реквесте имеются данные с ключем “attributes”, в противном случае запускается оригинальный метод.
Если вы попробуете сохранить товар из админ панели, как было написано выше, то он сменит свой тип на 1 из указанных выше благодаря этому коду, но если просто попробовать сохранить модель продукта через метод save() то ничего не изменится. Дело в контроллере, который обрабатывает сохранение товаров.
Magento\Catalog\Controller\Adminhtml\Product\Save::execute() использует инстанс класса Magento\Catalog\Model\Product\TypeTransitionManager
Как вы видите конвертирование товара никак не привязано к методу save() и вызывается непосредственно перед созданием продукта.
Для пользователей и разработчиков, я думаю стало чуть понятнее как именно работает новая особенность Magento 2. Меня удивляет, что за все время существования Magento 2 ни где не написали о ней.
Так же хочется добавить, что в Magento 2 достаточно просто добавить новые типы товаров и если вам может понадобиться позволить пользователям конвертировать их между собой то вам достаточно будет добавить пару строчек в di.xml вашего модуля и создать свой собственный плагин.
Для изменения типа продукта в Magento 1.* контент менеджерам пришлось бы создавать новые продукты вручную, делать их копиями оригинальных товаров и так далее или просить разработчиков сделать это через код. В Magento 2.* внедрена новая «особенность» для достижения этой цели. Прежде же чем разобраться с ней (я назову ее «авто конверсия») вам следует иметь в виду несколько моментов, для получения правильных результатов.
Авто конверсия с точки зрения пользователя
Давайте посмотрим в админ панель, вернее как это работает с точки зрения контент менеджера. Как и прежде вы можете выбрать тип нового товара до того, как вы его создадите.
В Magento 1 это отображается на Admin > Catalog > Manage Products > Страница выбора атрибут сета и типа продукта после нажатия на “Add Product”.

В Magento 2 для этого на странице Admin > Products > Catalog нажмите на стрелку с права от кнопки «Add Product».

После выбора необходимого типа Magento покажет вам страницу редактирования нового товара с предопределенными опциями и/или новыми секциями для выбранного типа. Если вы выберите тип продукта Simple product или просто нажмете на “Add Product” то увидите страницу редактирования Simple продукта, но обратите внимание на следующие 3 вещи:

- Атрибут Вес (Weight)
- Секция Configurations
- Секция Downloadable Information
Эти 3 секции определяют тип продукта.
Magento 2 может автоматически (при сохранении) изменять типы продуктов как у новых так и уже существующих продуктов следующих товаров:
Рассмотрим, что для этого необходимо сделать.
Simple и Virtual продукты

Если вы установите вес «Weight» = «The item has no weight» (именно нет веса, а не удалите просто значение из инпут поля) то продукт будет сохранен как Virtual продукт, но как только вы вернетесь на страницу редактирования товара и укажите, что у товара есть вес, то он будет конвертирован в Simple продукт сразу же после сохранения.
Downloadable продукт

Для того, что превратить Simple продукт в Downloadable вам нужно установить «Weight» = «The item has no weight» и ниже в секции «Downloadable Information» добавить 1 или более ссылок. Чтобы превратить этот товар обратно в Simple продукт достаточно установить «Weight» = «The item has weight» и сохранить продукт.
Configurable продукт

Для превращения Simple продукта в Configurable, достаточно добавить любой дочерний товар к нему как указано выше. А для того, чтобы сделать его снова Simple продуктом достаточно удалить все конфигурации и нажать на save. Как вы уже поняли, это значит, что в Magento 2 не может быть Configurable продукта без детей ( как это было в Magento 1).
Вот собственно и все.
На заметку
Перед тем как использовать авто конверсию в конфигурируемый товар надо иметь в виду, что в Magento 2 изменен принцип получения цены от Configurable продукта. В Magento 1 Configurable продукты имели свою собственную цену и полностью игнорировали цену связанных с ними продуктов. В Magento 2 применен другой подход. Сейчас Configurable продукты используют рассчитанную цену из привязанных детей. Обратной стороной медали является то, что при удалении всех привязанных продуктов товар конвертируется в Simple продукт, но если удалить все связанные с ним продукты с Grid страницы в админ панели, то продукт не конвертируется ( почему это так, я опишу позже ). Учитывая новую логику, то что товар является Configurable только если у него есть дети, в противном случае он «конвертируется» в Simple продукт, это может привести к некоторым не предвидимым ошибкам в коде, особенно если вы работаете с импортом товаров.
* В Magento 2.1 некоторые из этих ошибок исправлены и при прямом обращении к методу $product->getFinalPrice() теперь выбрасывается исключение
Fatal error: Uncaught exception 'Magento\Framework\Exception\LocalizedException' with message 'Configurable product "…sku…" does not have sub-products' in vendor\magento\module-configurable-product\Pricing\Price\ConfigurablePriceResolver.php:52
Для разработчиков
Теперь давайте поговорим о том, как именно все это работает. Я бы хотел начать с самого главного, с класса Magento\Catalog\Model\Product\TypeTransitionManager.
... public function __construct( \Magento\Catalog\Model\Product\Edit\WeightResolver $weightResolver, array $compatibleTypes ) { $this->compatibleTypes = $compatibleTypes; $this->weightResolver = $weightResolver; } public function processProduct(Product $product) { if (in_array($product->getTypeId(), $this->compatibleTypes)) { $product->setTypeInstance(null); $productTypeId = $this->weightResolver->resolveProductHasWeight($product) ? Type::TYPE_SIMPLE : Type::TYPE_VIRTUAL; $product->setTypeId($productTypeId); } } ...
Это достаточно простой класс, который обладает 2мя методами __construct и processProduct. Вся магия заключена во втором методе, processProduct($product). Как вы видите, он проверяет есть ли тип переданного продукта в массиве совместимых типов и если продукт имеет вес то это Simple продукт, если нет то Virtual продукт.
В Magento 2.1. как вы уже догадались, совместимы следующие типы:
- Simple
- Virtual
- Downloadable
- Configurable
Все они передаются через di.xml
... <type name="Magento\Catalog\Model\Product\TypeTransitionManager"> <arguments> <argument name="compatibleTypes" xsi:type="array"> <item name="simple" xsi:type="const">Magento\Catalog\Model\Product\Type::TYPE_SIMPLE</item> <item name="virtual" xsi:type="const">Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL</item> </argument> </arguments> </type> ...
Это значит, что только эти типы могут быть конвертированы в Simple или Virtual продукты.
Как было сказано выше, Simple продукт может быть конвертирован в конфигурируемый и загружаемый товар так же. Вся магия в следующих классах (плагинах):
- Magento\Downloadable\Model\Product\TypeTransitionManager\Plugin\Downloadable
- Magento\ConfigurableProduct\Model\Product\TypeTransitionManager\Plugin\Configurable
Magento\Downloadable\Model\Product\TypeTransitionManager\Plugin\Downloadable::aroundProcessProduct() проверяет, что тип продукта Simple, Virtual, Downloadable, с фронтенда были постом отправлены данные с ключем downloadable и то, что этот продукт не имеет веса. Если это так, то он Downloadable, в противном случае запускается оригинальный метод.
... public function aroundProcessProduct( \Magento\Catalog\Model\Product\TypeTransitionManager $subject, Closure $proceed, \Magento\Catalog\Model\Product $product ) { $isTypeCompatible = in_array( $product->getTypeId(), [ \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE, \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL, \Magento\Downloadable\Model\Product\Type::TYPE_DOWNLOADABLE ] ); $downloadableData = $this->request->getPost('downloadable'); $hasDownloadableData = false; if (isset($downloadableData)) { foreach ($downloadableData as $data) { foreach ($data as $rowData) { if (empty($rowData['is_delete'])) { $hasDownloadableData = true; break 2; } } } } if ($isTypeCompatible && $hasDownloadableData && !$this->weightResolver->resolveProductHasWeight($product)) { $product->setTypeId(\Magento\Downloadable\Model\Product\Type::TYPE_DOWNLOADABLE); return; } $proceed($product); } ...
Magento\ConfigurableProduct\Model\Product\TypeTransitionManager\Plugin\Configurable::aroundProcessProduct() плагин меняет тип продукта на “configurable” если в реквесте имеются данные с ключем “attributes”, в противном случае запускается оригинальный метод.
... public function aroundProcessProduct( \Magento\Catalog\Model\Product\TypeTransitionManager $subject, Closure $proceed, \Magento\Catalog\Model\Product $product ) { $attributes = $this->request->getParam('attributes'); if (!empty($attributes)) { $product->setTypeId(\Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE); return; } $proceed($product); } ...
Как все это работает
Если вы попробуете сохранить товар из админ панели, как было написано выше, то он сменит свой тип на 1 из указанных выше благодаря этому коду, но если просто попробовать сохранить модель продукта через метод save() то ничего не изменится. Дело в контроллере, который обрабатывает сохранение товаров.
Magento\Catalog\Controller\Adminhtml\Product\Save::execute() использует инстанс класса Magento\Catalog\Model\Product\TypeTransitionManager
... $product = $this->initializationHelper->initialize($this->productBuilder->build($this->getRequest())); $this->productTypeManager->processProduct($product); … $product->save(); ...
Как вы видите конвертирование товара никак не привязано к методу save() и вызывается непосредственно перед созданием продукта.
Вместо заключения
Для пользователей и разработчиков, я думаю стало чуть понятнее как именно работает новая особенность Magento 2. Меня удивляет, что за все время существования Magento 2 ни где не написали о ней.
Так же хочется добавить, что в Magento 2 достаточно просто добавить новые типы товаров и если вам может понадобиться позволить пользователям конвертировать их между собой то вам достаточно будет добавить пару строчек в di.xml вашего модуля и создать свой собственный плагин.
