Magento 2

Magento fundamentals: why we shouldn't (almost never) use the Object Manager

4 minutes reading

This article is the first of a series inspired by some basic knowledge required for developing on Magento 2.

After more than five years from the release of Magento 2.0 and plenty of available documentation, these may seem obvious topics. Still, it’s not so: every day we come across third-party code that demonstrates the opposite.

What is the Object Manager

The Object Manager is an object that is used by the framework to instantiate objects and pass them as parameters to the constructor of other classes.

A synonym of the verb pass is inject; that’s why objects passed by the Object Manager are usually called injectable objects.

The objects passed to the constructor of a class represent its dependencies; indeed, we speak of dependency injection, abbreviated as DI.
To learn more about it: https://en.wikipedia.org/wiki/Dependency_injection

Let’s see an example.

In order for TheMostUselessClass to access to system configuration, we will declare a dependency from ScopeConfigInterface in its constructor, like shown below:

<?php declare(strict_types=1);
use \Magento\Framework\App\Config\ScopeConfigInterface;

class TheMostUselessClass 
{
    protected $config;

    public function __construct(ScopeConfigInterface $config) 
    {
        $this->config = $config
    }
}

Let’s focus on the fact that ScopeConfigInterface is an interface and we know that an interface can’t be instantiated.

Thus, the Object Manager should instantiate a class that implements ScopeConfigInterface; to identify the proper class, it will lookup in a map obtained by di.xml files composition. Going more in-depth on how the map is built is out of the scope of this article.

Depending on interfaces (or abstractions) rather than on concrete classes is called dependency inversion principle and is used to write less coupled code.
To learn more about it: https://en.wikipedia.org/wiki/Dependency_inversion_principle

Not all the objects are injectable

Injectable objects that the Object Manager passes should always be service objects that we use to execute some domain logic. Usually, we don’t change the state of these objects (with a few exceptions), which are not intended to be persisted.

An example that we’ve already seen is the object implementing the ScopeConfigInterface, used to retrieve system configuration values.

Another widely used example of a service object is a repository, whose purpose is managing the persistence of entities on a database.

Since service objects are stateless, they are typically provided as singletons by the Object Manager. That means the same instance of an object is passed to those requiring it as a dependency.

Conversely, objects that need to be instantiated as new each time we need them are called newable objects. An instance representing an entity record stored in the database is a typical example of a newable object.

We don’t need to directly use the Object Manager to get such objects, because they don’t represent a dependency. To get a newable object, we instead use particular service objects called factories.

Being service objects, factories are instantiated by the Object Manager through the declaration of a constructor-based dependency, like shown below:

<?php declare(strict_types=1);
use \Magento\Catalog\Api\Data\ProductInterfaceFactory;

class CreateFabulousProduct
{
    protected $productFactory;

    public function __construct(
        ProductInterfaceFactory $productFactory
    ) {
        $this->productFactory = $productFactory; // injectable object
    }

    public function execute(/* ... */): ProductInterface
    {
        $product = $this->productFactory->create(); // newable object
        // ...initialize the properties of a fabulous product
        return $product;
    }
}

Factory classes are peculiar: it’s not necessary to implement them because they are auto-generated by the framework (automatically if we work in developer mode, through the bin/magento setup:di:compile command if we work in production mode).

When using the Object Manager is allowed

Finally, let’s see which are the exceptional circumstances under which we are allowed to use the Object Manager:

  • if we need to implement our own factory class (uncommon, but possible), we need to use the Object Manager, declaring a dependency from \Magento\Framework\ObjectManagerInterface;
  • if we write automated tests, we may need the Object Manager; in this case we usually get its instance through the \Magento\TestFramework\Helper\Bootstrap::getObjectManager() method;
  • if we contribute to the Magento core code, we may need to add one or more parameters to an existing class constructor. To prevent backward incompatibility problems, we declare each additional parameter as optional and, if the value of the new parameter is null, we fetch the dependency using the Magento\Framework\App\ObjectManager::getInstance() method;
  • finally, if we need to quickly fetch a dependency in a temporary piece of code, we can use the Object Manager like in this reusable snippet.

Conclusion

We’ve seen that directly using the Object Manager is rarely required because:

  • if we need an injectable object, we can fetch it declaring a dependency in the constructor of the class that needs it;
  • if we need a newable object, we can obtain it through its related factory object, which is fetched as an injectable object.

Since the circumstances in which we need to use the Object Manager are very few: we can now analyze our legacy code and apply the principles seen here to clean it up!

Resources

Post of

COO | Reggio Emilia

Alessandro works at Bitbull as an experienced technical leader devoted to software design, development, and mentoring.
Honored three times with the title of Magento Master and listed among the top 50 contributors in the last years, he is also an active Magento Community Maintainer since 2018 and member of the Magento Association content committee since 2020.