0

I'm aiming to programmatically create a custom attribute set during setup.

The attribute set should not inherit from the default set, but it should copy some of the default attributes.

For example, the custom attribute set should include a 'Product name' and 'Short description', but it should not contain 'Country of Manufacture' or 'Set product as new from'.

The attribute set should also include some custom attributes. For example, 'maximum order quantity'.

The InstallData.php script is as follows:

<?php

namespace MyOrganisation\MyModule\Setup;

use Magento\Catalog\Model\CategoryRepository;
use Magento\Catalog\Setup\CategorySetup;
use Magento\Catalog\Model\ResourceModel\Category as CategoryResourceModel;
use Magento\Catalog\Model\Category;
use Magento\Catalog\Helper\Category as CategoryHelper;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Catalog\Api\AttributeSetManagementInterface;
use Magento\Catalog\Model\Product;
use Magento\Eav\Api\AttributeGroupRepositoryInterface;
use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Eav\Api\Data\AttributeGroupInterfaceFactory;
use Magento\Eav\Api\Data\AttributeSetInterfaceFactory;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\InstallDataInterface;

class InstallData implements InstallDataInterface
{
    const CATEGORIES = array(
        array(
            'name' => 'Software',
            'description' => '',
            'meta_title' => 'software',
            'meta_keywords' => '',
            'meta_description' => ''
        ),
        array(
            'name' => 'Baby & Toddler',
            'description' => '',
            'meta_title' => 'baby-toddler',
            'meta_keywords' => '',
            'meta_description' => ''
        ),
        array(
            'name' => 'Food, Beverages & Tobacco',
            'description' => '',
            'meta_title' => 'food-beverages-tobacco',
            'meta_keywords' => '',
            'meta_description' => ''
        )
    );

    const PRODUCT_ATTRIBUTES = array(
        'product_id' => array(
            'type' => 'text',
            'input' => 'text',
            'label' => 'Product ID',
            'attribute_group_name' => 'Custom',
            'attribute_set_name' => 'Custom',
            'source' => '',
            'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_GLOBAL,
            'required' => true,
            'filterable' => false,
            'comparable' => false,
            'searchable' => false,
            'unique' => true,
            'visible' => true,
            'visible_on_front' => false,
            'visible_in_advanced_search' => false,
            'is_html_allowed_on_front' => false,
            'used_in_product_listing' => true,
            'user_defined' => true,
            'backend' => '',
        ),
        'limit_checkout_qty' => array(
            'type' => 'int',
            'input' => 'text',
            'label' => 'Maximum order quantity',
            'attribute_group_name' => 'Custom',
            'attribute_set_name' => 'Custom',
            'source' => '',
            'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_GLOBAL,
            'required' => true,
            'filterable' => false,
            'comparable' => false,
            'searchable' => false,
            'unique' => false,
            'visible' => true,
            'visible_on_front' => false,
            'visible_in_advanced_search' => false,
            'is_html_allowed_on_front' => false,
            'used_in_product_listing' => true,
            'user_defined' => true,
            'backend' => ''
        ),
    );

    /**
     * @var CategorySetup;
     */
    private $categorySetup;

    /**
     * @var CategoryResourceModel;
     */
    private $categoryResourceModel;

    /**
     * @var CategoryRepository;
     */
    private $categoryRepository;
    /**
     * @var Category
     */
    private $_category;
    /**
     * @var CategoryHelper
     */
    private $categoryHelper;

    /**
     * @var StoreManagerInterface;
     */
    private $storeManager;

    /**
     * @var EavSetupFactory
     */
    private $eavSetupFactory;
    /**
     * @var Product
     */
    private $product;
    /**
     * @var AttributeSetInterfaceFactory
     */
    private $attributeSetFactory;
    /**
     * @var AttributeSetManagementInterface
     */
    private $attributeSetManagement;
    /**
     * @var AttributeGroupInterfaceFactory
     */
    private $attributeGroupFactory;
    /**
     * @var AttributeGroupRepositoryInterface
     */
    private $attributeGroupRepository;

