Add new console command in Magento 2

Add new console command to Magento 2-01

Remember that time when you wanted to create a Magento shell or php script to perform some action, but didn’t know where to put it?
Remember the /shell folder in root Magento installation (or root folder, for that matter)?
Remember how it used to have, like, 50 shell and php scripts all mixed up, and you get a headache just by looking at them?
Pepperidge Farm remembers.

Those were good old times, and like all good times, they have now come to pass. There’s a new kid on the block, more powerful and way cooler, and the best part is – it’s integrated into Magento 2.

We’re talking, of course, about a new feature of Magento 2, its console component.
Root folder now contains a new directory – /bin, and in it a ‘magento’ script, used to start the console component. By typing bin/magento in terminal, we receive a number of commands we can run like:

  • create an admin user (admin:user:create)
  • clear, disable, enable cache (cache:clean, cache:enable, cache:disable, etc.)
  • run cron (cron:run)
  • enable and disable modules (module:enable, module:disable)
  • check indexer status, and reindex if needed (indexer:info, indexer:reindex, etc.)
  • and many more

This is a major improvement when compared to Magento 1, and like everything Magento does (although this one was borrowed from Symfony), it’s highly customizable and extendable. We can add our own commands fairly easily, and that’s exactly what we’re going to do in this article.

We won’t go into details of creating a new module in Magento 2, as there are already many tutorials you can follow.

In order to add a new command, we only need to perform a few steps. Firstly, create a di.xml file (if you do not have one already) in etc folder in your module, and put this in:

<?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="hello_world" xsi:type="object">Inchoo\Console\Console\Command\HelloWorldCommand</item>
				</argument>
			</arguments>
		</type>
	</config>

We have added a new entry in di.xml file. What does it do? If we check the type tag, and its name, we see it is pointing us to "Magento\Framework\Console\CommandList". Also, it has some arguments named “commands”, and an item tag. It looks weird. Let’s go to the CommandList class, and see what the fuss is all about:

class CommandList implements CommandListInterface
{
    /**
     * @var string[]
     */
    protected $commands;
 
    /**
     * Constructor
     *
     * @param array $commands
     */
    public function __construct(array $commands = [])
    {
        $this->commands = $commands;
    }
 
    /**
     * {@inheritdoc}
     */
    public function getCommands()
    {
        return $this->commands;
    }
}

Not much going on here. Simple class, where constructor is receiving a commands array as a parameter, and it has a getter method, and a protected variable, and.. umm.. Wait, constructor is receiving a commands array? That looks familiar. Our di.xml has something mentioning an array called commands. Let’s see it again:

<arguments>
	<argument name="commands" xsi:type="array">
		<item name="hello_world" xsi:type="object">Inchoo\Nassau\Console\Command\HelloWorldCommand</item>
	</argument>
</arguments>

Interesting. Our di.xmls argument tag is indeed called commands, and it’s type is set to “array”. It seems like Magento is using some kind of magic to send our argument to CommandList class. <argument> has an <item>, and this must be the thing it is sending.
If we check Magento documentation (yeah, you read that right):

“Arguments are injected into a class instance during its creation. Argument names must correspond to constructor parameters of the configured class.”

We see that this is actually dependency injection at work – injecting our Command object into an array of all commands. Neat.

Let’s see this HelloWorldCommand class (it is located in Console directory in our module).

class HelloWorldCommand extends Command
{
 
    protected function configure()
    {
        $this->setName('inchoo:hello_world')->setDescription('Prints hello world.');
    }
 
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $output->writeln('Hello World!');
    }
 
}

There are only two methods here: configure and execute. We inherit them from parent class (Command), and they allow us to set up our command.

Configure method is used to set initial configuration to the command like: name, description, command line arguments etc.
Execute method is the place where you write your logic. In this example we’ve just printed “Hello World!” to the screen, but hopefully you’ll have more imagination.

To see the command in action, invoke it with bin/magento like this:

bin/magento inchoo:hello_world

and you would be greeted by your command line. What a feeling. 🙂

To make your life easier, we’ve prepared a composer repository you can install, to try the command without having to type everything out.

Here are the instructions:

add our module as a dependency:

composer config repositories.inchoo_console vcs git@bitbucket.org:lurajcevi/inchoo_console.git

ask Composer to download it:

composer require inchoo/console:dev-master

if Composer asks you for a set of credentials, please follow these instructions.

When composer does his magic, we can enable our module:

bin/magento module:enable Inchoo_Console

and later

bin/magento setup:upgrade

Our module has been enabled, and we can use it. Since the idea was to add a new command to console application, let’s see if it worked. Call bin/magento again.

Hopefully, you’ll see the following command (among all others):

...
inchoo
inchoo:hello_world Prints hello world.
...

Let’s invoke it:

bin/magento inchoo:hello_world

and response is, as expected:

Hello World!

About Luka Rajcevic

Backend Developer

Big Tolkien and LOTR fan who plays guitar for his pleasure and relaxes while listening to jazz, blues or classical music.

Read more posts by Luka / Visit Luka's profile

5 comments

  1. Hi, Thank you for the great tut, but I have one question, How to pass param into the command line?
    Ex: bin/magento inchoo:hello_world file_name
    The execute can read file_name then read the content of file_name, the file_name is the CSV file
    Thank you again

    1. Oh, I find out the way use Symfony\Component\Console\Input\InputArgument;

      $options = [
                  new InputArgument(
                      self::FILE_NAME,  //param_name
                      InputArgument::REQUIRED,  //optiosn or required param
                      'Name of file to input'   // description
                  ),
              ];

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <blockquote cite=""> <code> <del datetime=""> <em> <s> <strike> <strong>. You may use following syntax for source code: <pre><code>$current = "Inchoo";</code></pre>.