So, let’s start 😎

Introduction

On the production environment, Magento should work on a read-only file system, it is a security recommendation.

Only the following folders can be writable:

  • app/etc
  • pub/static
  • pub/media
  • var

An important note: the folder with the generated classes was moved from var/generation to generated/code. Currently, this folder will be read-only on the production environment. For example, folders have the similar permissions on the Magento Cloud.

So, if you add a dependency incorrectly, then Magento can break.

A class at an entry point

A developer wrote a class at an entry point, and added into the constructor of this class a dependency on a generated factory.

<?php
use YourVendor\SomeModule\Model\GeneratedFactory;

require realpath(__DIR__) . '/../app/bootstrap.php';
$bootstrap = \Magento\Framework\App\Bootstrap::create(BP, $_SERVER);

class SomeClass
{
    private $generatedFactory;

    public function __construct(GeneratedFactory $generatedFactory)
    {
        $this->generatedFactory = $generatedFactory;
    }

    // Some code here...
}

$someObject = $bootstrap->getObjectManager()->create(SomeClass::class);

// There is some code that uses $someObject

This example will work in the developer mode correctly, GeneratedFactory will be generated on the fly. But in the production mode, on read-only file system, you will face an error that says that GeneratedFactory cannot be saved in generateted/code folder.

It happens because Magento does not scan entry points during running of bin/magento setup:di:compile command. And if entry points contain definitions of generated classes, they will be ignored.

So, there are two ways how to fix this.

How to fix it?

The first way is to create a real factory presented in the file system.

The second way is to move the class SomeClass into the folder app/code/YourVendor/SomeModule Then Magento code sniffer will find in the class SomeClass the dependency on the generated class GeneratedFactory and code generator will generate this generated factory.

<?php
namespace YourVendor\SomeModule;

use YourVendor\SomeModule\Model\GeneratedFactory;

class SomeClass
{
    private $generatedFactory;

    public function __construct(GeneratedFactory $generatedFactory)
    {
        $this->generatedFactory = $generatedFactory;
    }

    // Some code here...
}

And edit entry point my_api/index.php

<?php

use YourVendor\SomeModule\SomeClass;

require realpath(__DIR__) . '/../app/bootstrap.php';
$bootstrap = \Magento\Framework\App\Bootstrap::create(BP, $_SERVER);

$someObject = $bootstrap->getObjectManager()->create(SomeClass::class);

// There is some code that uses $someObject

Wrong dependency injection

Incorrect adding of a dependency on a generated class to an existing class. For example, the following code will work correctly in developer mode but will fail in production mode on a read-only file system.

<?php
namespace YourVendor\SomeModule;

use YourVendor\SomeModule\Model\GeneratedFactory;
use Magento\Framework\App\ObjectManager;

class SomeClass
{
    private $generatedFactory;
    private $someParam;

    public function __construct($someParam)
    {
        $this->someParam = $someParam;
        $this->generatedFactory = ObjectManager::getInstance()->get(GeneratedFactory::class);
    }

    // Some code here...
}

How to fix it?

A similar approach sometimes is used to add dependencies and save a backward compatibility. But this approach has the same problem like the example above. During running bin/magento setup:di:compile command, a generated class will not be generated.

There are two ways how to fix this too.

The first way is to create a real factory presented in the file system.

The second way is to slightly change the constructor of SomeClass

<?php
namespace YourVendor\SomeModule;

use YourVendor\SomeModule\Model\GeneratedFactory;
use Magento\Framework\App\ObjectManager;

class SomeClass
{
    private $generatedFactory;
    private $someParam;

    public function __construct($someParam, GeneratedFactory $generatedFactory = null)
    {
        $this->someParam = $someParam;
        $this->generatedFactory = $generatedFactory ?: ObjectManager::getInstance()->get(GeneratedFactory::class);
    }

    // Some code here...
}

In this way:

  • we added a new dependency
  • we saved backward compatibility
  • and a generated class will be generated
  • Magento works correctly in production mode on read-only file system

If you have any questions or comments, feel free to write them 😉