#RealMagento: Creating a Magento 2 Command Line Module + Video Tutorial

This blog post is the second in my series of Magento 2 tutorials I’ll be creating for the #RealMagento community. Here at Demac Media, our Magento development team have been busy learning the ropes of Magento2 to provide the best solutions to eCommerce businesses. If you haven’t already, click here to check out my last post wherein I show you how to create a basic Magento 2 Plugin. Watch the video walk-trough of the lesson learned in this tutorial below:

Step 0: Why Create Your Own Command Line Command?

In Magento 1 there were many different commands you could do from the command line, reindexall is one most familiar with. So why do you want to create your own?

Well, remember shell scripts? They weren’t bad, but I always felt they were not all that secure and they were a bit sloppy, requiring Mage.php and anything else you needed in order to access basic Magento functionality. It would be nice, when creating your own extension though, to let you have a command line that calls specific functionality for that extension. Magento 2 now allows you to do this and it’s a pretty great feature.

Step 1: What do you want to do with the command line?

ecommerce, web development, demac media, magento 2 command line, command line module, magento 2 tutorial, magento 2

Not that I need to drive this home to anyone, but the first step of creating any module is to determine what you want it to do. There are many different things that you might want to do with the command line command. Here are some examples:

  • Create User
  • Change User Password
  • Change user Email
  • Change Product Price
  • Change Product SKU

I am certain you can create a whole host of other things you might want to do, the above was just cogitating out loud as it were.

A colleague of mine, requested that I create something that would help him with indexing.

As you may know, indexers run in a specific order. Sometimes this colleague wants to run all theindexers after a certain point (for instance the customer grid was so large that reindex
all took too long). So he asked me to develop a command line command that will take in an argument of the indexer to start at, and run that indexer and all following ones.

First before we begin, download the .zips referenced in the tutorial by clicking the button below:





Click to Download the .zips for this Tutorial




Step 2: Creating the Command Line Module

So our first step is to create and register a new module. If you don’t know how to create a basic module please check my blog on creating Magento 2 modules and plugins by clicking here.

I am going to call my module ConsoleIndexer (so I will be placing it in Demac/ConsoleIndexer).

After you have created your module.xml and your registration.php. (If you are feeling lazy see attached zip file of the empty module I have created for this purpose).

Step 3: Enabling Module

After you have created your module.xml and your registration.php file you can then
enable your module by running bin/magento setup:upgrade or bin/magento module:enable [moduleName].

**If you are looking at the attachments this would be around ConsoleIndexer-BlankModule.zip

Step 4: Determining the Structure Of Your Module

An important step here is creating a usable structure for this module since we don’t want to be doing everything in one model.

1. I created a folder structure for our actual command, the model that will be interfacing with the command line:
Demac/ConsoleIndexer/Console/Command/PositionalReindexCommand.php

2. I have created a Model folder to put my interface and my model in:
Demac/ConsoleIndexer/Model/PositionalReindex.php
and
Demac/ConsoleIndexer/Model/PositionalReindexInterface.php

Step 5: Setting Up and Configuring your Command

Once you have set up your file structure we need to configure our command so that it is shown in the command line for bin/magento and so that it can be used.

So the first thing we need to do is make sure that our PositionalReindexCommand.php file has the appropriate resources it will need to interact with the command line. These are the resources we will need to insert into this php class in order to run our command:

    • use Symfony\Component\Console\Command\Command; * extend this for the command functions
    • use Symfony\Component\Console\Input\InputArgument; * allows us to accept Input Arguments for the command

use Symfony\Component\Console\Input\InputInterface; * Allows us to get the input from the command line
use Symfony\Component\Console\Output\OutputInterface; * Allows us to output to the command line

The next step we need to do is add a constructor and configure our command. See below for an example:


<?php /** * Created by PhpStorm. * User: davidphillip * Date: 2016-07-12 * Time: 6:53 PM */ namespace Demac\ConsoleIndexer\Console\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 PositionalReindexCommand extends Command{ /** * @var PositionalReindexInterface */ private $positionalReindexInterface; public function __construct(){ parent::__construct(); } protected function configure(){ $this->setName('positional:reindex');
        $this->setDescription('Sets the starting point of reindexall');
        $this->addArgument('reindex_command', InputArgument::REQUIRED, 'Reindex Command');

    }

    protected function execute(InputInterface $input, OutputInterface $output){

    }
}

So we are constructing the parent (Command) so that we have access to all the Command functions.

Next we are configuring our command. You see that we have set the name to ‘positional:reindex’. You can, of course, name it whatever you choose. We also set the Description and the Input argument names and whether or not it is required.
We also have instituted an execute function which is needed so that when the command is called we have a function that is executed. In this method we pass in the InputInterface and OutputInterface which allow us to communicate directly with the command line.

