Design Patterns in Magento 2 – Object manager

Magento / Adobe Commerce is a well-known e-commerce platform with the performance record of more than 10 years. From the first release of Magento 1 to the latest revision of Magento 2, its main advantage is the number of business processes it automates out of the box: catalog, sales and marketing, along with other numerous features. To ensure flexible communication between components and to provide the ability for efficient customization, Magento 2 has developed a complex architecture which is based on a combination of design patterns.

We are happy to introduce a series of articles dedicated to design patterns of Magento 2 architecture. Explanation of their roles in the architecture of the e-commerce platform will help understand not only Magento 2 website design, but shall also make a beneficial impact on developing new business components.

While describing the specific pattern, we will use the following structure: its basic explanation, role in the Magento / Adobe Commerce, the most efficient pattern’s applicability and limitations of usage. Such structure should help create a clear understanding of the architecture.

Who is this article for?

The material we present in these articles is for developers of all levels of expertise, solution architects and those who want to learn more details about the Magento / Adobe Commerce design.

Object manager

General overview

First, we decided to describe Object manager because it plays a leading role in the majority of the Magento 2 / Adobe Commerce aspects. According to the official documentation, Object manager is responsible for:

  • Object creation in factories and proxies.
  • Implementing the singleton pattern by returning the same shared instance of a class when requested.
  • Dependency management by instantiating the preferred class when a constructor requests its interface.
  • Automatically instantiating parameters in class constructors.

Let’s review this list in more detail by checking the ObjectMagerInterface which is a part of the Magento framework. It is the best way to describe its approach because method implementation is self-explanatory.

Method create

The first method we find in the interface is create.

    /**
     * Create new object instance
     *
     * @param string $type
     * @param array $arguments
     * @return mixed
     */
    public function create($type, array $arguments = []);

This method is one of the commonly used ones. With its help, Object manager creates new objects based on two parameters: type and arguments. The first one is a string type value that contains the name of the object type. For example, Magento\Framework\Cache\Frontend\Adapter\Zend. And the second parameter with type array contains the list of arguments required for the new instance of an object.

But what mechanism is being used for a new object initialization? We think that the patient reader already has an answer to that question. Under the hood, the Object manager uses a factory method pattern to generate a new instance by its type.

    /**
     * Create new object instance
     *
     * @param string $type
     * @param array $arguments
     * @return mixed
     */
    public function create($type, array $arguments = [])
    {
        return $this->_factory->create($this->_config->getPreference($type),   $arguments);
    }

We will review the factory method in the next article, but for now let’s limit its description to several sentences. While object manager’s implementation, if a $type parameter was sent, factory uses it for identifying a preference for the interface. In other cases, the class will be generated directly. This can be seen in the Config class getPreference method. It is a very important part because it describes the power of the Dependency injection approach used in the Magento 2 / Adobe Commerce. We will review this in more details in the upcoming articles.

Method get

Another method represented in the Object manager interface is the get method. This one has the same high value for the platform as the previously described one. According to its declaration, the get method retrieve the cached object instance.

    /**
     * Retrieve cached object instance
     *
     * @param string $type
     * @return mixed
     */
    public function get($type);

Through this method, the Object manager provides an important part of the Object lifestyle configuration. According to it, some types of objects can either be shared or not. In other words, be Singleton or Transient. In the di.xml the shared parameter is responsible for this configuration:

<argument xsi:type="object" shared="{shared}">{typeName}</argument>

Implementation of this method is simple for understanding:

    /**
     * Retrieve cached object instance
     *
     * @param string $type
     * @return mixed
     */
    public function get($type)
    {
        $type = ltrim($type, '\\');
        $type = $this->_config->getPreference($type);
        if (!isset($this->_sharedInstances[$type])) {
            $this->_sharedInstances[$type] = $this->_factory->create($type);
        }
        return $this->_sharedInstances[$type];
    }

With $type the ObjectManger class identifies a preference for the interface if it is declared, and returns a cached instance of the object if it is present in the pull of previously generated (shared) objects.

Method config

The last one in the list but the first by platform calls. Its declaration is simple:

    /**
     * Configure object manager
     *
     * @param array $configuration
     * @return void
     */
    public function configure(array $configuration);

