Zend certified PHP/Magento developer

Magento 2.4: Programmatically add an attribute from the default set to a custom attribute

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 MyOrganisationMyModuleSetup;
use MagentoCatalogModelCategoryRepository;
use MagentoCatalogSetupCategorySetup;
use MagentoCatalogModelResourceModelCategory as CategoryResourceModel;
use MagentoCatalogModelCategory;
use MagentoCatalogHelperCategory as CategoryHelper;
use MagentoStoreModelStoreManagerInterface;
use MagentoCatalogApiAttributeSetManagementInterface;
use MagentoCatalogModelProduct;
use MagentoEavApiAttributeGroupRepositoryInterface;
use MagentoEavSetupEavSetup;
use MagentoEavSetupEavSetupFactory;
use MagentoEavApiDataAttributeGroupInterfaceFactory;
use MagentoEavApiDataAttributeSetInterfaceFactory;
use MagentoFrameworkSetupModuleContextInterface;
use MagentoFrameworkSetupModuleDataSetupInterface;
use MagentoFrameworkSetupInstallDataInterface;
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' => MagentoEavModelEntityAttributeScopedAttributeInterface::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' => MagentoEavModelEntityAttributeScopedAttributeInterface::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 MagentoFrameworkExceptionInputException
* @throws MagentoFrameworkExceptionNoSuchEntityException
*/
private function createAttributeSet($attrSetName)
{
/** @var EavSetup $eavSetup */
$eavSetup = $this->eavSetupFactory->create();
$eavSetup->addAttributeSet(
Product::ENTITY,
$attrSetName
);
}
/**
* @param $attributeGroupName
* @param string|null $attrSetName
* @throws MagentoFrameworkExceptionInputException
* @throws MagentoFrameworkExceptionLocalizedException
* @throws MagentoFrameworkExceptionNoSuchEntityException
*/
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.