'Area code not set' issue in custom CLI commands in Magento 2

The area is not set in Magento CLI (it is not required for any core commands). It can be set at the beginning of your command's execute method:

/** @var \Magento\Framework\App\State **/
private $state;

public function __construct(\Magento\Framework\App\State $state) {
    $this->state = $state;
    parent::__construct();
}

public function execute() {
    $this->state->setAreaCode(\Magento\Framework\App\Area::AREA_FRONTEND); // or \Magento\Framework\App\Area::AREA_ADMINHTML, depending on your needs
}

I've stumbled into this problem again today and it's important to know that this problem is thrown whenever a dependency down the chain initiates an instance that needs to know the state of the application.

In many cases this error is session-bound (since the session needs to know the state of the application (frontend or adminhtml)).

In my case I needed to have Magento\Tax\Api\TaxCalculationInterface in a CLI command, but this requires at some point in it's dependency chain the customer session (probably to get the customer group).

Edit: I found a better solution using proxies. But for histories' sake, here's my previous answer:


To solve this I did not include this interface in my constructor, but rather it's factory:

/**
 * @var \Magento\Tax\Api\TaxCalculationInterfaceFactory
 */
protected $taxCalculationFactory;

/**
 * @param \Magento\Tax\Api\TaxCalculationInterfaceFactory $taxCalculationFactory
 */
public function __construct(
    \Magento\Tax\Api\TaxCalculationInterfaceFactory $taxCalculationFactory
) {
    $this->taxCalculationFactory = $taxCalculationFactory;
}

This way, the class is only instantiated in the one method where I needed it, and no longer in the constructor:

$taxCalculation = $this->taxCalculationFactory->create();

This solved the problem for me in this particular case.


And now the answer using a proxy:

If you don't want to trigger all the dependencies down the chain, you should use a proxy in your constructor. According to the original documentation:

... constructor injection also means that a chain reaction of object instantiation is often the result when you create an object.

and:

... Proxies extend other classes to become lazy-loaded versions of them. That is, a real instance of the class a proxy extends created only after one of the class’s methods is actually called.

So in my situation, with the TaxCalculationInterface, all I had to do was instantiate my tax calculation as a proxy in my constructor:

/**
 * @var \Magento\Tax\Api\TaxCalculationInterface\Proxy
 */
protected $taxCalculation;

/**
 * @param \Magento\Tax\Api\TaxCalculationInterface\Proxy $taxCalculation
 */
public function __construct(
    \Magento\Tax\Api\TaxCalculationInterface\Proxy $taxCalculation
) {
    $this->taxCalculation = $taxCalculation;
}

This way, my class is lazy loaded. That is: it's only instantiated as soon as I call one of it's methods. For example:

$rate = $this->taxCalculation->getCalculatedRate($productRateId);

You shouldn't use setAreaCode in the __construct for CLI commands. When you run any command Magento collect and create instance for each script registered in your application. If there are more than one __construct with area code definition you will have the error.

I suppose better to use the execute() method to set area code. Check the catalog module: vendor/magento/module-catalog/Console/Command/ImagesResizeCommand.php