From b40a7d6e5171130f5e653f2ede96abe9fd0159f5 Mon Sep 17 00:00:00 2001 From: thiaramus Date: Fri, 3 Apr 2026 10:20:39 -0500 Subject: [PATCH] ACP2E-4803: Review Customer Custom Attribute Documentation draft --- src/pages/config.md | 3 + .../admin/custom-boolean-attribute.md | 342 +++++++++++++++ .../admin/custom-dropdown-attribute.md | 404 +++++++++++++++++ .../admin/custom-multiselect-attribute.md | 408 ++++++++++++++++++ 4 files changed, 1157 insertions(+) create mode 100644 src/pages/tutorials/admin/custom-boolean-attribute.md create mode 100644 src/pages/tutorials/admin/custom-dropdown-attribute.md create mode 100644 src/pages/tutorials/admin/custom-multiselect-attribute.md diff --git a/src/pages/config.md b/src/pages/config.md index a2f2ead89..11a07b651 100644 --- a/src/pages/config.md +++ b/src/pages/config.md @@ -782,6 +782,9 @@ - [Customize form configuration](/tutorials/admin/custom-product-creation-form/configuration.md) - [Customize using a modifier class](/tutorials/admin/custom-product-creation-form/modifier-class.md) - [Create a text field attribute](/tutorials/admin/custom-text-field-attribute.md) + - [Create a boolean attribute](/tutorials/admin/custom-boolean-attribute.md) + - [Create a dropdown attribute](/tutorials/admin/custom-dropdown-attribute.md) + - [Create a multiselect attribute](/tutorials/admin/custom-multiselect-attribute.md) - [Frontend development](/tutorials/frontend/index.md) - [Customize checkout](/tutorials/frontend/custom-checkout/index.md) - [Add a new checkout step](/tutorials/frontend/custom-checkout/add-new-step.md) diff --git a/src/pages/tutorials/admin/custom-boolean-attribute.md b/src/pages/tutorials/admin/custom-boolean-attribute.md new file mode 100644 index 000000000..c4a419cb6 --- /dev/null +++ b/src/pages/tutorials/admin/custom-boolean-attribute.md @@ -0,0 +1,342 @@ +--- +title: Add a Custom Boolean Attribute | Commerce PHP Extensions +description: Follow this tutorial to create a custom boolean attribute for Adobe Commerce or Magento Open Source. +keywords: + - Extensions +--- + +# Add a custom boolean attribute + +This tutorial describes how a developer can create a custom boolean (Yes/No) attribute for the Customer entity using code. This will reflect in both the [Customer Grid](https://experienceleague.adobe.com/en/docs/commerce-admin/customers/customer-accounts/manage/manage-account) and the [Customer Form](https://experienceleague.adobe.com/en/docs/commerce-admin/customers/customer-accounts/manage/update-account) in the Admin. + +This Customer attribute will be used to store a simple Yes/No flag on a customer record, as an example. It will be created as an EAV attribute in a data patch. The EAV model allows a developer to add custom functionality to the entities without modifying the core databases and schemas. Data patches are run just once, so this code will create the custom attribute and will never run again, which could cause issues. + +## Code + +### Create the data patch class + +Create a data patch class called `AddCustomerAttributeBoolean` under the `\ExampleCorp\Customer\Setup\Patch\Data` namespace. This makes the application execute the data patch automatically when `bin/magento setup:upgrade` is run. All data patches must implement the `\Magento\Framework\Setup\Patch\DataPatchInterface` interface. + +```php +moduleDataSetup = $moduleDataSetup; + $this->customerSetup = $customerSetupFactory->create(['setup' => $moduleDataSetup]); + $this->attributeResource = $attributeResource; + $this->logger = $logger; + } + ``` + +### Implement the apply method + +There are five steps in developing a data patch. All the steps below are written inside the `apply` method. + +1. Starting and ending the setup execution. This turns off foreign key checks and sets the SQL mode. + + ```php + $this->moduleDataSetup->getConnection()->startSetup(); + + /* + Attribute creation code must be run between these two lines + to ensure that the attribute is created smoothly. + */ + + $this->moduleDataSetup->getConnection()->endSetup(); + ``` + +1. Add the boolean customer attribute with the required settings. + + Boolean attributes are stored as integers (`0` for No, `1` for Yes). Set `type` to `int`, `input` to `boolean`, and assign the built-in `Boolean` source model, which provides the Yes/No option list. + + The third parameter for `addAttribute` is an array of settings required to configure the attribute. Passing an empty array uses all the default values for each possible setting. To keep the code to a minimum, just declare the settings needing to be overridden and the rest of the settings will be used from the defaults. + + The `\Magento\Customer\Api\CustomerMetadataInterface` interface contains constants like the customer entity's code and the default attribute set code, which can be referenced. + + ```php + $this->customerSetup->addAttribute( + CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, // entity type code + 'custom_boolean_attribute', // unique attribute code + [ + 'label' => 'Custom Boolean Attribute', + 'type' => 'int', + 'input' => 'boolean', + 'source' => \Magento\Eav\Model\Entity\Attribute\Source\Boolean::class, + 'required' => 0, + 'position' => 333, + 'system' => 0, + 'user_defined' => 1, + 'is_used_in_grid' => 1, + 'is_visible_in_grid' => 1, + 'is_filterable_in_grid' => 1, + 'is_searchable_in_grid' => 0, + ] + ); + ``` + + | Setting Key | Description | + | --- | --- | + | `label` | `Custom Boolean Attribute` - Label for displaying the attribute value | + | `type` | `int` - Stored as an integer in the database (`0` or `1`) | + | `input` | `boolean` - Renders as a Yes/No select in the customer form | + | `source` | Provides the Yes/No option list for the boolean input | + | `required` | `0` - Attribute will be an optional field in the customer form | + | `position` | `333` - Sort order in the customer form | + | `system` | `0` - Not a system-defined attribute | + | `user_defined` | `1` - A user-defined attribute | + | `is_used_in_grid` | `1` - Ready for use in the customer grid | + | `is_visible_in_grid` | `1` - Visible in the customer grid | + | `is_filterable_in_grid` | `1` - Filterable in the customer grid | + | `is_searchable_in_grid` | `0` - Not searchable in the customer grid (boolean fields are filtered, not searched) | + +1. Add attribute to an attribute set and group. + + There is only one attribute set and group for the customer entity. The default attribute set ID is a constant defined in the `CustomerMetadataInterface` interface and setting the attribute group ID to null makes the application use the default attribute group ID for the customer entity. + + ```php + $this->customerSetup->addAttributeToSet( + CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, // entity type code + CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER, // attribute set ID + null, // attribute group ID + 'custom_boolean_attribute' // unique attribute code + ); + ``` + +1. Make the attribute visible in the customer form. + + ```php + // Get the newly created attribute's model + $attribute = $this->customerSetup->getEavConfig() + ->getAttribute(CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, 'custom_boolean_attribute'); + + // Make attribute visible in Admin customer form + $attribute->setData('used_in_forms', [ + 'adminhtml_customer', + 'customer_account_create', + 'customer_account_edit', + ]); + + // Save modified attribute model using its resource model + $this->attributeResource->save($attribute); + ``` + + Unlike the text field attribute, which is visible only in the Admin, the boolean attribute is also registered for the storefront registration and account edit forms. Adjust the `used_in_forms` values to match the visibility requirements of your project. + +1. Gracefully handle exceptions. + + ```php + try { + // All the code inside the apply method goes into the try block. + } catch (Exception $exception) { + $this->logger->error($exception->getMessage()); + } + ``` + +### Implement rest of the interface + +This data patch does not have any other patch as a dependency, and this data patch was not renamed earlier, so both `getDependencies` and `getAliases` can return an empty array. + +```php +public static function getDependencies(): array +{ + return []; +} + +public function getAliases(): array +{ + return []; +} +``` + +### Execute the data patch + +Run `bin/magento setup:upgrade` from the project root to execute the newly added data patch. + +- The attribute is created in the customer form under the _Account Information_ section. +- The attribute is displayed in the customer grid and can be filtered using a Yes/No dropdown. + +### Code reference + +```php +moduleDataSetup = $moduleDataSetup; + $this->customerSetup = $customerSetupFactory->create(['setup' => $moduleDataSetup]); + $this->attributeResource = $attributeResource; + $this->logger = $logger; + } + + /** + * Get array of patches that have to be executed prior to this. + * + * @return string[] + */ + public static function getDependencies(): array + { + return []; + } + + /** + * Get aliases (previous names) for the patch. + * + * @return string[] + */ + public function getAliases(): array + { + return []; + } + + /** + * Run code inside patch + */ + public function apply(): void + { + // Start setup + $this->moduleDataSetup->getConnection()->startSetup(); + + try { + // Add customer attribute with settings + $this->customerSetup->addAttribute( + CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, + 'custom_boolean_attribute', + [ + 'label' => 'Custom Boolean Attribute', + 'type' => 'int', + 'input' => 'boolean', + 'source' => \Magento\Eav\Model\Entity\Attribute\Source\Boolean::class, + 'required' => 0, + 'position' => 333, + 'system' => 0, + 'user_defined' => 1, + 'is_used_in_grid' => 1, + 'is_visible_in_grid' => 1, + 'is_filterable_in_grid' => 1, + 'is_searchable_in_grid' => 0, + ] + ); + + // Add attribute to default attribute set and group + $this->customerSetup->addAttributeToSet( + CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, + CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER, + null, + 'custom_boolean_attribute' + ); + + // Get the newly created attribute's model + $attribute = $this->customerSetup->getEavConfig() + ->getAttribute(CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, 'custom_boolean_attribute'); + + // Make attribute visible in Admin customer form and storefront forms + $attribute->setData('used_in_forms', [ + 'adminhtml_customer', + 'customer_account_create', + 'customer_account_edit', + ]); + + // Save attribute using its resource model + $this->attributeResource->save($attribute); + } catch (Exception $e) { + $this->logger->error($e->getMessage()); + } + + // End setup + $this->moduleDataSetup->getConnection()->endSetup(); + } +} +``` \ No newline at end of file diff --git a/src/pages/tutorials/admin/custom-dropdown-attribute.md b/src/pages/tutorials/admin/custom-dropdown-attribute.md new file mode 100644 index 000000000..93143c0e0 --- /dev/null +++ b/src/pages/tutorials/admin/custom-dropdown-attribute.md @@ -0,0 +1,404 @@ +--- +title: Add a Custom Dropdown Attribute | Commerce PHP Extensions +description: Follow this tutorial to create a custom dropdown attribute for Adobe Commerce or Magento Open Source. +keywords: + - Extensions +--- + +# Add a custom dropdown attribute + +This tutorial describes how a developer can create a custom dropdown (select) attribute for the Customer entity using code. This will reflect in both the [Customer Grid](https://experienceleague.adobe.com/en/docs/commerce-admin/customers/customer-accounts/manage/manage-account) and the [Customer Form](https://experienceleague.adobe.com/en/docs/commerce-admin/customers/customer-accounts/manage/update-account) in the Admin. + +Use a select attribute when you want administrators (and optionally customers) to choose a value from a controlled set of options — for example, a customer tier (Silver/Gold/Platinum) or an internal segmentation flag. The attribute will be created as an EAV attribute in a data patch. This tutorial also implements `PatchRevertableInterface`, which allows the attribute to be cleanly removed by running `bin/magento setup:rollback`. + +## Code + +### Create the data patch class + +Create a data patch class called `AddCustomerAttributeOptions` under the `\ExampleCorp\Customer\Setup\Patch\Data` namespace. This makes the application execute the data patch automatically when `bin/magento setup:upgrade` is run. Unlike the text field and boolean attribute tutorials, this class implements both `\Magento\Framework\Setup\Patch\DataPatchInterface` and `\Magento\Framework\Setup\Patch\PatchRevertableInterface`. Adding the revertable interface requires implementing a `revert()` method that removes the attribute when the patch is rolled back. + +```php +moduleDataSetup = $moduleDataSetup; + $this->customerSetupFactory = $customerSetupFactory; + $this->attributeResource = $attributeResource; + $this->logger = $logger; + } + ``` + +### Implement the apply method + +There are five steps in developing a data patch. All the steps below are written inside the `apply` method. + +1. Starting and ending the setup execution. This turns off foreign key checks and sets the SQL mode. + + ```php + $this->moduleDataSetup->getConnection()->startSetup(); + + /* + Attribute creation code must be run between these two lines + to ensure that the attribute is created smoothly. + */ + + $this->moduleDataSetup->getConnection()->endSetup(); + ``` + +1. Add the dropdown customer attribute with the required settings. + + Dropdown attributes store the selected option's ID as an integer. Set `input` to `select` and assign a source model that provides the option list. The built-in `Table` source model reads options from the `eav_attribute_option` and `eav_attribute_option_value` tables, which can be managed through the Admin or populated programmatically. To provide a fixed, code-defined set of options, replace `Table::class` with a custom source model that implements `toOptionArray()`. + + ```php + /** @var CustomerSetup $customerSetup */ + $customerSetup = $this->customerSetupFactory->create(['setup' => $this->moduleDataSetup]); + + $customerSetup->addAttribute( + CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, // entity type code + 'custom_options', // unique attribute code + [ + 'label' => 'Customer Custom Attribute Options', + 'type' => 'int', + 'input' => 'select', + 'source' => \Magento\Eav\Model\Entity\Attribute\Source\Table::class, + 'required' => false, + 'position' => 444, + 'system' => false, + 'user_defined' => true, + 'is_used_in_grid' => true, + 'is_visible_in_grid' => true, + 'is_filterable_in_grid' => true, + 'is_searchable_in_grid' => false, + ] + ); + ``` + + | Setting Key | Description | + | --- | --- | + | `label` | `Customer Custom Attribute Options` - Label for displaying the attribute value | + | `type` | `int` - Stores the selected option ID as an integer | + | `input` | `select` - Renders as a dropdown in the customer form | + | `source` | Provides the list of selectable options | + | `required` | `false` - Attribute will be an optional field in the customer form | + | `position` | `444` - Sort order in the customer form | + | `system` | `false` - Not a system-defined attribute | + | `user_defined` | `true` - A user-defined attribute | + | `is_used_in_grid` | `true` - Ready for use in the customer grid | + | `is_visible_in_grid` | `true` - Visible in the customer grid | + | `is_filterable_in_grid` | `true` - Filterable in the customer grid | + | `is_searchable_in_grid` | `false` - Not searchable in the customer grid (dropdown fields are filtered by option, not free-text searched) | + +1. Add attribute to an attribute set and group. + + There is only one attribute set and group for the customer entity. The default attribute set ID is a constant defined in the `CustomerMetadataInterface` interface and setting the attribute group ID to null makes the application use the default attribute group ID for the customer entity. + + ```php + $customerSetup->addAttributeToSet( + CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, // entity type code + CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER, // attribute set ID + null, // attribute group ID + 'custom_options' // unique attribute code + ); + ``` + +1. Make the attribute visible in the customer form. + + ```php + // Get the newly created attribute's model + $attribute = $customerSetup->getEavConfig() + ->getAttribute(CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, 'custom_options'); + + // Make attribute visible in Admin customer form and storefront forms + $attribute->setData('used_in_forms', [ + 'adminhtml_customer', + 'customer_account_create', + 'customer_account_edit', + ]); + + // Save modified attribute model using its resource model + $this->attributeResource->save($attribute); + ``` + +1. Gracefully handle exceptions. + + ```php + try { + // All the code inside the apply method goes into the try block. + } catch (Exception $exception) { + $this->logger->error($exception->getMessage()); + } + ``` + +### Implement the revert method + +Because this class implements `PatchRevertableInterface`, it must also define a `revert()` method. This method is called when `bin/magento setup:rollback` targets this patch and removes the attribute from the system. + +```php +public function revert(): void +{ + $this->moduleDataSetup->getConnection()->startSetup(); + + /** @var CustomerSetup $customerSetup */ + $customerSetup = $this->customerSetupFactory->create(['setup' => $this->moduleDataSetup]); + + try { + $customerSetup->removeAttribute( + \Magento\Customer\Model\Customer::ENTITY, + 'custom_options' + ); + } catch (Exception $e) { + $this->logger->error($e->getMessage()); + } + + $this->moduleDataSetup->getConnection()->endSetup(); +} +``` + +### Implement rest of the interface + +This data patch does not have any other patch as a dependency, and this data patch was not renamed earlier, so both `getDependencies` and `getAliases` can return an empty array. + +```php +public static function getDependencies(): array +{ + return []; +} + +public function getAliases(): array +{ + return []; +} +``` + +### Execute the data patch + +Run `bin/magento setup:upgrade` from the project root to execute the newly added data patch. + +- The attribute is created in the customer form under the _Account Information_ section. +- The attribute is displayed in the customer grid and can be filtered using a dropdown of available options. + +To remove the attribute, run `bin/magento setup:rollback` and target this patch. The `revert()` method will execute and delete the attribute from the system. + +### Code reference + +```php +moduleDataSetup = $moduleDataSetup; + $this->customerSetupFactory = $customerSetupFactory; + $this->attributeResource = $attributeResource; + $this->logger = $logger; + } + + /** + * Get array of patches that have to be executed prior to this. + * + * @return string[] + */ + public static function getDependencies(): array + { + return []; + } + + /** + * Get aliases (previous names) for the patch. + * + * @return string[] + */ + public function getAliases(): array + { + return []; + } + + /** + * Run code inside patch + */ + public function apply(): void + { + // Start setup + $this->moduleDataSetup->getConnection()->startSetup(); + + /** @var CustomerSetup $customerSetup */ + $customerSetup = $this->customerSetupFactory->create(['setup' => $this->moduleDataSetup]); + + try { + // Add customer attribute with settings + $customerSetup->addAttribute( + CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, + 'custom_options', + [ + 'label' => 'Customer Custom Attribute Options', + 'type' => 'int', + 'input' => 'select', + 'source' => \Magento\Eav\Model\Entity\Attribute\Source\Table::class, + 'required' => false, + 'position' => 444, + 'system' => false, + 'user_defined' => true, + 'is_used_in_grid' => true, + 'is_visible_in_grid' => true, + 'is_filterable_in_grid' => true, + 'is_searchable_in_grid' => false, + ] + ); + + // Add attribute to default attribute set and group + $customerSetup->addAttributeToSet( + CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, + CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER, + null, + 'custom_options' + ); + + // Get the newly created attribute's model + $attribute = $customerSetup->getEavConfig() + ->getAttribute(CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, 'custom_options'); + + // Make attribute visible in Admin customer form and storefront forms + $attribute->setData('used_in_forms', [ + 'adminhtml_customer', + 'customer_account_create', + 'customer_account_edit', + ]); + + // Save attribute using its resource model + $this->attributeResource->save($attribute); + } catch (Exception $e) { + $this->logger->error($e->getMessage()); + } + + // End setup + $this->moduleDataSetup->getConnection()->endSetup(); + } + + /** + * Rollback all changes, done by this patch + */ + public function revert(): void + { + // Start setup + $this->moduleDataSetup->getConnection()->startSetup(); + + /** @var CustomerSetup $customerSetup */ + $customerSetup = $this->customerSetupFactory->create(['setup' => $this->moduleDataSetup]); + + try { + $customerSetup->removeAttribute( + Customer::ENTITY, + 'custom_options' + ); + } catch (Exception $e) { + $this->logger->error($e->getMessage()); + } + + // End setup + $this->moduleDataSetup->getConnection()->endSetup(); + } +} +``` \ No newline at end of file diff --git a/src/pages/tutorials/admin/custom-multiselect-attribute.md b/src/pages/tutorials/admin/custom-multiselect-attribute.md new file mode 100644 index 000000000..516d7cb2d --- /dev/null +++ b/src/pages/tutorials/admin/custom-multiselect-attribute.md @@ -0,0 +1,408 @@ +--- +title: Add a Custom Multiselect Attribute | Commerce PHP Extensions +description: Follow this tutorial to create a custom multiselect attribute for Adobe Commerce or Magento Open Source. +keywords: + - Extensions +--- + +# Add a custom multiselect attribute + +This tutorial describes how a developer can create a custom multiselect attribute for the Customer entity using code. This will reflect in both the [Customer Grid](https://experienceleague.adobe.com/en/docs/commerce-admin/customers/customer-accounts/manage/manage-account) and the [Customer Form](https://experienceleague.adobe.com/en/docs/commerce-admin/customers/customer-accounts/manage/update-account) in the Admin. + +Use a multiselect attribute when you need to store multiple simultaneous values for a single customer field — for example, eligible shipping methods, allowed sales channels, or subscription preferences. Unlike the [dropdown attribute](custom-dropdown-attribute.md), which stores a single selected option ID as an integer, a multiselect attribute stores a comma-separated list of option IDs as a `varchar` value, handled by the `ArrayBackend` backend model. This tutorial also implements `PatchRevertableInterface`, which allows the attribute to be cleanly removed by running `bin/magento setup:rollback`. + +## Code + +### Create the data patch class + +Create a data patch class called `AddCustomerAttributeMultipleOptions` under the `\ExampleCorp\Customer\Setup\Patch\Data` namespace. This makes the application execute the data patch automatically when `bin/magento setup:upgrade` is run. This class implements both `\Magento\Framework\Setup\Patch\DataPatchInterface` and `\Magento\Framework\Setup\Patch\PatchRevertableInterface`. + +```php +moduleDataSetup = $moduleDataSetup; + $this->customerSetupFactory = $customerSetupFactory; + $this->attributeResource = $attributeResource; + $this->logger = $logger; + } + ``` + +### Implement the apply method + +There are five steps in developing a data patch. All the steps below are written inside the `apply` method. + +1. Starting and ending the setup execution. This turns off foreign key checks and sets the SQL mode. + + ```php + $this->moduleDataSetup->getConnection()->startSetup(); + + /* + Attribute creation code must be run between these two lines + to ensure that the attribute is created smoothly. + */ + + $this->moduleDataSetup->getConnection()->endSetup(); + ``` + +1. Add the multiselect customer attribute with the required settings. + + Multiselect attributes differ from dropdown attributes in two important ways. First, `type` is set to `varchar` rather than `int`, because the stored value is a comma-separated string of selected option IDs (for example, `3,7,12`) rather than a single integer. Second, a `backend` model must be specified. `ArrayBackend` handles serializing the array of selected IDs into that comma-separated string on save, and deserializing it back to an array on load. Without it, the multiselect will not persist correctly. + + ```php + /** @var CustomerSetup $customerSetup */ + $customerSetup = $this->customerSetupFactory->create(['setup' => $this->moduleDataSetup]); + + $customerSetup->addAttribute( + CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, // entity type code + 'custom_multi_options', // unique attribute code + [ + 'label' => 'Customer Custom Attribute MultiOptions', + 'type' => 'varchar', + 'input' => 'multiselect', + 'source' => \Magento\Eav\Model\Entity\Attribute\Source\Table::class, + 'backend' => \Magento\Eav\Model\Entity\Attribute\Backend\ArrayBackend::class, + 'required' => false, + 'position' => 555, + 'system' => false, + 'user_defined' => true, + 'is_used_in_grid' => true, + 'is_visible_in_grid' => true, + 'is_filterable_in_grid' => true, + 'is_searchable_in_grid' => false, + ] + ); + ``` + + | Setting Key | Description | + | --- | --- | + | `label` | `Customer Custom Attribute MultiOptions` - Label for displaying the attribute value | + | `type` | `varchar` - Stores selected option IDs as a comma-separated string | + | `input` | `multiselect` - Renders as a multiselect list in the customer form | + | `source` | Provides the list of selectable options | + | `backend` | `ArrayBackend` - Serializes and deserializes the comma-separated option ID string | + | `required` | `false` - Attribute will be an optional field in the customer form | + | `position` | `555` - Sort order in the customer form | + | `system` | `false` - Not a system-defined attribute | + | `user_defined` | `true` - A user-defined attribute | + | `is_used_in_grid` | `true` - Ready for use in the customer grid | + | `is_visible_in_grid` | `true` - Visible in the customer grid | + | `is_filterable_in_grid` | `true` - Filterable in the customer grid | + | `is_searchable_in_grid` | `false` - Not searchable in the customer grid (multiselect fields are filtered by option, not free-text searched) | + +1. Add attribute to an attribute set and group. + + There is only one attribute set and group for the customer entity. The default attribute set ID is a constant defined in the `CustomerMetadataInterface` interface and setting the attribute group ID to null makes the application use the default attribute group ID for the customer entity. + + ```php + $customerSetup->addAttributeToSet( + CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, // entity type code + CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER, // attribute set ID + null, // attribute group ID + 'custom_multi_options' // unique attribute code + ); + ``` + +1. Make the attribute visible in the customer form. + + ```php + // Get the newly created attribute's model + $attribute = $customerSetup->getEavConfig() + ->getAttribute(CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, 'custom_multi_options'); + + // Make attribute visible in Admin customer form and storefront forms + $attribute->setData('used_in_forms', [ + 'adminhtml_customer', + 'customer_account_create', + 'customer_account_edit', + ]); + + // Save modified attribute model using its resource model + $this->attributeResource->save($attribute); + ``` + +1. Gracefully handle exceptions. + + ```php + try { + // All the code inside the apply method goes into the try block. + } catch (Exception $exception) { + $this->logger->error($exception->getMessage()); + } + ``` + +### Implement the revert method + +Because this class implements `PatchRevertableInterface`, it must also define a `revert()` method. This method is called when `bin/magento setup:rollback` targets this patch and removes the attribute from the system. + +```php +public function revert(): void +{ + $this->moduleDataSetup->getConnection()->startSetup(); + + /** @var CustomerSetup $customerSetup */ + $customerSetup = $this->customerSetupFactory->create(['setup' => $this->moduleDataSetup]); + + try { + $customerSetup->removeAttribute( + \Magento\Customer\Model\Customer::ENTITY, + 'custom_multi_options' + ); + } catch (Exception $e) { + $this->logger->error($e->getMessage()); + } + + $this->moduleDataSetup->getConnection()->endSetup(); +} +``` + +### Implement rest of the interface + +This data patch does not have any other patch as a dependency, and this data patch was not renamed earlier, so both `getDependencies` and `getAliases` can return an empty array. + +```php +public static function getDependencies(): array +{ + return []; +} + +public function getAliases(): array +{ + return []; +} +``` + +### Execute the data patch + +Run `bin/magento setup:upgrade` from the project root to execute the newly added data patch. + +- The attribute is created in the customer form under the _Account Information_ section. +- The attribute is displayed in the customer grid and can be filtered by selecting one or more options. + +To remove the attribute, run `bin/magento setup:rollback` and target this patch. The `revert()` method will execute and delete the attribute from the system. + +### Code reference + +```php +moduleDataSetup = $moduleDataSetup; + $this->customerSetupFactory = $customerSetupFactory; + $this->attributeResource = $attributeResource; + $this->logger = $logger; + } + + /** + * Get array of patches that have to be executed prior to this. + * + * @return string[] + */ + public static function getDependencies(): array + { + return []; + } + + /** + * Get aliases (previous names) for the patch. + * + * @return string[] + */ + public function getAliases(): array + { + return []; + } + + /** + * Run code inside patch + */ + public function apply(): void + { + // Start setup + $this->moduleDataSetup->getConnection()->startSetup(); + + /** @var CustomerSetup $customerSetup */ + $customerSetup = $this->customerSetupFactory->create(['setup' => $this->moduleDataSetup]); + + try { + // Add customer attribute with settings + $customerSetup->addAttribute( + CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, + 'custom_multi_options', + [ + 'label' => 'Customer Custom Attribute MultiOptions', + 'type' => 'varchar', + 'input' => 'multiselect', + 'source' => \Magento\Eav\Model\Entity\Attribute\Source\Table::class, + 'backend' => ArrayBackend::class, + 'required' => false, + 'position' => 555, + 'system' => false, + 'user_defined' => true, + 'is_used_in_grid' => true, + 'is_visible_in_grid' => true, + 'is_filterable_in_grid' => true, + 'is_searchable_in_grid' => false, + ] + ); + + // Add attribute to default attribute set and group + $customerSetup->addAttributeToSet( + CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, + CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER, + null, + 'custom_multi_options' + ); + + // Get the newly created attribute's model + $attribute = $customerSetup->getEavConfig() + ->getAttribute(CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, 'custom_multi_options'); + + // Make attribute visible in Admin customer form and storefront forms + $attribute->setData('used_in_forms', [ + 'adminhtml_customer', + 'customer_account_create', + 'customer_account_edit', + ]); + + // Save attribute using its resource model + $this->attributeResource->save($attribute); + } catch (Exception $e) { + $this->logger->error($e->getMessage()); + } + + // End setup + $this->moduleDataSetup->getConnection()->endSetup(); + } + + /** + * Rollback all changes, done by this patch + */ + public function revert(): void + { + // Start setup + $this->moduleDataSetup->getConnection()->startSetup(); + + /** @var CustomerSetup $customerSetup */ + $customerSetup = $this->customerSetupFactory->create(['setup' => $this->moduleDataSetup]); + + try { + $customerSetup->removeAttribute( + Customer::ENTITY, + 'custom_multi_options' + ); + } catch (Exception $e) { + $this->logger->error($e->getMessage()); + } + + // End setup + $this->moduleDataSetup->getConnection()->endSetup(); + } +} +``` \ No newline at end of file