Adobe Commerce CLI commands - or how to create a problem out of nothing
Tags: magento2, adobecommerce, webdev, development
Published: 2023-08-15 19:45:38

### Table Of Contents
- [Intro](#intro)
- [The Underlying Problem & Its Roots](#The-Underlying-Problem--Its-Roots)
- [The Classic "Chicken or the Egg" Dilemma](#the-classic-chicken-or-the-egg-dilemma)
- [The Pitfall of CLI Command Constructors](#the-pitfall-of-cli-command-constructors)
- [Strategies to Prevent These Errors](#strategies-to-prevent-these-errors)
- [Simplify with Automated Proxy Injection](#simplify-with-automated-proxy-injection)
- [Summary](#summary)


## 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.](https://github.com/alankent/magento2devbox-skeleton/issues/4). 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:
```error
The default website isn't defined. Set the website and try again.
```

For Commerce editions, there's also:
```error
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')
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t77vk8glx0375v3ge19x.png)

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:

```php
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);
    }
    ...
}
```

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`:

```xml
    <type name="AbcTest\DummyConsoleCommand\Console\Command\DummyCommand">
        <arguments>
            <argument name="model" xsi:type="object">\AbcTest\DummyConsoleCommand\Model\DoSmthing\Proxy</argument>
        </arguments>
    </type>
```

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](https://developer.adobe.com/commerce/php/development/components/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](https://github.com/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](https://github.com/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**! 😁