Magento 2 provides 6 product types including simple, grouped, configurable, virtual, bundled and downloadable having unique behaviors and attributes.
In a such kind of requirement where your product class has distinct behavior or attributes then it should be represented by its own custom product type. This allows the product type to have complex and custom logic and presentation, with no impact on other product types, ensuring that default product types can continue to function as intended.
In this post, I will be guiding you on how to create a new Magento 2 product type.
You can find the complete module in Github at Magelearn_BookProductType
Let's start by creating a custom module.
Create a folder inside app/code/Magelearn/BookProductType
Add registration.php file in it:
<?php \Magento\Framework\Component\ComponentRegistrar::register( \Magento\Framework\Component\ComponentRegistrar::MODULE, 'Magelearn_BookProductType', __DIR__ );
Add composer.json file in it:
{ "name": "magelearn/magento2-book-product-type", "description": "Custom product types for magento 2", "license": "proprietary", "authors": [ { "name": "vijay rami", "email": "vijaymrami@gmail.com" } ], "require": {}, "type": "magento2-module", "autoload": { "files": [ "registration.php" ], "psr-4": { "Magelearn\\BookProductType\\": "" } } }
Add etc/module.xml file in it:
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> <module name="Magelearn_BookProductType" setup_version="1.0.2"> <sequence> <module name="Magento_Catalog"/> </sequence> </module> </config>
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Catalog:etc/product_types.xsd"> <type name="book" label="Book Product Type" modelInstance="Magelearn\BookProductType\Model\Product\Type\Bookproducttype" indexPriority="0" sortOrder="80" isQty="true"> <customAttributes> <attribute name="refundable" value="true"/> </customAttributes> <priceModel instance="Magelearn\BookProductType\Model\Product\Price" /> </type> <composableTypes> <type name="book" /> </composableTypes> </config>
In type, we can define the following attributes.
- name: new product code (book)
- label: product name (Book Product Type)
- modelInstance: new product model
- indexPriority: priority index
- isQty: whether it has a quantity
- sortOrder: position number in sort list
- canUseQtyDecimals: whether the quantity is fractional
Also we are capable to override such internal models:
- priceModel: model of a price
- indexerModel: model of indexing logic
- stockIndexerModel: model of stock indexing
Composite Products
When you are adding child products to a composite product type like configurable, grouped or bundle, it will only show products where the associated product type has explicitly declared its eligibility to be the child of a composite product.
So new product types can allow themselves to be children of composite product types by adding a composableTypes node in product_types.xml
Custom Attributes Node
The customAttributes nodes can be used when Magento wants to get a list of product types that comply with the condition.
Inside this node, we can declare some attributes nodes:
- refundable: It will check the product type, it can be refunded or not. By default Magento CE, the downloadable and virtual product cannot be refunded (In Magento EE added Gift product). The product type in sales order item table will be used for checking the refundable product.
- taxable: It will check the product type, it is taxable or not.
- Is_real_product: It is used for checking “it is a real product or not”. Group product, Downloadable Product, Virtual Product are not real products.
- is_product_set: checks whether product type is a set of products. In group product is_product_set value is set to true.
Product Type Model
Each product type instance is associated with an instance of the corresponding product type model. Here in this Product model calss, you can modify product type behaviour and attributes and is called during several product manipulation processes. You can apply your custom logic in the product type model.
Create the app/code/Magelearn/BookProductType/Model/Product/Type/Bookproducttype.php file
<?php declare(strict_types=1); namespace Magelearn\BookProductType\Model\Product\Type; use Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product\Type\AbstractType; /** * Book product type implementation */ class Bookproducttype extends AbstractType { /** * Product type code */ const TYPE_CODE = 'book'; public function save($product) { parent::save($product); // your additional saving logic return $this; } /** * Delete data specific for Simple product type * * @param \Magento\Catalog\Model\Product $product * @return void */ public function deleteTypeSpecificData(Product $product) { } }
A product model must inherit from the \Magento\Catalog\Model\Product\Type\AbstractType class. This class has only one abstract method and it is deleteTypeSpecificData. This specific method is called while saving a product instance if its type has changed, and gives the original product type the opportunity to clean up any type-specific data before the type change is finalized.
Unless the new custom product type has such type-specific data, the method can be overridden with an empty method.
Core Product Types
- Simple: \Magento\Catalog\Model\Product\Type\Simple
- Virtual: \Magento\Catalog\Model\Product\Type\Virtual
- Configurable: \Magento\ConfigurableProduct\Model\Product\Type\Configurable
- Grouped: \Magento\GroupedProduct\Model\Product\Type\Grouped
- Downloadable: \Magento\Downloadable\Model\Product\Type
- Bundle: \Magento\Bundle\Model\Product\Type
- Giftcard (Enterprise Edition Only): \Magento\GiftCard\Model\Catalog\Product\Type\Giftcard
Product Type Model Methods
- beforeSave() : This method runs during the beforeSave() products of the given type.
- save(): This method runs during the afterSave() products of the given type.
- isSalable(): This method allows a custom product type to customize product saleability.
- _prepareProduct(): This method provides a hook to interact with the information by request when a product is initially added to the cart.
- isVirtual(): By default it is false. If your product type is like virtual type then you can use this function and return true.
Associate With Common Attributes
<?php namespace Magelearn\BookProductType\Setup; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\ModuleDataSetupInterface; use Magento\Framework\Setup\InstallDataInterface; use Magento\Eav\Setup\EavSetupFactory; /** * Class InstallData * @package Magelearn\BookProductType\Setup * * @codeCoverageIgnore */ class InstallData implements InstallDataInterface { /** * @var EavSetupFactory */ protected $_eavSetupFactory; public function __construct( EavSetupFactory $eavSetupFactory ) { $this->_eavSetupFactory = $eavSetupFactory; } /** * {@inheritdoc} * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @SuppressWarnings(PHPMD.NPathComplexity) */ public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context) { $eavSetup = $this->_eavSetupFactory->create(["setup"=>$setup]); $productTypes = 'book'; $eavSetup->addAttribute( \Magento\Catalog\Model\Product::ENTITY, 'isbn', [ 'group' => 'Product Details', 'backend' => '', 'frontend' => '', 'label' => 'ISBN', 'type' => 'text', 'input' => 'text', 'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_STORE, 'visible' => true, 'required' => true, 'user_defined' => true, 'apply_to' => $productTypes, 'visible_on_front' => true, 'used_in_product_listing' => true, 'is_used_in_grid' => true, 'is_visible_in_grid' => false, 'is_filterable_in_grid' => true, ] ); } }
After we will assign core attributes that are already scoped to this new product attribute type.
For that add file at app/code/Magelearn/BookProductType/Setup/UpgradeData.php
<?php namespace Magelearn\BookProductType\Setup; use Magento\Eav\Setup\EavSetup; use Magento\Eav\Setup\EavSetupFactory; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\ModuleDataSetupInterface; use Magento\Framework\Setup\UpgradeDataInterface; class UpgradeData implements UpgradeDataInterface { /** * @var EavSetupFactory */ private $eavSetupFactory; /** * @var Logger */ private $logger; /** * @param EavSetupFactory $eavSetupFactory */ public function __construct(EavSetupFactory $eavSetupFactory, \Psr\Log\LoggerInterface $logger) { $this->eavSetupFactory = $eavSetupFactory; $this->logger = $logger; } public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context) { if (version_compare($context->getVersion(), '1.0.2', '<')) { $this->upgradePriceForBooks($setup); } } public function upgradePriceForBooks(ModuleDataSetupInterface $setup) { /** @var EavSetup $eavSetup */ $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]); //associate these attributes with new product type $fieldList = [ 'price', 'special_price', 'special_from_date', 'special_to_date', 'minimal_price', 'cost', 'tier_price', ]; // make these attributes applicable to book product type foreach ($fieldList as $field) { $applyTo = explode( ',', $eavSetup->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $field, 'apply_to') ); if (!in_array(\Magelearn\BookProductType\Model\Product\Type\Bookproducttype::TYPE_CODE, $applyTo)) { $applyTo[] = \Magelearn\BookProductType\Model\Product\Type\Bookproducttype::TYPE_CODE; $eavSetup->updateAttribute( \Magento\Catalog\Model\Product::ENTITY, $field, 'apply_to', implode(',', $applyTo) ); } } $this->logger->info('Book attributes upgraded'); } }
And at last, we will add our uninstall script.
Add file at app/code/Magelearn/BookProductType/Setup/Uninstall.php
<?php namespace Magelearn\BookProductType\Setup; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\SchemaSetupInterface; use Magento\Framework\Setup\UninstallInterface as UninstallInterface; use Magento\Eav\Setup\EavSetupFactory; use Magento\Framework\Setup\ModuleDataSetupInterface; class Uninstall implements UninstallInterface { /** * EAV setup factory * * @var EavSetupFactory */ private $_eavSetupFactory; private $_mDSetup; /** * Init * * @param EavSetupFactory $eavSetupFactory */ public function __construct( EavSetupFactory $eavSetupFactory, ModuleDataSetupInterface $mDSetup ) { $this->eavSetupFactory = $eavSetupFactory; $this->moduleDataSetup = $mDSetup; } public function uninstall(SchemaSetupInterface $setup, ModuleContextInterface $context) { $this->moduleDataSetup->getConnection()->startSetup(); /** @var EavSetup $eavSetup */ $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]); $eavSetup->removeAttribute(\Magento\Catalog\Model\Product::ENTITY, 'isbn'); $this->moduleDataSetup->getConnection()->endSetup(); } }
Price Model
The price model should extend \Magento\Catalog\Model\Product\Type\Price. This class has no abstract methods which must be implemented but allows extending classes to interact with nearly every aspect of price calculation.
<?php declare(strict_types=1); namespace Magelearn\BookProductType\Model\Product; /** * Class Price * @package Magelearn\BookProductType\Model\Product */ class Price extends \Magento\Catalog\Model\Product\Type\Price { /** * Default action to get price of product * * @param Product $product * @return float */ public function getPrice($product) { return $product->getData('price')*1.25; } }
You can also add indexerModel and stockIndexerModel like priceModel in the product type definition XML.
Now we will check how to render this custom attribute for this custom product type on catalog category and catalog product pages.
To display the custom attribute, ISBN on Catalog category view page,
Add file at app/code/Magelearn/BookProductType/view/frontend/layout/catalog_category_view.xml
<?xml version="1.0"?> <!-- /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ --> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceBlock name="category.product.addto"> <block class="Magelearn\BookProductType\Block\Product\View\Book" name="catalog.product.book.isbn" template="Magelearn_BookProductType::display-isbn.phtml" before="compare" /> </referenceBlock> </body> </page>
Since our custom product type is a 'book' product type,
So that we will add catalog_product_view_type_book.xml file to display this custom attribute on Catalog Product view page.
Add file at app/code/Magelearn/BookProductType/view/frontend/layout/catalog_product_view_type_book.xml
<?xml version="1.0"?> <!-- /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ --> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="product.info.main"> <block class="Magento\Catalog\Block\Product\View" name="product.info.isbn" template="Magelearn_BookProductType::display-isbn.phtml" /> </referenceContainer> </body> </page>
<?php declare(strict_types=1); namespace Magelearn\BookProductType\Block\Product\View; /** * Block for Book in listing page * * @api * @since 100.1.1 */ class Book extends \Magento\Catalog\Block\Product\ProductList\Item\Block { }
<?php $product = $block->getProduct(); $product_type = $product->getTypeId(); ?> <?php if($product_type == 'book'): ?> <div class="isbn"> <label class="label"><span><strong><?= __('ISBN: ')?></strong><?= $block->escapeHtml($product->getIsbn()) ?></span></label> </div> <?php endif; ?>
0 Comments On "How to create a new product type in Magento 2?"