March 25, 2015
Nowadays, a well-designed, easy to use Command-Line Interface (CLI) is a must-have feature for solid backend software. This post explores different options we considered when developing the Magento 2 strategy for the CLI.
A good CLI seamlessly integrates into automated processes. For example, continuous deployment allows pre-configuring applications as a part of environment installation and continuous integration can use the application as a part of a bigger workflow. Besides, if a program is run on a remote environment, a lot of users will find it faster to do routine maintenance using commands in the shell than via a web interface. Running one command is sometimes equivalent to passing through multiple steps in UI. Commands are easy to store in a notebook or another script and repeatedly run it over again with the same result. In order to achieve same level of automation with web UI special software should record user actions and replay them back.
Single shell command
The modern trend in the design of command-line interfaces is to have a single shell entry point where the name of shell script is followed by the command name to invoke application functionality. Examples are "docker run” or “git clone”. This design has certain advantages:
A single script is easy to find in comparison with multiple scripts located in arbitrary places in file system. Once it is added to the PATH, it will always be accessible by its name.
With the single entry point it is possible to implement help functionality which will provide a list of all available commands and its parameters.
Commands are likely to follow same conventions and it will require less time to learn them.
If you take a look at Magento today, you will find a number of shell script utilities to automate development and maintenance operations. Some duplicate functionality from the admin backend (such as “clean cache” or “reindex”), others are needed during deployment such as “compile dependency injection definitions”. All of these scripts were created at different times required by occasion or as a result of the growing maturity of Magento’s internal CI/CD process. We realize that the amount of scripts will grow as more developers will use the product and more automated environments will go live. With this in mind, we have investigated the best option on how to unify shell scripts across application and to use industry standards to design CLI.
We plan to create a common entry point for all the scripts with the set of commands and hints on how to use them, something like “magento install”. Here “magento” is the name of the unifying script.
Also, we will use a framework that allows external parties to register new commands in the CLI script which will handle all the routine work with processing the arguments, displaying list of commands, and displaying help. It should not be harder to develop a new command than creating a class. Besides, there are certain requirements dictated by the nature of Magento:
The way commands are registered in the framework will follow the Magento modularity strategy. It will be possible for every module to add commands to the CLI and that code will reside inside the module. It will not require any other dedicated code pool for everybody to drop commands into. The framework will collect commands from across the modules.
It will be easy to develop commands in the same style as all other Magento code, so that the developer can take advantage of dependency injection, plugins, and so on for commands. A command class should look the same as any other class in Magento.
Having this in mind we found few options on the market with respect to the framework choice.
The first option we considered was Magerun. It is one of the best CLI for Magento, built by Magento community member Christian Münch. Currently it is in the state of active development for Magento 2, with 34% of all the commands supported in Magento 1 being already ported to Magento 2: http://magerun.net/n98-magerun2-project-update-2015-02/. This framework is very advanced, is built on top of Symfony CLI, and fulfills base requirements to the framework. Besides that, it adds a lot of Magento specific features:
It allows modules to develop custom commands. Based on documentation for the Magento 1 version of Magerun, in order to register new command, developer should drop n98-magerun.yaml file to the one of supported locations, such as app/etc, lib, or other places in the file system outside of the Magento root directory.
It provides access to the Magento application context. This is done in a fancy way, where a separate type of Symfony Application handles logic of initialization Magento initialization and provides access to the context objects. The Symfony CLI Command class has a lot of customizations as well, which allow it to operate inside Magento and to have an access to the context information.
This fulfills requirement for modularity and application context, however not exactly in the way we wanted to see it.
For example, the registration file, n98-magerun.yaml, cannot be defined in a module directory, so the module would need to find a way to add it in the external locations. This will make the process of module installation more complex, because Composer will need to handle such files and put them in proper location.
Application context provided via separate methods in application makes it more complicated for the command to retrieve dependencies. It has do things like $this->getApplication()>get('Magento\Framework\Store\StoreManagerInterface’) instead of just having the dependency in the class constructor.
As a result we decided to use the bare Symfony CLI component instead. It has all the framework for CLI commands in place, missing only Magento-specific features. We will need to implement modularity, so that commands will be discovered in the modules. We plan to use a command locator class, which will be instantiated via dependency injection object manager, so that it will allow registering new commands using DI configuration. Commands instantiated via DI will be first-class citizens inside Magento because all the context information and framework support will be available via classes injected into constructor.
Besides, potentially it will be compatible with the Magerun, so the current body of command existing in Magento ecosystem will still work. We talked to Christian and he agreed that this approach will give a way for Magerun to re-use Magento commands using just a lightweight framework.
A good CLI is a beneficial feature for advanced applications and it is time for Magento to introduce it. The approach which Magento have selected is to use an industry standard framework. It will have little to no modification needed to run commands specific to Magento. Besides, Magento will provide custom commands registration mechanism which follows Magento 2 modularity.