Understanding Object Manager in Magento 2

Understanding Object Manager in Magento 2

Why direct usage of Object Manager is discouraged in Magento 2, and why you should use proper dependency injection instead.

When I first started working with Magento 2, one of the most confusing aspects to understand was how to create and manage objects.

Posts on Stack Overflow and other forums often recommended using Magento's Object Manager directly to create objects. This approach seemed a lot simpler and more straightforward, and I followed it in many of my early coding attempts.

But over time, I realized that it was a very bad idea—mainly because it circumvents Magento's dependency injection layer.

There are a few other good reasons why you should avoid using Object Manager directly in your Magento 2 code, so let's dive into why this is the case.

What is the Object Manager?

The Object Manager serves as Magento's dependency injection container. This is the central class responsible for managing the creation of objects in the framework.

Not only does it create objects, but it also manages the dependencies between them. When you request an object from the Object Manager, it will automatically create all other objects that the requested object depends on.

Creating Objects with Object Manager (The Wrong Way)

You could create objects directly with the Object Manager by writing code like this, and placing it anywhere in your Magento codebase:

// Get an instance of Object Manager
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
 
// Create an instance of a Product model
$product = $objectManager->create('Magento\Catalog\Model\Product');

This works! But it also creates some serious problems.

First, it bypasses Magento's dependency injection system, which can lead to runtime issues or incompatibilities, especially in strict environments like Adobe Commerce Marketplace code reviews. While it doesn't directly break the compilation process (executed with bin/magento setup:di:compile), it does prevent Magento from optimizing object creation and managing dependencies efficiently.

Second, it breaks compatibility with Adobe Commerce Marketplace. If you're planning to distribute your extension on this marketplace and use code like this, it's a no-go, because it violates their coding standards.

And also when you use the Object Manager directly in your code like this, you create a tight coupling between your class and the object you're creating. This makes the code both hard to debug and difficult to test, since you can't easily mock the object for testing purposes.

Injecting the Object Manager (Still Not Great)

A somewhat better approach is to inject the Object Manager interface as a dependency in your class constructor, and then use it to create the objects you need:

<?php
 
namespace Vendor\Module\Service;
 
use Magento\Framework\ObjectManagerInterface;
 
class Foo
{
    public function __construct(
        private ObjectManagerInterface $objectManager,
    ) {
    }
 
    public function someCode()
    {
        $product = $this->objectManager->create('Magento\Catalog\Model\Product');
    }
}

While this avoids some of the immediate pitfalls of directly calling ObjectManager::getInstance() (like potential issues in compiled environments), it's still a bad idea.

Your code remains tightly coupled to the Object Manager, which makes it difficult to test and maintain. It also obscures the actual dependencies of your class, since you're creating objects dynamically within your code. This makes it harder for anyone reading your code to understand the exact objects you're working with.

It also creates an implementation dependency rather than an interface dependency. This means your class depends on the Object Manager's concrete implementation, rather than the specific interface of the dependency you need.

There are specific reasons to use SOLID principles in object-oriented design, and this approach violates the "D" principle, which stands for Dependency Inversion Principle.

By injecting the Object Manager rather than the class you'd like to use, you're not delegating the responsibility of object creation to Magento's dependency injection system, which is the reason it exists.

Proper Dependency Injection (The Right Way)

The correct way to create objects in Magento 2 is to use proper dependency injection.

This involves injecting the objects you need directly into your class constructor, rather than creating them dynamically:

<?php
 
namespace Vendor\Module\Service;
 
use Magento\Catalog\Model\Product;
 
class Foo
{
    public function __construct(
        private Product $product,
    ) {
    }
 
    public function someCode()
    {
        // Use $this->product directly
    }
}

In this example, we're injecting the Magento\Catalog\Model\Product object directly into the class constructor. This makes the dependency explicit and allows Magento to manage the object creation process.

When you use dependency injection to properly create objects like this, Magento's Object Manager will automatically create the Product object and inject it into your class when it's instantiated. This allows Magento to optimize the creation of objects and manage their dependencies more efficiently.

Dependencies of Dependencies

One of the most powerful aspects of Magento's dependency injection system is that it can manage dependencies of dependencies. This means that if you inject a class that has its own dependencies, Magento will automatically create those dependencies as well.

The Product class we're injecting in the example above actually has its own dependencies (and a ridiculous number of them, at that). Let's look at its constructor:

