ARTICLE OUTDATED!

This article is outdated and it contains several errors and bad practices. I updated it in a new article you will find here.


Compared to Magento 1, Magento 2 has a built-in store control from the console. In order to see all the commands available in our store, we need to run the following command from the main folder:

$ php bin/magento

They help to automate tasks and allow us quick access to the basic functionalities, i.e. clearing cache, reindexing, or adding an admin account. The commands are nothing else than an executed PHP script. Magento enables us to easily create our own command and this is what we want to do here.

In today’s article we’ll see how to use the console to create a customer account with our custom command.

Creating a module

First, we need to create a module:

//file: app/code/Magently/Customer/registration.php

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Magently_Customer',
    __DIR__
);
//file: app/code/Magently/Customer/etc/module.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd">
    <module name="Magently_Customer" setup_version="0.1.0">
    </module>
</config>

Adding a new command

Adding a new command to CLI is based on passing on the argument from the XML level to the class Magento\Framework\Console\CommandList. Dependency Injection comes in handy here. Let’s create the file app/code/Magently/Customer/etc/di.xml with the following content:

//file: app/code/Magently/Customer/etc/di.xml

<?xml version="1.0"?>
    <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
        <type name="Magento\Framework\Console\CommandList">
            <arguments>
                <argument name="commands" xsi:type="array">
                    <item name="customer_user_create" xsi:type="object">Magently\Customer\Console\Command\CustomerUserCreateCommand</item>
                </argument>
            </arguments>
        </type>
    </config>

We add the object responsible for executing the script to the class Magento\Framework\Console\CommandList. The constructor of this class is simply an array where class objects are passed on in a similar manner as in the above example.

Let’s proceed to the next step – creating a class for our new command and a helper responsible for adding a new user:

//file: app/code/Magently/Customer/Console/Command/CustomerUserCreateCommand.php

<?php
namespace Magently\Customer\Console\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Magently\Customer\Helper\Customer;

class CustomerUserCreateCommand extends Command
{
    protected $customerHelper;

    public function __construct(Customer $customerHelper)
    {
        $this->customerHelper = $customerHelper;
        parent::__construct();
    }

    protected function configure()
    {
        $this
            ->setName('customer:user:create')
            ->setDescription('Create new customer')
            ->setDefinition($this->getOptionsList());
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $output->writeln('<info>Creating new user...</info>');
        $this->customerHelper->setData($input);
        $this->customerHelper->execute();

        $output->writeln('');
        $output->writeln('<info>User created with the following data:</info>');
        $output->writeln('<comment>Customer ID: ' . $this->customerHelper->getCustomerId());
        $output->writeln('<comment>Customer Website ID ' . $input->getOption(Customer::KEY_WEBSITE));
        $output->writeln('<comment>Customer First Name: ' . $input->getOption(Customer::KEY_FIRSTNAME));
        $output->writeln('<comment>Customer Last Name: ' . $input->getOption(Customer::KEY_LASTNAME));
        $output->writeln('');
        $output->writeln('<comment>Customer Email: ' . $input->getOption(Customer::KEY_EMAIL));
        $output->writeln('<comment>Customer Password: ' . $input->getOption(Customer::KEY_PASSWORD));
    }

    protected function getOptionsList()
    {
        return [
            new InputOption(Customer::KEY_FIRSTNAME, null, InputOption::VALUE_REQUIRED, '(Required) Customer first name'),
            new InputOption(Customer::KEY_LASTNAME, null, InputOption::VALUE_REQUIRED, '(Required) Customer last name'),
            new InputOption(Customer::KEY_EMAIL, null, InputOption::VALUE_REQUIRED, '(Required) Customer email'),
            new InputOption(Customer::KEY_PASSWORD, null, InputOption::VALUE_REQUIRED, '(Required) Customer password'),
            new InputOption(Customer::KEY_WEBSITE, null, InputOption::VALUE_REQUIRED, '(Required) Website ID'),
            new InputOption(Customer::KEY_SENDEMAIL, 0, InputOption::VALUE_OPTIONAL, '(1/0) Send email? (default 0)')
        ];
    }
}

