Is there a correct, and officially supported way, to add your CLI commands to a Magento 2 module? From what I've gathered your options are

  1. Add your command class to the commands argument of Magento\Framework\Console\CommandList via a di.xml file

  2. Register your command via \Magento\Framework\Console\CommandLocator::register in a registration.php file or a cli_commands.php file

None of these options are blessed with an @api. It's not clear, as extension developers, how we should add command line scripts such that they'll stick around version to version.

Does anyone know if there's an official Magento policy on The Right™ way to do this?

up vote 6 down vote accepted

cli_commands.php should be used in case the command is added in a non-modular package. So if the command is in the module and it's OK (expected) that it's available only when the module is enabled, di.xml should be used. If you don't want to add a module and want to have just an arbitrary Composer package, you can use cli_commands.php to register command there. Of course, it should be then really independent from Magento. Or, for now, this approach can be used to register command(s) necessary even if a module is disabled (ensure that it does not rely on any Module's logic that works only when it's enabled).

The correct way is:

Create your module as you do for any kind of module

Just create your registration.php file

\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'My_Module',
    __DIR__
);

And create you module.xml file:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="My_Module" setup_version="0.1.0">
    </module>
</config>

Add an entry in 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="my_command" xsi:type="object">My\Module\Command\Mycommand</item>
            </argument>
        </arguments>
    </type>
</config>

Create your command class:

<?php
namespace My\Module\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class Mycommand extends Command
{
    protected function configure()
    {
        $this->setName('my:command');
        $this->setDescription('Run some task');

        parent::configure();
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $output->writeln('Hello world!');
    }
}

To run your task just type:

php bin/magento my:command

About compatibility:

@api is not needed for commands, it is used for service contracts AFAIK.

If you need to let them compatible, just use an interface API inside your script instead of putting the logic inside it.

For example:

<?php
use My\Module\Api\TaskInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class MyCommand extends Command
{
    protected $taskInterface;

    public function __construct(
        TaskInterface $taskInterface
    ) {
        $this->taskInterface= $taskInterface;
        parent::__construct();
    }

    protected function configure()
    {
        $this->setName('my:command');
        $this->setDescription('Run some task');

        parent::configure();
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->taskInterface->runTask();

        $output->writeln('Done.');
    }
}
  • 1
    Useful information, +1, but are you sure about that? The Magento sample data modules use the cli_commands.php method, and that seems cleaner, and less likely to break if Magento changes the implementation of Magento\Framework\Console\CommandList – Alan Storm Apr 21 '16 at 19:36
  • I'm not 100% sure, only Magento itslef can answer this, but I have been playing a while on it and if you remove "cli_commands.php" you will still see your command available in your bin/magento. They also declared them on di.xml, so I think it is just something still there from beta version. Like it was for builders and factories. If you see, cli_command.php is only used in sample-data. – Phoenix128_RiccardoT Apr 21 '16 at 19:49
  • What makes you say that @api will become deprecated? – Kristof at Fooman Apr 21 '16 at 19:58
  • @Kristof, so sorry I did not correctly understood one of the trained partner solution slide, so ignore that sentence. I modified my post removing it. – Phoenix128_RiccardoT Apr 21 '16 at 20:01

if I got it right, commands defined in the CommandList over DI are only available in an installed Magento Instance and also only for Magento Modules (since they have to be defined in the di.xml): https://github.com/magento/magento2/blob/6352f8fbca2cbf21de88db0cf7f4555bfc60451c/lib/internal/Magento/Framework/Console/Cli.php#L124

the Magento\Framework\App\DeploymentConfig::isAvailable() in the above method checks for an install Date in the Config to check for an installed Magento2: https://github.com/magento/magento2/blob/6352f8fbca2cbf21de88db0cf7f4555bfc60451c/lib/internal/Magento/Framework/App/DeploymentConfig.php#L83).

The commands defined in the Magento\Framework\Console\CommandLocator on the other hand are always available and even can be defined by non Magento Modules through the static CommandLocator::register method in a file autoloaded by composer (for example cli_commands.php)

https://github.com/magento/magento2/blob/6352f8fbca2cbf21de88db0cf7f4555bfc60451c/lib/internal/Magento/Framework/Console/Cli.php#L130

https://github.com/magento/magento2/blob/6352f8fbca2cbf21de88db0cf7f4555bfc60451c/lib/internal/Magento/Framework/Console/Cli.php#L146

So I think both methods are needed and have its right to exist

  • That's a good observation +1, but why you should need a Magento script while the related module is not installed? – Phoenix128_RiccardoT Apr 21 '16 at 20:30
  • 1
    its about the magento instance beeing not installed. There are several commands available before you install magento2. For example the sampledata:deploy command, which makes sense if you want to directly install the sample data with magento2 setup. – David Verholen Apr 21 '16 at 20:33
  • I think you are right. So the "correct" way for a Magento module is to use di.xml (as in my answer) and using "cli_command.php" when you need to run some task before your module is installed. It does make sense. – Phoenix128_RiccardoT Apr 21 '16 at 20:36
  • ye, I think that should be it. +1 because I think you have the right (and very well explained) answer, but this did not fit into a comment ;) – David Verholen Apr 21 '16 at 20:39
  • Thank you @David Verholen, my +1 was meaning I gave you +1, not that you deserved just a +1 ;) – Phoenix128_RiccardoT Apr 21 '16 at 20:41

Your Answer

 
discard

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.