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.

meestexpress-api-magento2-module-logo

MeestExpress API Magento2 module

Structure:
1. Functionality
2. Compatibility
3. How to install MeestExpress API
  3.1. Install MeestExpress API via composer
  3.2. Install ready-to-paste package
4. How to uninstall MeestExpress API
  4.1. Installed MeestExpress API via composer
  4.2. Installed ready-to-paste package approach
5. User guide
6. Developer guide
  6.1. Sample module how to use this MeestExpress API module
7. Contribution


MeestExpress is one of the biggest postal and courier companies in Ukraine that organize delivery of parcels, money, letters for businesses and individuals. More information you can find here https://www.meest-express.com.ua
This module helps to communicate with MeestExpress API v3.0


1. Functionality

  • Authenticate in MeestExpress system
  • Save token for future request
  • Use all endpoints of MeestExpress API

2. Compatibility

  • Magento 2.1, 2.2, 2.3

3. How to install MeestExpress API

We are going to install MeestExpress API module. There are 2 options:

  • via composer
  • via copy paste approach

3.1. Install MeestExpress API via composer (recommended)

Run the following commands in Magento 2 root folder:

composer require bdn/magento-2-meestexpress-api
php bin/magento setup:upgrade
php bin/magento php bin/magento setup:di:compile

3.2. Install ready-to-paste package

  • Download the latest version from Github
  • Unzip it to your project. Folder: app/code/Bdn/MeestExpress
  • Run the following commands in Magento 2 root folder:
php bin/magento setup:upgrade
php bin/magento php bin/magento setup:di:compile

4. How to uninstall MeestExpress API

We will uninstall MeestExpress API module. Choose the way how you installed the module previously.

4.1. Installed MeestExpress API via composer

Run the following commands in Magento 2 root folder:

php bin/magento module:disable Bdn_MeestExpress
php bin/magento setup:di:compile
composer remove bdn/magento-2-meestexpress-api

4.2. Installed ready-to-paste package approach

  • Delete the folder: app/code/Bdn/MeestExpress
  • Run the following commands in Magento 2 root folder:
php bin/magento module:disable Bdn_MeestExpress
php bin/magento setup:di:compile

5. User guide

  • Login into Magento Admin Panel
  • Go to the Admin Panel > Stores > Configuration > Services > MeestExpress and insert your MeestExpress credentials
    meestexpress-api-magento2-module-save-credential-gif
  • Go to the terminal and run command for the authentication and saving the token for future requests to the MeestExpress system.
php bin/magento bdn:meestexpress:auth

If thecredentials are correct, you will see this:
meestexpress-api-magento2-module-success-auth-gif


If the credentials are not correct, you will see this:
meestexpress-api-magento2-module-failed-auth-gif

6. Developer’s guide

Developer can use this module like a service via contsuctor Dependency Injection

  • Add service to your constructor
/**
 * @var \Bdn\MeestExpress\Model\Service\Api
 */
private $meestExpressApi;

/**
 * Your constructor.
 *
 * @param \Bdn\MeestExpress\Model\Service\Api $meestExpressApi
 */
public function __construct(
    \Bdn\MeestExpress\Model\Service\Api $meestExpressApi
) {
    $this->meestExpressApi = $meestExpressApi;
}

  • Use public function in any place where you need it.
$this->meestExpressApi->countrySearch('Ukra');
$this->meestExpressApi->zipCodeSearch('79038');
$this->meestExpressApi->tracking('TAC-2550636');

6.1 Sample module how to use this MeestExpress API module

Here you can find a sample module, which shows how a developer can use MeestExpress API module.
IMPORTANT: MeestExpress API module SHOULD BE installed before!

Steps:

  • Download and unpack sample module from here
  • Move it to the folder: app/code
  • Run the following commands
php bin/magento module:enable Bdn_MeestExpressUsage
php bin/magento setup:upgrade
php bin/magento setup:di:compile
php bin/magento cache:flush

{ 
   "status":"OK",
   "info":{ 
      "fieldName":"",
      "message":"",
      "messageDetails":""
   },
   "result":[ 
      { 
         "countryID":"c35b6195-4ea3-11de-8591-001d600938f8",
         "countryDescr":{ 
            "descrUA":"\u0423\u041a\u0420\u0410\u0407\u041d\u0410",
            "descrRU":"\u0423\u041a\u0420\u0410\u0418\u041d\u0410",
            "descrEN":"UKRAINE "
         }
      }
   ]
}

  • Test other available methods in the file: app/code/Bdn/MeestExpressUsage/Controller/Index/Index.php

RECOMMENDATION: Create a cron job for getting new access token from the MeestExpress service

  • Create a class which calls auth method and save a return api token
    app/code/Bdn/MeestExpressUsage/Cron/Auth.php
<?php
declare(strict_types=1);

namespace Bdn\MeestExpressUsage\Cron;

use Bdn\MeestExpress\Api\ApiInterface;
use Bdn\MeestExpress\Model\Config\MeestExpressConfig;

class Auth
{

    /**
     * ApiAuth constructor.
     *
     * @param ApiInterface       $apiService
     * @param MeestExpressConfig $meestExpressConfig
     */
    public function __construct(
        ApiInterface $apiService,
        MeestExpressConfig $meestExpressConfig
    ) {
        $this->apiService         = $apiService;
        $this->meestExpressConfig = $meestExpressConfig;
    }

    public function execute()
    {
        $response = $this->apiService->auth();
        $this->meestExpressConfig->saveApiToken($response['result']['token']);
    }
}
  • Create a crontab
    app/code/Bdn/MeestExpressUsage/etc/crontab.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">
    <group id="meestexpress_auth">
        <job name="auth" instance="Bdn\MeestExpressUsage\Cron\Auth" method="execute">
            <schedule>* */6 * * *</schedule>
        </job>
    </group>
