Magento – How to add a unique custom attribute for a customer

custom-attributescustomer-attributemagento2

I was able to add a custom attribute called 'Username' to the Magento Customer model.

The install-function in InstallData.php looks like this:

public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
{
    $this->logger->info("Username data installed.");

    /** @var CustomerSetup $customerSetup */
    $customerSetup = $this->customerSetupFactory->create(['setup' => $setup]);

    $customerEntity = $customerSetup->getEavConfig()->getEntityType('customer');
    $attributeSetId = $customerEntity->getDefaultAttributeSetId();

    /** @var $attributeSet AttributeSet */
    $attributeSet = $this->attributeSetFactory->create();
    $attributeGroupId = $attributeSet->getDefaultGroupId($attributeSetId);

    $customerSetup->addAttribute(Customer::ENTITY, 'username', [
        'type' => 'varchar',
        'label' => 'Username',
        'input' => 'text',
        'required' => false,
        'visible' => true,
        'user_defined' => true,
        'unique' => true,
        'required' => true,
        'sort_order' => 1000,
        'position' => 1000,
        'system' => 0,
    ]);

    $attribute = $customerSetup->getEavConfig()->getAttribute(Customer::ENTITY, 'username')
    ->addData([
        'attribute_set_id' => $attributeSetId,
        'attribute_group_id' => $attributeGroupId,
        'used_in_forms' => ['adminhtml_customer', 'checkout_register', 'customer_account_create', 'customer_account_edit', 'adminhtml_checkout'],
    ]);

    $attribute->save();


}

Everything works correctly, but the 'unique' => true does not work. I found out that this field is not used for customers and uniqueness must be programmed manually.

So I basically want to check the uniqueness of the attribute just before a customer is saved to the database (so just before account creation).

I found this code in app/code/Magento/Customer/Model/ResourceModel/Customer.php in _beforeSave().

    $connection = $this->getConnection();
    $bind = ['email' => $customer->getEmail()];

    $select = $connection->select()->from(
        $this->getEntityTable(),
        [$this->getEntityIdField()]
    )->where(
        'email = :email'
    );
    if ($customer->getSharingConfig()->isWebsiteScope()) {
        $bind['website_id'] = (int)$customer->getWebsiteId();
        $select->where('website_id = :website_id');
    }
    if ($customer->getId()) {
        $bind['entity_id'] = (int)$customer->getId();
        $select->where('entity_id != :entity_id');
    }

    $result = $connection->fetchOne($select, $bind);
    if ($result) {
        throw new AlreadyExistsException(
            __('A customer with the same email already exists in an associated website.')
        );
    }

It is exactly this behaviour for the email that I want to replicate for my username.

Does anyone have an idea how I do this?

Thank you!

Best Answer

You need to InstallData.php like below code :

public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
{
    $this->logger->info("Username data installed.");

    /** @var CustomerSetup $customerSetup */
    $customerSetup = $this->customerSetupFactory->create(['setup' => $setup]);

    $customerEntity = $customerSetup->getEavConfig()->getEntityType('customer');
    $attributeSetId = $customerEntity->getDefaultAttributeSetId();

    /** @var $attributeSet AttributeSet */
    $attributeSet = $this->attributeSetFactory->create();
    $attributeGroupId = $attributeSet->getDefaultGroupId($attributeSetId);

    $customerSetup->addAttribute(Customer::ENTITY, 'username', [
        'type' => 'varchar',
        'label' => 'Username',
        'input' => 'text',
        'backend' => \Vendor\Module\Model\Attribute\Backend\Username::class,
        'required' => false,
        'visible' => true,
        'user_defined' => true,
        'unique' => true,
        'required' => true,
        'sort_order' => 1000,
        'position' => 1000,
        'system' => 0,
    ]);

    $attribute = $customerSetup->getEavConfig()->getAttribute(Customer::ENTITY, 'username')
    ->addData([
        'attribute_set_id' => $attributeSetId,
        'attribute_group_id' => $attributeGroupId,
        'used_in_forms' => ['adminhtml_customer', 'checkout_register', 'customer_account_create', 'customer_account_edit', 'adminhtml_checkout'],
    ]);

    $attribute->save();


}

Please create Username.php file in \Vendor\Module\Model\Attribute\Backend and code like

<?php 
namespace Vendor\Module\Model\Attribute\Backend;

use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Exception\CouldNotDeleteException;
use Magento\Framework\Exception\CouldNotSaveException;

class Username extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend
{
    /**
     * Generate and set unique Username to customer
     *
     * @param Customer $object
     * @return void
     */
    protected function checkUniqueUsername($object)
    {
        $attribute = $this->getAttribute();
        $entity = $attribute->getEntity();
        $attributeValue = $object->getData($attribute->getAttributeCode());
        $increment = null;
        while (!$entity->checkAttributeUniqueValue($attribute, $object)) {
            throw new NoSuchEntityException(__('Account with Username is already exist'));
        }
    }

    /**
     * Make username unique before save
     *
     * @param Customer $object
     * @return $this
     */
    public function beforeSave($object)
    {
        $this->checkUniqueUsername($object);
        return parent::beforeSave($object);
    }
}