How to add new dependencies to classes

Share on:

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 😎

Preconditions

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.

First example

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

 1<?php
 2use YourVendor\SomeModule\Model\GeneratedFactory;
 3
 4require realpath(__DIR__) . '/../app/bootstrap.php';
 5$bootstrap = \Magento\Framework\App\Bootstrap::create(BP, $_SERVER);
 6
 7class SomeClass
 8{
 9    private $generatedFactory;
10
11    public function __construct(GeneratedFactory $generatedFactory)
12    {
13        $this->generatedFactory = $generatedFactory;
14    }
15
16    // Some code here...
17}
18
19$someObject = $bootstrap->getObjectManager()->create(SomeClass::class);
20
21// 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.

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.

 1<?php
 2namespace YourVendor\SomeModule;
 3
 4use YourVendor\SomeModule\Model\GeneratedFactory;
 5
 6class SomeClass
 7{
 8    private $generatedFactory;
 9
10    public function __construct(GeneratedFactory $generatedFactory)
11    {
12        $this->generatedFactory = $generatedFactory;
13    }
14
15    // Some code here...
16}

And edit entry point my_api/index.php

 1<?php
 2
 3use YourVendor\SomeModule\SomeClass;
 4
 5require realpath(__DIR__) . '/../app/bootstrap.php';
 6$bootstrap = \Magento\Framework\App\Bootstrap::create(BP, $_SERVER);
 7
 8$someObject = $bootstrap->getObjectManager()->create(SomeClass::class);
 9
10// There is some code that uses $someObject

Second example

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<?php
 2namespace YourVendor\SomeModule;
 3
 4use YourVendor\SomeModule\Model\GeneratedFactory;
 5use Magento\Framework\App\ObjectManager;
 6
 7class SomeClass
 8{
 9    private $generatedFactory;
10    private $someParam;
11
12    public function __construct($someParam)
13    {
14        $this->someParam = $someParam;
15        $this->generatedFactory = ObjectManager::getInstance()->get(GeneratedFactory::class);
16    }
17
18    // Some code here...
19}

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

 1<?php
 2namespace YourVendor\SomeModule;
 3
 4use YourVendor\SomeModule\Model\GeneratedFactory;
 5use Magento\Framework\App\ObjectManager;
 6
 7class SomeClass
 8{
 9    private $generatedFactory;
10    private $someParam;
11
12    public function __construct($someParam, GeneratedFactory $generatedFactory = null)
13    {
14        $this->someParam = $someParam;
15        $this->generatedFactory = $generatedFactory ?: ObjectManager::getInstance()->get(GeneratedFactory::class);
16    }
17
18    // Some code here...
19}

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 😉