What’s going on here?

First, in __construct we remit \Magently\Customer\Helper\Customer, where we’ll place the logic of adding a user. In the configure() method, we specify basic information about the command – name, description, and a list of additional options that we’ll pass on to the console later on. The list of options is an array with objects Symfony\Component\Console\Input\InputOption. We forward to the constructor the option name, the default value, we specify the field requirements, and provide a short description. We can specify Help as well with (Command::setHelp()). For longer operations we can set the title (Command::setProcessTitle()).

The execute() method runs the query and gives us access to the $input and $output variables. $input gives us the arguments and options, whereas $output is used to display information – Output::writeln() shows the text and breaks the line. Thanks to OutputFormatter we can use tags:

//file: vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatter.php
...
        $this->setStyle('error', new OutputFormatterStyle('white', 'red'));
        $this->setStyle('info', new OutputFormatterStyle('green'));
        $this->setStyle('comment', new OutputFormatterStyle('yellow'));
        $this->setStyle('question', new OutputFormatterStyle('black', 'cyan'));
...

Helper

The last part is our helper:

//file: app/code/Magently/Customer/Helper/Customer.php

<?php
namespace Magently\Customer\Helper;

use \Magento\Framework\App\Helper\Context;
use \Magento\Store\Model\StoreManagerInterface;
use \Magento\Framework\App\State;
use \Magento\Customer\Model\CustomerFactory;
use \Symfony\Component\Console\Input\Input;

class Customer extends \Magento\Framework\App\Helper\AbstractHelper
{
    const KEY_EMAIL = 'customer-email';
    const KEY_FIRSTNAME = 'customer-firstname';
    const KEY_LASTNAME = 'customer-lastname';
    const KEY_PASSWORD = 'customer-password';
    const KEY_WEBSITE = 'website';
    const KEY_SENDEMAIL = 'send-email';

    protected $storeManager;
    protected $state;
    protected $customerFactory;
    protected $data;
    protected $customerId;

    public function __construct(
        Context $context,
        StoreManagerInterface $storeManager,
        State $state,
        CustomerFactory $customerFactory
    ) {
        $this->storeManager = $storeManager;
        $this->state = $state;
        $this->customerFactory = $customerFactory;

        parent::__construct($context);
    }

    public function setData(Input $input)
    {
        $this->data = $input;
        return $this;
    }

    public function execute()
    {
        $this->state->setAreaCode('frontend');

        $customer = $this->customerFactory->create();
        $customer
            ->setWebsiteId($this->data->getOption(self::KEY_WEBSITE))
            ->setEmail($this->data->getOption(self::KEY_EMAIL))
            ->setFirstname($this->data->getOption(self::KEY_FIRSTNAME))
            ->setLastname($this->data->getOption(self::KEY_LASTNAME))
            ->setPassword($this->data->getOption(self::KEY_PASSWORD));
        $customer->save();

        $this->customerId = $customer->getId();

        if($this->data->getOption(self::KEY_SENDEMAIL)) {
            $customer->sendNewAccountEmail();
        }        
    }

    public function getCustomerId()
    {
        return (int)$this->customerId;
    }
}

The execute() method adds a new user. If any data is incorrect at this stage (i.e. too short password), the script will stop and the console will show an Exception.

Now let’s install the module:

$ php bin/magento setup:upgrade

Results

Let’s check if our command is on the list:

$ php bin/magento list

As we can see, our command is available. Even though we didn’t add anything to Help, we can still see how our command can be run.

$ php bin/magento customer:user:create --help

Next, let’s add a new user according to the usage pattern.

$ php bin/magento customer:user:create --customer-firstname="John" --customer-lastname="Doe" --customer-email="john@example.com" --customer-password="john123" --website="1"

If we run the command again, we should get the following exception:

We hope you’ll find this article useful. Let us know in the comments in case you have any questions.