public function __construct(
    \Magento\Framework\Model\Context $context,
    \Magento\Framework\Registry $registry,
    \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory,
    AttributeValueFactory $customAttributeFactory,
    \Magento\Store\Model\StoreManagerInterface $storeManager,
    \Magento\Catalog\Api\ProductAttributeRepositoryInterface $metadataService,
    Product\Url $url,
    Product\Link $productLink,
    \Magento\Catalog\Model\Product\Configuration\Item\OptionFactory $itemOptionFactory,
    \Magento\CatalogInventory\Api\Data\StockItemInterfaceFactory $stockItemFactory,
    \Magento\Catalog\Model\Product\OptionFactory $catalogProductOptionFactory,
    \Magento\Catalog\Model\Product\Visibility $catalogProductVisibility,
    Status $catalogProductStatus,
    \Magento\Catalog\Model\Product\Media\Config $catalogProductMediaConfig,
    Product\Type $catalogProductType,
    \Magento\Framework\Module\Manager $moduleManager,
    \Magento\Catalog\Helper\Product $catalogProduct,
    \Magento\Catalog\Model\ResourceModel\Product $resource,
    \Magento\Catalog\Model\ResourceModel\Product\Collection $resourceCollection,
    \Magento\Framework\Data\CollectionFactory $collectionFactory,
    \Magento\Framework\Filesystem $filesystem,
    \Magento\Framework\Indexer\IndexerRegistry $indexerRegistry,
    \Magento\Catalog\Model\Indexer\Product\Flat\Processor $productFlatIndexerProcessor,
    \Magento\Catalog\Model\Indexer\Product\Price\Processor $productPriceIndexerProcessor,
    \Magento\Catalog\Model\Indexer\Product\Eav\Processor $productEavIndexerProcessor,
    CategoryRepositoryInterface $categoryRepository,
    Product\Image\CacheFactory $imageCacheFactory,
    \Magento\Catalog\Model\ProductLink\CollectionProvider $entityCollectionProvider,
    \Magento\Catalog\Model\Product\LinkTypeProvider $linkTypeProvider,
    \Magento\Catalog\Api\Data\ProductLinkInterfaceFactory $productLinkFactory,
    \Magento\Catalog\Api\Data\ProductLinkExtensionFactory $productLinkExtensionFactory,
    EntryConverterPool $mediaGalleryEntryConverterPool,
    \Magento\Framework\Api\DataObjectHelper $dataObjectHelper,
    \Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface $joinProcessor,
    array $data = [],
    \Magento\Eav\Model\Config $config = null,
    FilterProductCustomAttribute $filterCustomAttribute = null
) {
    // ...
}

Holy moly—that's a lot of dependencies! But since all of these dependencies are injected into the Product class, we don't need to worry about how they are created or managed. When we inject the Product class into our own class, Magento will automatically create all of these dependencies for us.

Magento's dependency injection container

Why Proper Dependency Injection Matters

Hopefully, I've already demonstrated enough reasons as to why we should use proper dependency injection in Magento 2. But here are a few more reasons why you should avoid using Object Manager directly:

  1. Your code becomes more testable - You can easily mock dependencies for unit tests. For example, using PHPUnit, you can mock the Product class and pass it into your class during testing, ensuring you're testing your logic in isolation. While most developers don't write tests for their Magento code, it's still a good practice to follow because it makes your code easier to reason about.

  2. Magento can optimize object creation - The system can determine the most efficient way to create and manage objects. For instance, Magento's DI system uses shared instances by default and generates factories and proxies to improve performance, whereas direct Object Manager usage might unnecessarily create new instances.

  3. Your code remains compatible with all Magento environments. This includes production mode (often used with compilation for performance), deployments on platforms like Adobe Commerce Cloud, and code that adheres to Adobe Commerce Marketplace standards.

  4. Your dependencies are explicit - Anyone reading your code can immediately see what other components your class depends on. This makes your code easier to understand and maintain, and also makes it easier for other developers to work with your code.

  5. You follow Magento's coding standards - Making your extensions far more likely to pass a code review. This is especially important if you're planning to distribute your extension on the Adobe Commerce Marketplace, but it's also critical for ensuring your code works with future versions of Magento.

When Direct Object Manager Usage is Acceptable

I always like to look at the inverse of a "rule" to understand when it's acceptable to break it. And indeed, there are a few cases where using the Object Manager directly is acceptable, while still being in line with Magento's best practices:

  1. In factories - Where the class to be instantiated is determined at runtime. For example, a factory might use the Object Manager to create a specific product type based on a configuration value.

  2. In proxies - Where lazy loading is implemented. Proxy classes are used to defer the creation of an object until it's actually needed, and they often involve Object Manager usage internally.

  3. In bootstrap code or one-off scripts - Before the dependency injection system is fully initialized, or in rare cases like custom command-line scripts, where DI isn't practical. However, even in these cases, you should have a strong justification for why you are even writing one-off CLI scripts instead of creating them as standard executable CLI commands.

For almost everything else, proper dependency injection is the way to go.

Recap

While it might seem easier to grab the Object Manager and create objects directly, this approach creates an enormous amount of technical debt and introduces compatibility issues, both with the codebase and with future Magento updates. By using proper dependency injection, you create more maintainable, testable, and compatible Magento code.

Remember that the Object Manager's sole purpose is to manage the creation of objects in the framework for you. Why not just let it do the work?

Not only will it save you effort, but it will also make the quality of your codebase far more solid.

  1. Learn more about dependency injection with object manager (Free article)
  2. Grow your Magento expertise with all courses & lessons (700+ students)
  3. Get weekly Magento deep-dives and learn something new every week (9,000+ subscribers)