Add new console command in Magento 2

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\Console\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!
However, if this or anything else regarding Magento development confuses you, we will gladly check out your site and offer technical insights on what to improve based on our detailed custom report. Feel free to get in touch!
12 comments
Follow below steps to access command line argument.
1] use below class in command file.
use Symfony\Component\Console\Input\InputArgument;
2] create below constant
const YOUR_ARGUMENT = ‘test_value’;
3] In configure method write below code :
$this->setName(‘inchoo:hello_world’)
->setDescription(‘Prints hello world.’)
->setDefinition([
new InputArgument(
self::YOUR_ARGUMENT, //param_name
InputArgument::OPTIONAL,
‘Name of file to input’ // description
),
]);
parent::configure();
4] To get command line parameter use below code in execute method
$argValue = $input->getArgument(self::YOUR_ARGUMENT);
It is easier just using the Symfony 3 practice:
To access the argument just use:
I find this a lot easier than using constants and defining options. Anyway, my two cents.
damn formatting thing – it broke my previous comment and there is no preview on ot – lets see if this one works
HelloWorldCommand.php
setName('inchoo:hello_world')->setDescription('Prints hello world.');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->writeln('Hello World!');
}
}
di.xml
Inchoo\Helloworld\Console\Command\HelloWorldCommand
HelloWorldCommand.php
di.xml
To be honest I haven’t seen such an unfinished and misleading tutorial for a while. If any newbie would try to follow it, he/she would never have it working. Lemme bring my 5 cents here:
1) you are creating a di.xml with this element
and then later you are referring to it as to
are you kidding me? with all the respect but that is such a stupid typo/ copy paste? what was that? somebody should figure out on its own what the hell the module name should be? more attention to such things please, we have enough BAD tutorials.. but it is not that bad.. the worst is that you are mentioning a command file
“Let’s see this HelloWorldCommand class (it is located in Console directory in our module).”
where it came from? was it brought in by Santa maybe? If that would have been a part of module creation which you have mentioned, it would be understandable you had not explained when you created it but it had not! it is a part of THIS tutorial, so you had to explain at which step it is being created since you are referring to it as to “this HelloWorldCommand class”.
Also it is NOT in Console directory, it should be in Console/Command directory – so even its location is wrong
but again it is not the worst thing here.. have you even tested this command? it wont work because
a) it has no namespace – how the hell it would be reached by di?
b) it refers to Command, InputInterface and OutputInterface – it would fail because such classes would not be found…
oh my God I cant believe I am writing this.. I don’t ususally blame other peoples tutorials but here I had to. Really BAD.
SO to make a long story short – for all folks who would try it:
1) Assuming module name you create is Helloworld and it is in Inchoo namespace, so you would need to create a file Inchoo\Helloworld\Console\Command\HelloWorldCommand.php with this content
and your Inchoo\Helloworld\etc\di.xml should look like
and then yes, it would work..
author , please update this tutorial and test it before publishing it. thank you
Hi Oleksandr,
1. This indeed was a typo
2. “Let’s see this HelloWorldCommand class”
– class has been mentioned in the XML file and the assumption is people following the tutorial read the files they’re copy-pasting (or using in their own code)
3. It has no namespaces for formatting reasons, to be more readable and succinct
4. InputInterface and OutputInterface do not exist – check point 3.
Thanks for the comment. The typo is now fixed. Also, while we’re at it, your comment contains the same code as ours, and does not contribute anything new (it does not have namespaces as well, so it would not work 🙂 ). Check the original repository for more info: https://bitbucket.org/lurajcevi/inchoo_console/src
Luka
I would have to agree here… Your example isn’t complete (and is still incorrect with regards to the folder location- it should be namespace/module/Console/Command)
Also, you should really include the namespace as well to make this a fully-working example:
<?php
namespace Inchoo\Console\Console\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
…
“Pepperidge Farm remembers.”
Hahahahaha best line of entire article.
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
Oh, I find out the way use Symfony\Component\Console\Input\InputArgument;
Hello.
Could you please describe what is the $options array? How you use it?
I working on the same issue as you. When I add some argument like bin/magento inchoo:hello_world file_name I see:
[RuntimeException]
Too many arguments.
in console. What did you do for passing argument?
Very nice article, very helpful for me and it will definitely helps to other Magento 2 developers as well.
Thank you once again.
Very good, I have several scripts that will apply in this way, thank Luka.