along with the implementation

    /**
     * Configure di instance
     * Note: All arguments should be pre-processed (sort order, translations, etc) before passing to method configure.
     *
     * @param array $configuration
     * @return void
     */
    public function configure(array $configuration)
    {
        $this->_config->extend($configuration);
    }

This method is an entry point for the whole application initialisation during HTTP calls and area initialisation. At this step the Object manager works on “collecting” all preferences described in the di.xml in one place – global configuration.

Object manager usage

Direct call

It may sound strange to talk about usage and start an explanation with limitations but it has a total sense. Object manager direct usage in the application is dangerous because it breaks the idea of atomate object generation based on the type and object parameters declaration. Remember that the create method in the Object manager implementation uses the factory method which by its origin requires only object name  (type).

Factory and proxies

Factory and Proxies are some kinds of exceptions for the direct call of Object manager because they need Object manager to generate new objects. As an example, you can overview any kind of DTO factory.

Backwards compatibility

Sometimes it is required to add a new parameter to the object constructor and to follow backwards compatibility rules a newly added object has to be declared with the help of the Object manager. For example,

Magento\Catalog\Model\Indexer\Category\Product\AbstractAction
    /**
     * @param ResourceConnection $resource
     * @param \Magento\Store\Model\StoreManagerInterface $storeManager
     * @param \Magento\Catalog\Model\Config $config
     * @param QueryGenerator $queryGenerator
     * @param MetadataPool|null $metadataPool
     * @param TableMaintainer|null $tableMaintainer
     */
    public function __construct(
        \Magento\Framework\App\ResourceConnection $resource,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        \Magento\Catalog\Model\Config $config,
        QueryGenerator $queryGenerator = null,
        MetadataPool $metadataPool = null,
        TableMaintainer $tableMaintainer = null
    ) {
        $this->resource = $resource;
        $this->connection = $resource->getConnection();
        $this->storeManager = $storeManager;
        $this->config = $config;
        $this->queryGenerator = $queryGenerator ?: ObjectManager::getInstance()->get(QueryGenerator::class);
        $this->metadataPool = $metadataPool ?: ObjectManager::getInstance()->get(MetadataPool::class);
        $this->tableMaintainer = $tableMaintainer ?: ObjectManager::getInstance()->get(TableMaintainer::class);
    }

Tests

In integration tests the Object manager can be used for a new object generation for fixtures. As a simple and direct explanation of this point we can say that the direct call of the Object manager replaces the new PHP method. For example, Magento\AdobeStockImage\Test\Integration\Model\SaveImageTest

    /**
     * Test with image.
     *
     * @param array $documentData
     * @param string $sourceFile
     * @param string $destinationPath
     * @return void
     * @dataProvider getSaveTestDataProvider
     */
    public function testSave(array $documentData, string $sourceFile, string $destinationPath): void
    {
        $this->deleteImage($destinationPath);
        $document = $this->getDocument($documentData);
        $saveImage = Bootstrap::getObjectManager()->create(SaveImageInterface::class);
        $saveImage->execute(
            $document,
            $this->getImageFilePath($sourceFile),
            $destinationPath
        );
        $this->assertImageSavedToDirectory($destinationPath);
        $this->assertAssets($destinationPath, $documentData);
        $this->deleteImage($destinationPath);
    }

Magic methods

The Object manager like in the tests step described above can be called directly in the PHP magic methods to initiate a new instance or get already existed. As an example, let’s check the __wakeup() magic method at Magento\Framework\DB\Select

    /**
     * Init not serializable fields
     *
     * @return void
     * @since 100.0.11
     */
    public function __wakeup()
    {
        $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
        $this->_adapter = $objectManager->get(ResourceConnection::class)->getConnection();
        $this->selectRenderer = $objectManager->get(\Magento\Framework\DB\Select\SelectRenderer::class);
    }

As a summary we can say that the Object manager approach is an object lifecycle dispatched in Magento 2 platform. It uses for objects initialisation, shared object managing. It has specific rules for usage and following them guarantees project stability and maintainability.