Иногда по тем или иным причинам контент менеджерам необходимо конвертировать тот или иной продукт из 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 вещи:
- Атрибут Вес (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 'MagentoFrameworkExceptionLocalizedException' with message 'Configurable product "…sku…" does not have sub-products' in vendormagentomodule-configurable-productPricingPriceConfigurablePriceResolver.php:52
Для разработчиков
Теперь давайте поговорим о том, как именно все это работает. Я бы хотел начать с самого главного, с класса MagentoCatalogModelProductTypeTransitionManager.
...
public function __construct(
MagentoCatalogModelProductEditWeightResolver $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="MagentoCatalogModelProductTypeTransitionManager">
<arguments>
<argument name="compatibleTypes" xsi:type="array">
<item name="simple" xsi:type="const">MagentoCatalogModelProductType::TYPE_SIMPLE</item>
<item name="virtual" xsi:type="const">MagentoCatalogModelProductType::TYPE_VIRTUAL</item>
</argument>
</arguments>
</type>
...
Это значит, что только эти типы могут быть конвертированы в Simple или Virtual продукты.
Как было сказано выше, Simple продукт может быть конвертирован в конфигурируемый и загружаемый товар так же. Вся магия в следующих классах (плагинах):
- MagentoDownloadableModelProductTypeTransitionManagerPluginDownloadable
- MagentoConfigurableProductModelProductTypeTransitionManagerPluginConfigurable
MagentoDownloadableModelProductTypeTransitionManagerPluginDownloadable::aroundProcessProduct() проверяет, что тип продукта Simple, Virtual, Downloadable, с фронтенда были постом отправлены данные с ключем downloadable и то, что этот продукт не имеет веса. Если это так, то он Downloadable, в противном случае запускается оригинальный метод.
...
public function aroundProcessProduct(
MagentoCatalogModelProductTypeTransitionManager $subject,
Closure $proceed,
MagentoCatalogModelProduct $product
) {
$isTypeCompatible = in_array(
$product->getTypeId(),
[
MagentoCatalogModelProductType::TYPE_SIMPLE,
MagentoCatalogModelProductType::TYPE_VIRTUAL,
MagentoDownloadableModelProductType::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(MagentoDownloadableModelProductType::TYPE_DOWNLOADABLE);
return;
}
$proceed($product);
}
...
MagentoConfigurableProductModelProductTypeTransitionManagerPluginConfigurable::aroundProcessProduct() плагин меняет тип продукта на “configurable” если в реквесте имеются данные с ключем “attributes”, в противном случае запускается оригинальный метод.
...
public function aroundProcessProduct(
MagentoCatalogModelProductTypeTransitionManager $subject,
Closure $proceed,
MagentoCatalogModelProduct $product
) {
$attributes = $this->request->getParam('attributes');
if (!empty($attributes)) {
$product->setTypeId(MagentoConfigurableProductModelProductTypeConfigurable::TYPE_CODE);
return;
}
$proceed($product);
}
...
Как все это работает
Если вы попробуете сохранить товар из админ панели как было написано выше то он сменит свой тип на 1 из указанных выше благодаря этому коду, но если просто попробовать сохранить модель продукта через метод save() то ничего не изменится. Дело в контроллере, который обрабатывает сохранение товаров.
MagentoCatalogControllerAdminhtmlProductSave::execute() использует инстанс класса MagentoCatalogModelProductTypeTransitionManager
...
$product = $this->initializationHelper->initialize($this->productBuilder->build($this->getRequest()));
$this->productTypeManager->processProduct($product);
…
$product->save();
...
Как вы видите конвертирование товара никак не привязано к методу save() и вызывается непосредственно перед созданием продукта.
Вместо заключение
Для пользователей и разработчиков, я думаю стало чуть понятнее как именно работает новая особенность Magento 2. Меня удивляет, что за все время существования Magento 2 ни где не написали о ней.
Так же хочется добавить, что в Magento 2 достаточно просто добавить новые типы товаров и если вам может понадобиться позволить пользователям конвертировать их между собой то вам достаточно будет добавить пару строчек в di.xml вашего модуля и создать свой собственный плагин.
Автор: Oxidant