    /**
     * UpgradeData constructor.
     * @param CategorySetup $categorySetup
     * @param CategoryResourceModel $categoryResourceModel
     * @param CategoryRepository $categoryRepository
     * @param Category $category
     * @param CategoryHelper $categoryHelper
     * @param EavSetupFactory $eavSetupFactory
     * @param Product $product
     * @param AttributeSetInterfaceFactory $attributeSetInterfaceFactory
     * @param AttributeSetManagementInterface $attributeSetManagement
     * @param AttributeGroupInterfaceFactory $attributeGroupFactory
     * @param AttributeGroupRepositoryInterface $attributeGroupRepository
     */
    public function __construct(
        CategorySetup $categorySetup,
        CategoryResourceModel $categoryResourceModel,
        CategoryRepository $categoryRepository,
        Category $category,
        CategoryHelper $categoryHelper,
        EavSetupFactory $eavSetupFactory,
        Product $product,
        AttributeSetInterfaceFactory $attributeSetInterfaceFactory,
        AttributeSetManagementInterface $attributeSetManagement,
        AttributeGroupInterfaceFactory $attributeGroupFactory,
        AttributeGroupRepositoryInterface $attributeGroupRepository

    ) {
        $this->categorySetup = $categorySetup;
        $this->categoryResourceModel = $categoryResourceModel;
        $this->categoryRepository = $categoryRepository;
        $this->_category = $category;
        $this->categoryHelper = $categoryHelper;
        $this->eavSetupFactory = $eavSetupFactory;
        $this->product = $product;
        $this->attributeSetFactory = $attributeSetInterfaceFactory;
        $this->attributeSetManagement = $attributeSetManagement;
        $this->attributeGroupFactory = $attributeGroupFactory;
        $this->attributeGroupRepository = $attributeGroupRepository;
    }

    /**
     * @param ModuleDataSetupInterface $setup
     * @param ModuleContextInterface $context
     */
    public function install(
        ModuleDataSetupInterface $setup,
        ModuleContextInterface $context
    ) {
        ////
        // FRESH INSTALL
        ////
        if (version_compare($context->getVersion(), '0.0.1', '<')) {

            $this->createCategories();

            $this->createProductAttributes();
        }
    }

    private function createCategories()
    {

        // Get all categories which are sub-categories of the default category
        $existing_categories = $this->_category->getCategories(2, 1, false, true, true);
        $existing_category_names = array();

        // Store existing category names in array
        foreach($existing_categories as $existing_category) {
            array_push($existing_category_names, $existing_category->getName());
        }

        foreach (self::CATEGORIES as $category) {

            // Only create a category if it does not already exist
            if (!in_array(
                $category['name'],
                $existing_category_names
            )) {

                $c = clone $this->_category;
                $c->setName($category['name']);
                $c->setParentId(2);
                $c->setIsActive(true);
                $c->setCustomAttributes([
                    'description' => $category['description'],
                    'meta_title' => $category['meta_title'],
                    'meta_keywords' => $category['meta_keywords'],
                    'meta_description' => $category['meta_description'],
                ]);
                $this->categoryRepository->save($c);
            }
        }
    }

    /**
     * Attempt to fetch attribute set. Return ID on success or false on failure.
     * @param EavSetup $eavSetup
     * @param int|string $idOrName
     * @return bool|int
     */
    private function attributeSetExists(
        EavSetup $eavSetup,
        int|string $idOrName,
    ) {
        try {

            $attributeSetId = $eavSetup->getAttributeSetId(
                Product::ENTITY,
                $idOrName
            );

            return $attributeSetId;

        } catch (\Exception $e) {

            return false;
        }
    }

