Magento fundamentals: why we shouldn't (almost never) use the Object Manager
4 minutes readingThis 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
- Magento 2 Developer Documentation - Dependency injection
- Magento 2 Developer Documentation - ObjectManager
- Magento Stack Exchange - How can I inject data into a class using di.xml?
- Magento 2 Contribution Guide - Adding a constructor parameter
Post of
Alessandro Ronchi☝ Ti piace quello che facciamo? Unisciti a noi!