bdn_cli_add-new-console-command-to-magento2

Add new console command to Magento2

Structure:
1. Magento2 default console commands
2. Add new console command
3. Process input and output of the command
  3.1 Colorfull output
4. Shortcut for using everyday commands


Magento2 provides console commands that helps the developers to do some actions fast and easily. In my opinion, the command line interface (CLI) is more comfortable than doing some actions via admin panel.
For example, if you need to clean cache, just write command in terminal

php bin/magento cache:clean

and your cache will be cleaned.
In this article we are going to understand:
– how to add new console command in Magento2;
– how to implement some logic into the command;
– how to make the output and get the input from the command line;
– what commands Magento2 already has and which are is most useful of them;
Also I want to share my shortcut of using everyday commands in the fastest way.
I have prepared the module, that will be described below, you can download it from here or write everything step by step on your own.


1. Magento2 default console commands

Magento provides a powerful command line, based on Symfony Console Component. You can check the Symfony documentation for deeper understanding of how it works and what power is under the hood.
To show all the commands that Magento2 has, write in the console

php bin/magento list

You will see the list of all the commands that are available in the system like on the picture below:
bdn_cli_list


The commands are grouped by functionality that they provide:
- admin
Can create admin user or unlock admin account
- app
Creates dump of application or imports data from shared configuration files to appropriate data storage
- cache
All manipulations with application cache: clean, flush, disable, enable, make a status.
- catalog
Creates resized product images or removes unused product attributes.
- config
Changes system configuration values, shows configuration values.
- cron
Generates and installs crontab, removes tasks or runs tasks.
- customer
Upgrades customer’s hash according to the latest algorithm
- deploy
Shows or sets application mode. Magento2 has 3 application modes: production, developer, default.
- dev
Here are quite comprehensive commands to work with: DI information, query logs, template hints, running tests, URN map generating and other different commands.
- i18n
Works with internalisation information of the project.
- indexer
Commands for working with indexes: index info, reindex, reset index, show and set index modes.
- info
Give an information about the application: backups, currency, dependencies, language, timezone.
- maintenance
Maintenance mode can be easily enabled, disabled and little more.
- module
It’s a usefull command, for enabling, disabling, uninstalling modules.
- sampledata
A tool for deploying or removing sample data from your store.
- setup
An important group of commands that gives possibilities for working with backups, configurations, database data and schemas, compiling DI files and other usefull commands.
- store
Displays the list of stores and websites
- theme
Uninstalls themes
- varnish
Generates Varnish VCL

So, Magento2 provides quite good and powerfull commands for developers. The developers can extend each group of commands or create a new one. Now we will make our new command.


2. Add new console command

First of all we shoud create a new module with the name Bdn_Cli.
You can find information how to create an empty module in this article.
The skeleton of the module should look like on the picture below:
bdn_cli_scratch_module

  • Create a folder from the root of your Magento2 folder: <projectRoot>/app/code/Bdn/Cli/Console/Command
  • Make a file FirstCommand.php in it with the content:
<?php
declare(strict_types=1);

namespace Bdn\Cli\Console\Command;

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

/**
 * Class CityImport
 *
 * @package Eltrino\MeestExpress\Console\Command
 */
class FirstCommand extends Command
{

    /**
     * {@inheritDoc}
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * @inheritDoc
     */
    protected function configure()
    {
        //name of the command, name before first ':' is equal to the command group name 
        $this->setName('bdn:cli:first');
        //short description shown while running "php bin/magento list"
        $this->setDescription('First console command description');
        //full command description shown when running the command with the "--help" option
        $this->setHelp('Long description of the command');

        parent::configure();
    }

    /**
     * @inheritDoc
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $output->writeln('First command is working');
    }
}
Let’s understand the responsibility of each function in the current file:
  • public function __construct() can be used for injection of services required
  • protected function configure() are responsible for configuration of the command.
  • protected function execute() are responsible for execution, after the command call

Next, let’s make our system know about the new command.
– Create a new file di.xml in the folder <projectRoot>/app/code/Bdn/Cli/etc

<?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="bdn_cli_first" xsi:type="object">Bdn\Cli\Console\Command\FirstCommand</item>
            </argument>
        </arguments>
    </type>
</config>

This file is responsible for all the Dependency Injections in our module. Here we give the information about a new command and write a path where the command code is located.


Now we should enable the module and ask the application to show all the commands in the system:

php bin/magento module:enable Bdn_Cli
php bin/magento setup:upgrade
php bin/magento setup:di:compile
php bin/magento list

Find our new command in the list:
bdn_cli_command_in_list

Finaly, run our new command:

php bin/magento bdn:cli:first

bdn_cli_run_first_command


3. Process input and output of the command

Each console command can read the input, and return the output.
We are going to a create new command, where we get the product SKU like an input argument and return the product quantity.

  • Create a new file with the command app/code/Bdn/Cli/Console/Command/ProductQtyBySku.php
<?php
declare(strict_types=1);

namespace Bdn\Cli\Console\Command;

use Magento\Framework\App\Area;
use Magento\Framework\App\State;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Magento\CatalogInventory\Model\Stock\StockItemRepository;
use Magento\Catalog\Model\Product;

/**
 * Class CityImport
 *
 * @package Eltrino\MeestExpress\Console\Command
 */
class ProductQtyBySku extends Command
{

