DEV Community

Cover image for Adobe Commerce CLI commands - or how to create a problem out of nothing

Adobe Commerce CLI commands - or how to create a problem out of nothing

Table Of Contents

Intro

Hello everyone,

In this article, I'll provide practical examples showcasing how unexpected challenges can arise in Magento 2 & Adobe Commerce projects while building and/or using custom CLI commands from 3rd parties. This becomes especially significant while working having CI/CD and performing project setups and test execution in pipelines.

Introducing the main "hero" of our article: ISSUE: magento setup command is throwing magento.flag does't exist.. We'll take a deeper look into this issue, understanding its roots and finding ways to address it.

Disclaimer: Just a quick heads-up on naming - both Adobe Commerce and Magento 2 are covered in this article. After all, Adobe Commerce is Magento. Long Live Magento! 😁

The Underlying Problem & Its Roots

While working on various Magento 2 projects, I frequently encountered snags when performing a clean setup of a project using a freshly created database. This usually happens during integration or functional test setups in pipelines & local environments. Still, this issue isn't exclusive to that scenario and can pop up during upgrades, migrations, or deployments.

The culprits? Here are two pesky errors you might recognize:

The default website isn't defined. Set the website and try again.
Enter fullscreen mode Exit fullscreen mode

For Commerce editions, there's also:

SQLSTATE[42S02]: Base table or view not found: 1146 Table
 'magento2.flag' doesn't exist, query was: 
SELECT flag.* FROM flag WHERE (flag.flag_code='staging')
Enter fullscreen mode Exit fullscreen mode

When you see these during the php bin/magento setup:install command execution, they offer little insight into the real issue. After a few hair-pulling hours of research, you'd discover these errors stem from the premature invocation of the DB resource, triggering the initialization of config and state entities from the Magento DB.

But here's the conundrum: You can't fetch data from the DB at this point because there aren't any tables present yet. They only appear and populate during the installation process, not beforehand. Β―_(ツ)_/Β―

The Classic "Chicken or the Egg" Dilemma

So, we're caught in a circular problem: trying to install the system but getting stopped because it's not installed. Confusing, right? πŸ€ͺ

This happens because someone, somewhere, is prematurely calling the DB connection resource. It is obvious, and I hope you would be as grateful as Sam Eliot is for this revelation insight.

Image description

This "someone" should be inside bin/magento and called before actual command execution. Usually, CLI command constructors are guilty here. Let me explain why.

The Pitfall of CLI Command Constructors

For clarity, take a look at this code:

class DoSmthing
{
    private ?string $dummyConfigValue;

    public function __construct(ScopeConfigInterface $storeManager)
    {
        $this->dummyConfigValue = $storeManager->getValue('some/dummy/path', ScopeInterface::SCOPE_STORE);
    }

    public function getValue(): ?string
    {
        return $this->dummyConfigValue;
    }
}

class DummyCommand extends Command
{
    ...
    public function __construct(DoSmthing $model, string $name = null)
    {
        $this->model = $model;
        parent::__construct($name);
    }
    ...
}
Enter fullscreen mode Exit fullscreen mode

The root of the problem lies in the constructors of both the command and model classes.

In the DoSmthing class, the system attempts to retrieve a config value. This action calls the store, store_website, and core_config_data tables when creating the DummyCommand object.

And guess what happens when this code runs before the tables are ready? Yep, errors appear!

Lets repeat one more time:

  1. We execute php bin/magento setup:install ;
  2. bin/magento application creates all instances of CLI commands;
  3. DummyCommand instance depends on the DoSmthing model. So DoSmthing should be created before DummyCommand object creation;
  4. Application performs creation of DoSmthing model and executes DoSmthing::__construct method;
  5. DoSmthing::__construct method calls to the config manager;
  6. Config manager calls to a chain of tables that are not created yet;
  7. ERROR

Strategies to Prevent These Errors

Remember this golden rule: Every CLI command class initializes every time you invoke php bin/magento. So, here's how to save yourself future headaches:

Rule 1 - Be Wise

It is not the most wise solution to call for potential resource consuming operations like DB/API calls in __construct methods of CLI command classes. Those operations will be constantly executed even once you'd have a wish to get only a list of available commands.

Rule 2 - Be Ready

Steer clear of resources in __construct that might NOT be available during runtime, especially since Magento tables aren't present during setup:install.

Rule 3 - Fix The Problem

If you're not in control of the troublesome code, inject a Proxy using di configs.

We can easily solve our problem from the example with DummyCommand by just adding those lines in di.xml:

    <type name="AbcTest\DummyConsoleCommand\Console\Command\DummyCommand">
        <arguments>
            <argument name="model" xsi:type="object">\AbcTest\DummyConsoleCommand\Model\DoSmthing\Proxy</argument>
        </arguments>
    </type>
Enter fullscreen mode Exit fullscreen mode

Proxy is a well-known pattern, and in Magento, it is widely used to avoid unneeded code execution before we actually request it. In our example - DoSmthing class object will not be instantiated until we do call for any method of this class.

No config retrieving while CLI class object creation - No error.

More details about proxy here: Proxies .

Simplify with Automated Proxy Injection

For those with a small number of extensions on their project, pinpointing the rogue CLI commands isn't too challenging. However, for more extensive, legacy projects or ones brimming with third-party extensions, identifying the culprits becomes an ordeal.

To save time, we've crafted a solution to automate the proxy injection process. Introducing: run-as-root/magento-cli-auto-proxy.

This package injects Proxy into all __construct arguments of CLI commands, eligible for proxying. The result? No errors related to early resource access and, as a side affect, faster execution of php bin/magento and php bin/magento setup:install.

Check out the README for more details. We always appreciate feedback and contributions!

Summary

Let's sum it up:

  • Unthoughtfully created CLI commands can unexpectedly lead to problems, even if they appear harmless at first.
  • The core of the problem often arises from premature database calls, leading to a classic "chicken or the egg" scenario where system installation gets stalled.
  • One significant pitfall identified is the CLI command constructors and their potential to wreak havoc when invoked at the wrong time.
  • To combat these challenges, we laid out three guiding principles:
    1. Be Wise by avoiding resource-intensive calls in constructors.
    2. Be Ready by steering clear of resources that might not be available.
    3. Fix The Problem by utilizing proxies for problematic third-party code.
  • To streamline and automate the solution, we introduced the package run-as-root/magento-cli-auto-proxy which aims to automate proxy injections, offering a more efficient and error-free Magento experience.

Thank you for diving deep into this article. I welcome all your questions and appreciate any feedback you might have.
Happy coding and... Long Live Magento! 😁

Top comments (0)