Next we need to set up our our di.xml (dependency injection file) which will add our command to the Commands List for bin/magento.

<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="positionalReindexCommand" xsi:type="object">Demac\ConsoleIndexer\Console\Command\PositionalReindexCommand</item>
            </argument>
        </arguments>
    </type>
</config>

Here you see we are adding our Class PositionalReindexCommand to the Magento Framework command list and specifying the path to the class.

Run bin/magento setup:upgrade again and it will update with the new configuration. After that type in bin/magento position and you will get an error and it will ask you if you meant “positional:reindex”. That is just a quick way of checking that your command is registered correctly.
**Note: If you are looking at the attachments this would be around ConsoleIndexer-Phase1.zip

Step 6: Setting Up Our Interface and Model

So now that we have our command set up, we need something that actually reindexes in the manner we want.

So in our PositionalReindexInterface.php we will set up a function that needs to be implemented by our model. See below:

 <?php /** * Created by PhpStorm. * User: davidphillip * Date: 2016-07-12 * Time: 6:54 PM */ namespace Demac\ConsoleIndexer\Model; interface PositionalReindexInterface { public function positionalReindex($indexerName, $output); }

Note we have indicated what arguments we need to pass into the the positionalReindex function.

Next we set up our Model that implements the above interface. See Below:

<?php /** * Created by PhpStorm. * User: davidphillip * Date: 2016-07-12 * Time: 6:54 PM */ namespace Demac\ConsoleIndexer\Model; class PositionalReindex implements PositionalReindexInterface { public function __construct(){ } public function positionalReindex($indexName, $output){ } }

**Note: If you are looking at the attachments this would be around ConsoleIndexer-Phase2.zip

Step 7: Make Our Functions do what we want

1. Add the resources you are going to need to reindex items.

namespace Demac\ConsoleIndexer\Model;

use Magento\Framework\Exception\LocalizedException;
use Magento\Indexer\Model\Indexer\Collection;
use Magento\Framework\Indexer\ConfigInterface;
use Magento\Framework\Indexer\IndexerInterface;

class PositionalReindex implements PositionalReindexInterface {

    /**
     * @var ObjectManager
     */
    private $objectManager;
    /**
     * @var Collection
     */
    private $collection;
    /**
     * @var ConfigInterface
     */
    private $configInterface;
    /**
     * @var IndexerInterface
     */
    private $indexerInterface;