    const PRODUCT_SKU_ARGUMENT = 'product_sku';

    /**
     * @var StockItemRepository
     */
    private $stockItemRepository;

    /**
     * @var Product
     */
    private $product;
    /**
     * @var State
     */
    private $state;

    /**
     * {@inheritDoc}
     */
    public function __construct(
        StockItemRepository $stockItemRepository,
        Product $product,
        State $state
    ) {
        parent::__construct();
        $this->stockItemRepository = $stockItemRepository;
        $this->product             = $product;
        $this->state               = $state;
    }

    /**
     * @inheritDoc
     */
    protected function configure()
    {
        $this->setName('bdn:product:qty');
        $this->setDescription('Get product qty by SKU');

        //add argument to our command
        $this->addArgument(self::PRODUCT_SKU_ARGUMENT, InputArgument::REQUIRED, 'Product SKU');

        parent::configure();
    }

    /**
     * @inheritDoc
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        //we need it, to show area where qty is looking for
        $this->state->setAreaCode(Area::AREA_ADMINHTML);

        //get argument value from input
        $productSku = $input->getArgument(self::PRODUCT_SKU_ARGUMENT);

        //get product entity_id by sku
        $productId = $this->product->getIdBySku($productSku);

        if ($productId) {
            //get product quantity by product entity_id
            $qty = $this->stockItemRepository->get($productId)->getQty();

            $output->writeln("Product '{$productSku}' has {$qty} item(s)");
        } else {
            $output->writeln("Product with SKU: {$productSku} not exist.");
        }
    }
}

  • Declare a new command in Bdn/Cli/etc/di.xml
...
<item name="bdn_product_qty_by_sku" xsi:type="object">Bdn\Cli\Console\Command\ProductQtyBySku</item>
...

Run the commands:

php bin/magento setup:di:compile
php bin/magento bdn:product:qty yourSku

In the ouput you will see the response like on the picture below:
bdn_cli_run_product_qty_command


3.1. Colorfull output

When you want to show a lot of information in the console, it will be helpfull to use colors in the output. Just test it inside your function protected function execute(...)

$output->writeln('<info>green text</info>');
$output->writeln('<comment>yellow text</comment>');
$output->writeln('<question>black text on a cyan background</question>');
$output->writeln('<error>white text on a red background</error>');

You can also set these colors and options directly inside the tag name:
Available foreground and background colors are: black, red, green, yellow, blue, magenta, cyan and white.

$output->writeln('<fg=red>red text</>');
$output->writeln('<fg=yellow;bg=magenta>yellow text on a magenta background</>');
$output->writeln('<bg=blue;options=bold>bold text on a blue background</>');

bdn_cli_colorfull_output
So it’s simple and usefull.

4. Shortcut for using everyday commands

As a tipical developer I’m lazy and I want to optimize routine actions. Every working day I’m using the same commands more than 20 times in total:

php bin/magento cache:flush
php bin/magento setup:di:compile

So I decided to optimize it and wrote a small bash script with shortcuts for these commands:

#!/usr/bin/env bash

if [ "$1" == "1" ]; then
    php bin/magento cache:flush
elif [ "$1" == "2" ]; then
    php bin/magento setup:di:compile
elif [ "$1" == "3" ]; then
    php bin/magento setup:upgrade
elif [ "$1" == "4" ]; then
    php bin/magento setup:static-content:deploy
else
    php bin/magento $@
fi
  • Create a file m2.sh with this simple script and locate it the root of the project <projectRoot>/tools/m2.sh

  • After that, you should add symlink of this script to /bin folder

For MacOs:

ln -s  <fullPathToTheProjectRoot>/tools/m2.sh /usr/local/bin/m2 

For Linux:

ln -s  <fullPathToTheProjectRoot>/tools/m2.sh /bin/m2

But it can be changed based on your operation system and version of it.

In this scenario file content is important, not the location of the file!
So now if you want to clean cache or run DI compile command or some other commands, just write them easily in the terminal:

m2 1
m2 2
m2 command:that:you:need

Hope this small script will make your development more convenient.


Conclusion

You have enough knowledge for creating any console command in Magento2, because you practised all important features for the new command. Let’s summarise: we created a new command with all required information, learnt how CLI Input and Output are working, used DI inside the command, practised colofult ouput for the console. Also optimised our work with command line. I am sure, it was usefull and clear, so invent something for console command and implement it from the sratch.

Sincerely yours,
Bohdan.