</config>

7. Contribution

This MeestExpressAPI magento2 module has been covered for 40% of all native MeestExpress API functionality. The main part is ready to use. I want to hear a feedback from developers and users, for the future support of this module.
If you are developer, you can easily find some issues(functions) that need to be covered.
Everything is described in Github Issue Page.


Conclusion

We have installed MeestExpress API module for Magento2 via one of the best ways that you can follow: a composer or a copy paste approach. Saved credentials for accessing to API. Understood how the developers can easily use this module for their purpose.
I will appreciate some feedback from you. Leave a comment below or find any convenient way in the contact page, I’ll answer as soon as possible, Thank You.

Sincerely yours,
Bohdan.

how-to-create-module-in-magento2-logo

How to create a module in Magento2

Structure:
1. What is the module and why are we using it?
2. Where are Magento2 modules located?
3. Creating the first module and describing its structure
4. How to check that the module is working?


We are going to create the Magento2 module. You will find the information about the required files for your module here. I’ll explain in details what each file and directory does. We enable and disable the module in the Magento2 system.
I have prepared module, that will be described below, you can download it from here or write everything step by step by your own.
Let’s go.

1) What is the module and why are we using it?

The module is a directory where all code base is located (controllers, blocks, configurations, models, etc).
This code is responsible for some specific functionality and business logic. Best practice says that module should be self-sufficient. That means to have minimum relations to other modules or systems (but everybody knows that sometimes it is impossible).

2) Where are Magento2 modules located?

We need to know where our module should be located and where modules are located in the system in general.

  • Usually, custom module is located in : <projectRoot>/app/code/<Vendor>/<ModuleName>
    Then, we are creating our first module. First module location is: <projectRoot>/app/code/Bdn/Intro

Above I described a path where custom modules are located. Each eCommerce store that is being developed, often has a directory with custom modules:

<Vendor> is name of a company

<ModuleName> is a name of functionality that a developer extends from the core functionality or their own custom solutions.

For example, we are extending some checkout or wishlist functionality, then the name of module will be app/code/<Vendor>/Checkout or app/code/<Vendor>/Wishlist witch describes the area that has been changed.

  • Modules that are installing via composer are located in: /vendor/<Vendor>/module-<module-name>
    For example magento module that is responsible for catalog rules: /vendor/magento/module-catalog-rule

In the nearest future, I’m going to describe how to deploy your module to the Packagist and install it via the Composer to any Magento2 project.

3) Creating the first module and describing its structure

Now we are coming to the most interesting part of this article. We will create a minimum needed for finding the module in the system.

So, let’s go step by step:

  • Create a folder from the root of your Magento2 folder: <projectRoot>/app/code/Bdn/Intro
  • Make a file registration.php in it with the content:
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Bdn_Intro',
    __DIR__
);
  • Next create a folder etc inside our module <projectRoot>/app/code/Bdn/Intro/etc and make a file module.xml with the content:
<?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="Bdn_Intro" setup_version="0.0.1">
    </module>
</config>

These are all the files that are needed for creating the first module.
Your module should look like screenshot below
bdn_intro_module_structure

Let’s describe these 2 files and 1 folder:

Files:
<projectRoot>/app/code/Bdn/Intro/registration.php – it’s typically the same file in ALL modules. Registrate the module path in the system registry, for the future access to the files of the module.
<projectRoot>/app/code/Bdn/Intro/etc/module.xml – the file contains basic information about your modules: name, version, dependencies.

Name – it’s just a name of your module, in format <Vendor>_<ModuleName>
Version – is a version of the module. Magento2 uses it when we make some updates in the database.
Dependencies – are module dependencies to the other modules in the system. They help to understand what modules require for correct work of the current module. (in this module we don’t use dependencies).

Folder
<projectRoot>/app/code/Bdn/Intro/etc – this folder contains all module configurations like (config, di, events, webapi, routes, system, etc.), all files that are stored in etc is .xml files. That’s all what we need to know about it for now. Soon we are going to learn each configuration and understand how and why Magento2 uses it.

Also Magento recommends to create composer.json file in the root of your module <projectRoot>/app/code/Bdn/Intro/composer.json, but it’s not necessary for us, we will explore it in the article “How to add Magento2 module to Packagist”.

4) How to check that the module is working?

As we already know, Magento2 is a modulary system, we can show all the modules that are enabled and disabled in our system. Let’s check our module that was found in the system.

Next commands we are launching in the console (terminal).

Go to the root of your project

cd <projectRoot>

Show all the modules in the system

php bin/magento module:status

bdn_intro_cli_module_status


Enable module

php bin/magento module:enable Bdn_Intro
php bin/magento setup:upgrade

bdn_intro_cli_module_enable


Run again the module status command, and find Bdn_Intro module in the list of enabled modules

php bin/magento module:status

bdn_intro_cli_module_status_enabled


Also you can check easily that your module was enabled in the file: <projectRoot>/app/etc/config.php
Open the file and find the line like this. That means that your module was enabled in the system.

'Bdn_Intro' => 1

For disabling the module, write this command

php bin/magento module:disable Bdn_Intro

And check it again

php bin/magento module:status

bdn_intro_cli_module_disable

Then, check the file <projectRoot>/app/etc/config.php and find this. That means that we disabled the module successfully.

'Bdn_Intro' => 0

Conclusion

You have created your first basic module in Magento2. Learnt how to check the module status via command line. You understood what files are required for the module sctructure. Now you are ready to create bigger and more interesting modules.

Sincerely yours,
Bohdan.