How to add new dependencies to classes

Sometimes we need to add new dependencies to classes, and we need to do this backward-compatible. In this article, I am going to describe how to do it. Iโ€™m also going to tell about the common mistakes.

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<?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.

The example of SomeClass:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?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

The example of SomeClass:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?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 ๐Ÿ˜‰

You can find the example of wrong DI in the Magento module in this repository: https://github.com/BaDos/example-wrong-di

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy