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)

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.

<?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.

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

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.

<?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...
}

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

Published by

BaDos

Blog Comments powered by Disqus.