Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Console commands

A module can add its own commands to bin/magento. They are handy for running things not tied to a page: an import, a recalculation, a data check. A command is a plain class based on the Symfony Console component, which other frameworks use as well.

Where a command lives

Command classes go into the module's Console/Command folder and are registered in etc/di.xml. There is no generator — the file is created by hand.

app/code/Acme/Hello/
├── Console/
│   └── Command/
│       └── CountReviews.php    new file
└── etc/
    └── di.xml                  changed

The command class

// app/code/Acme/Hello/Console/Command/CountReviews.php
namespace Acme\Hello\Console\Command;
 
use Acme\Hello\Model\ResourceModel\Review\CollectionFactory;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
 
class CountReviews extends Command
{
    public function __construct(private CollectionFactory $collectionFactory)
    {
        parent::__construct();
    }
 
    protected function configure(): void
    {
        $this->setName('hello:reviews:count')
            ->setDescription('Shows the number of reviews');
    }
 
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $count = $this->collectionFactory->create()->getSize();
 
        $output->writeln("<info>Reviews: $count</info>");
 
        return Command::SUCCESS;
    }
}

The configure() method sets the name and the description; the execute() method does the work and returns an exit code: Command::SUCCESS or Command::FAILURE. By convention the name starts with the module name. The constructor must call parent::__construct().

Registration

A command is added to the command list through an array argument in di.xml:

<!-- app/code/Acme/Hello/etc/di.xml -->
<type name="Magento\Framework\Console\CommandListInterface">
    <arguments>
        <argument name="commands" xsi:type="array">
            <item name="hello_reviews_count" xsi:type="object">Acme\Hello\Console\Command\CountReviews</item>
        </argument>
    </arguments>
</type>

This is the technique from the arguments lesson: a module appends its item to an array of a core class.

Running

bin/magento cache:clean
bin/magento hello:reviews:count

The command prints Reviews: 2. It also shows up in bin/magento list in the hello group.

Arguments and options

An argument is a value after the command name; an option is a named key with two dashes. They are declared in configure():

// app/code/Acme/Hello/Console/Command/CountReviews.php
use Symfony\Component\Console\Input\InputOption;
 
protected function configure(): void
{
    $this->setName('hello:reviews:count')
        ->setDescription('Shows the number of reviews')
        ->addOption('min-rating', null, InputOption::VALUE_REQUIRED, 'Minimum rating', 1);
}
 
protected function execute(InputInterface $input, OutputInterface $output): int
{
    $minRating = (int) $input->getOption('min-rating');
 
    $collection = $this->collectionFactory->create();
    $collection->addFieldToFilter('rating', ['gteq' => $minRating]);
 
    $output->writeln('<info>Reviews: ' . $collection->getSize() . '</info>');
 
    return Command::SUCCESS;
}
bin/magento hello:reviews:count --min-rating=5

The command counts only reviews rated five. A required unnamed value is declared with addArgument('file', InputArgument::REQUIRED, 'Description') and read with $input->getArgument('file').

Styling the output

TagWhat it looks like
infoGreen text
commentYellow text
errorWhite text on a red background

Tags are written right in the string, as in the example above.

Common mistake

Magento creates the objects of all commands on every bin/magento run, even when another command is called. A heavy dependency in the constructor — a session, the product repository — slows down every command and sometimes breaks setup:install while the database is still empty. Substitute such dependencies with a proxy in di.xml, as shown in the arguments lesson: the object is created only on first use.

Thanks for reading!

Donate to the site — we work hard for you.