    private function createProductAttributes()
    {
        /** @var EavSetup $eavSetup */
        $eavSetup = $this->eavSetupFactory->create();

        foreach (self::PRODUCT_ATTRIBUTES as $attribute => $data) {

            $attrSetName = null;
            $attributeGroupId = null;

            /**
             * Initialise Attribute Set Id
             */
            if (isset($data['attribute_set_name'])) {

                $attributeSetId = $this->attributeSetExists($eavSetup, $data['attribute_set_name']);

                // If $this->attributeExists() returns false, or if attribute does not exist, create it.
                if (
                    !$attributeSetId ||
                    (
                        is_int($attributeSetId) &&
                        (
                            $attributeSetId == $eavSetup->getDefaultAttributeSetId(Product::ENTITY) &&
                            $data['attribute_set_name'] != 'Default'
                        )
                    )
                ) {
                    $attrSetName = $data['attribute_set_name'];
                    $this->createAttributeSet($attrSetName);
                    $attributeSetId = $eavSetup->getAttributeSetId(Product::ENTITY, $attrSetName);
                }

            } else {
                $attributeSetId = $this->product->getDefaultAttributeSetId();
            }

            /**
             * Initialise Attribute Group Id
             */
            if (isset($data['attribute_group_name'])) {
                $attributeGroupId = $eavSetup->getAttributeGroupId(Product::ENTITY, $attributeSetId, $data['attribute_group_name']);

                /**
                 * If our attribute group name does not exist, we create it
                 */
                if($attributeGroupId == $eavSetup->getDefaultAttributeGroupId(Product::ENTITY) && $data['attribute_group_name'] != 'General') {
                    $attributeGroupName = $data['attribute_group_name'];
                    $this->createAttributeGroup($attributeGroupName, $attrSetName);
                    $attributeGroupId = $eavSetup->getAttributeGroupId(Product::ENTITY, $attributeSetId, $attributeGroupName);
                }
            }

            /**
             * Add attributes to the eav/attribute
             */
            $eavSetup->addAttribute(
                Product::ENTITY,
                $attribute,
                [
                    'type' => $data['type'],
                    'label' => $data['label'],
                    'input' => $data['input'],
                    'group' => $attributeGroupId ? '' : 'General', // Let empty, if we want to set an attribute group id
                    'attribute_set' => $data['attribute_set_name'],
                    'class' => '',
                    'source' => $data['source'],
                    'global' => $data['global'],
                    'visible' => $data['visible'],
                    'required' => $data['required'],
                    'user_defined' => $data['user_defined'],
                    'default' => '',
                    'searchable' => $data['searchable'],
                    'filterable' => $data['filterable'],
                    'comparable' => $data['comparable'],
                    'visible_on_front' => $data['visible_on_front'],
                    'used_in_product_listing' => $data['used_in_product_listing'],
                    'unique' => $data['unique'],
                    'backend' => $data['backend'],
                    'frontend' => '',

                ]
            );


            /**
             * Set attribute group Id if needed
             */
            if (!is_null($attributeGroupId)) {
                /**
                 * Set the attribute in the right attribute group in the right attribute set
                 */
                $eavSetup->addAttributeToGroup(Product::ENTITY, $attributeSetId, $attributeGroupId, $attribute);
            }


            /**
             * Add options if needed
             */
            if (isset($data['options'])) {
                $options = [
                    'attribute_id' => $eavSetup->getAttributeId(Product::ENTITY, $attribute),
                    'values' => $data['options']
                ];
                $eavSetup->addAttributeOption($options);
            }
        }
    }

    /**
     * @param $attrSetName
     * @throws \Magento\Framework\Exception\InputException
     * @throws \Magento\Framework\Exception\NoSuchEntityException
     */
    private function createAttributeSet($attrSetName)
    {

        /** @var EavSetup $eavSetup */
        $eavSetup = $this->eavSetupFactory->create();

        $eavSetup->addAttributeSet(
            Product::ENTITY,
            $attrSetName
        );
    }

    /**
     * @param $attributeGroupName
     * @param string|null $attrSetName
     * @throws \Magento\Framework\Exception\InputException
     * @throws \Magento\Framework\Exception\LocalizedException
     * @throws \Magento\Framework\Exception\NoSuchEntityException
     */
    private function createAttributeGroup($attributeGroupName, $attrSetName = null) {

        /** @var EavSetup $eavSetup */
        $eavSetup = $this->eavSetupFactory->create();

        if ($attrSetName) {
            $this->createAttributeSet($attrSetName);
            $attributeSetId = $eavSetup->getAttributeSetId(Product::ENTITY, $attrSetName);
        } else {
            $attributeSetId = $this->product->getDefaultAttributeSetId();
        }


        $attributeGroup = $this->attributeGroupFactory->create();

        $attributeGroup->setAttributeSetId($attributeSetId);
        $attributeGroup->setAttributeGroupName($attributeGroupName);
        $this->attributeGroupRepository->save($attributeGroup);
    }
}

I know that I could just define all of the attributes manually and have them created in the same manner as the custom attributes. I'm just wondering if there is a best practice for inheritance of particular attributes, where I don't want to just use a skeletonId and inherit all of another set's attributes.

1 Answer 1

1

I'm just wondering if there is a best practice for inheritance of particular attributes, where I don't want to just use a skeletonId and inherit all of another set's attributes.

I’ll preface by saying that I am not aware of a way to copy the settings from one attribute set to another while only excluding certain attributes.

In the past when I have created custom attribute sets, I define a list of the attribute codes to exclude from the new set, then remove them from the new set immediately after creation. One of the benefits of this is that groups get copied across to the new attribute set with attributes already linked to them, which can save a lot of code. However, it depends on how much the new set diverges from the skeleton. Here’s an example how that could work.

function removeAttributesFromSet(int $setId, array $attrIds)
{
    $this->setup->getConnection()->delete(
        $this->setup->getTable('eav_entity_attribute'),
        ['attribute_set_id = ?' => $setId, 'attribute_id IN (?)' => $attrIds]
    );
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.