    public function __construct(Collection $collection, ConfigInterface $configInterface, IndexerInterface $indexerInterface){

        $this->collection = $collection;
        $this->configInterface = $configInterface;
        $this->indexerInterface = $indexerInterface;
    }

You can see that we have added a bunch of stuff to be used inside the model. We have the Indexer Model, Collection, ConfigInterface, and IndexerInterface. Notice we have also added these to the model’s constructor for later use.

2. Next Create our positional reindex function

public function positionalReindex($indexName, $output){
    $indexers = $this->configInterface->getIndexers();
    $startIndexing = 0;

    foreach($indexers as $indexer){

        if($indexer['indexer_id'] == $indexName){
            $startIndexing = 1;
            $actualIndexer = $this->indexerInterface->load($indexer['indexer_id']);
            try{
                $startTime = microtime(true);
                $actualIndexer->reindexAll();
                $resultTime = microtime(true) - $startTime;
                $output->writeln($indexer['indexer_id'].' Reindex Successfully Completed In: '.$resultTime);
                $output->writeln($indexer['indexer_id'].' Status: '.$actualIndexer->getStatus());
            }
            catch(LocalizedException $e){
                $output->wrtieln($e->getMessage());
            }
            catch(\Exception $e){
                $output->wrtieln($e->getMessage());
            }


        }
        elseif ($startIndexing==1){
            $actualIndexer = $this->indexerInterface->load($indexer['indexer_id']);
            try{
                $startTime = microtime(true);
                $actualIndexer->reindexAll();
                $resultTime = microtime(true) - $startTime;
                $output->writeln($indexer['indexer_id'].' Reindex Successfully Completed In: '.$resultTime);
                $output->writeln($indexer['indexer_id'].' Status: '.$actualIndexer->getStatus());
            }
            catch(LocalizedException $e){
                $output->wrtieln($e->getMessage());
            }
            catch(\Exception $e){
                $output->wrtieln($e->getMessage());
            }
        }
    }

}

 

Ok, a lot is going on here, so let’s walk through it.

 

Our positionalReindex function is taking in two arguments, $indexName and $output. The index name is actually the indexers ID that we will use to load the indexer. The $output is the output interface from our Command file so that we can output to the command line.

$indexers = $this->configInterface->getIndexers();
$startIndexing = 0;

The above is using the Indexer configInterface to get all the Indexers. Apparently this does not actually return the indexers themselves but an array of data about each indexer (including the Indexer ID).

I also added in a $startIndexing variable in order to check to see if we have reached the point where we have started indexing.


foreach($indexers as $indexer){

    if($indexer['indexer_id'] == $indexName){
        $startIndexing = 1;
        $actualIndexer = $this->indexerInterface->load($indexer['indexer_id']);
        try{
            $startTime = microtime(true);
            $actualIndexer->reindexAll();
            $resultTime = microtime(true) - $startTime;
            $output->writeln($indexer['indexer_id'].' Reindex Successfully Completed In: '.$resultTime);
            $output->writeln($indexer['indexer_id'].' Status: '.$actualIndexer->getStatus());
        }
        catch(LocalizedException $e){
            $output->wrtieln($e->getMessage());
        }
        catch(\Exception $e){
            $output->wrtieln($e->getMessage());
        }


    }
    elseif ($startIndexing==1){
        $actualIndexer = $this->indexerInterface->load($indexer['indexer_id']);
        try{
            $startTime = microtime(true);
            $actualIndexer->reindexAll();
            $resultTime = microtime(true) - $startTime;
            $output->writeln($indexer['indexer_id'].' Reindex Successfully Completed In: '.$resultTime);
            $output->writeln($indexer['indexer_id'].' Status: '.$actualIndexer->getStatus());
        }
        catch(LocalizedException $e){
            $output->wrtieln($e->getMessage());
        }
        catch(\Exception $e){
            $output->wrtieln($e->getMessage());
        }
    }
}

Now for the biggie. In this foreach we loop through the indexers we have and compare the index id with the value passed into the function. Once we have reached that we set $startIndexing = 1 so that the elseif will keep indexing even though the index id doesn’t match.

Once it’s matched or we have $startIndexing equalling 1 we need to get the actual indexer. We do this by calling our IndexInterface and passing the index id into the load function. After that we have the actual indexer and can run the reindexAll() function. This doesn’t run all indexers it just reindexes that specific indexer.

The rest of this is just frills where I have a calculator that computes the time it took to reindex and it outputs to the command line both the reindex time and the indexer’s current status post reindexing.

**Note that I did this in a try/catch statement. If you look at the actual indexers it does it this way as well and so is probably the best way to do this.

Step 8: Using this model in the Command Class

The first thing we need to do is go back into our di.xml and create a preference for our model instead of our interface for the function positionalReindex(). This is so that the function, when called, will actually do something.

See below:

<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="positionalReindexCommand" xsi:type="object">Demac\ConsoleIndexer\Console\Command\PositionalReindexCommand</item>
            </argument>
        </arguments>
    </type>
    <preference for="Demac\ConsoleIndexer\Model\PositionalReindexInterface" type="Demac\ConsoleIndexer\Model\PositionalReindex"/>
</config>[/xml]


** You will see that we have added a preference for our PositionalReindex model now

The next step is adding the following to our PositionalReindexCommand.php class:


use Demac\ConsoleIndexer\Model\PositionalReindexInterface;

After adding the above to the rest of the assets in the class you need to add it to your constructor.

/**
 * @var PositionalReindexInterface
 */
private $positionalReindexInterface;

public function __construct( PositionalReindexInterface $positionalReindexInterface){
    parent::__construct();
    $this->positionalReindexInterface = $positionalReindexInterface;
}

Finally we need to set up our execute to run our positionalReindex() function. See below:

protected function execute(InputInterface $input, OutputInterface $output){

try{
$reindexId = $input->getArgument('reindex_command');
$this->positionalReindexInterface->positionalReindex($reindexId,$output);
}
catch(\Exception $e){
$output->writeln($e->getMessage());
}
}

In the above function we are grabbing the input argument from the command line and passing that into the our positionalReindex function called from the PositionalReindexInterface we have recently declared in our constructor.

Step 8: Finish Setting up Module and test

So, we are basically done, but since we have changed the di.xml we need to run the bin/magento setup:upgrade command again. After that has been successfully run you can try out the command.

  • Use bin/magento indexer:info to get a list of the indexers and their ids
  • Choose an id and copy it
  • run bin/magento positional:reindex [indexer_id]

This should output each indexer that it is running with the status post indexing, skipping all the ones above it.

**Note: If you are looking at the attachments this would be around ConsoleIndexer-Phase3.zip

And you are finished. You have created your first Magento 2 Command Line command.

Huzzahs are in order! Attached to this blog is a video tutorial walking you through the above exercise, a three phase setup of the above module, click here to watch it now.

ecommerce, web development, demac media, magento 2 command line, command line module, magento 2 tutorial, magento 2