Dependency injection with Magento's Object Manager

Dependency injection with Magento's Object Manager

Learn how Magento's Object Manager works with dependency injection to create & manage objects in the Magento framework.

If you don't know what dependency injection, or DI, is, here is a short explanation.

Dependency injection is a way to make objects in a more organized way. Instead of using the new keyword to create objects, you define objects you'd like to create by passing their related classes as arguments to the class constructor. In PHP, this is the __construct() function.

When code in this class is executed, the classes we pass to the constructor are then sent to a single class that is in charge of making objects from these classes. This referred to as the "dependency injection container" class.

Object Manager is the name of the Magento class that handles how these dependencies are added. Even though we should never call the Object Manager directly in our code, this class is in charge of making and managing all of Magento's dependencies. When we give it a class, it does its magic, then gives us back an object that is an instance of that class. Then, this object is assigned back to the class property that’s mapped to it.

Magento 2 Snippet for Dependency Injection and Magento Object Manager

Benefits of DI

There are many benefits of having a single place to create all of these objects in.

First, it separates our program, so we aren’t creating objects all around the place in different parts of our code. Since all objects are created in the same place, this class also takes care of any other dependencies of that class for us. This not only makes it easier to test our code, but it also makes it less complicated.

Second, since a single class now controls how objects are made, each new object can have extra functionality added to it by the class that is creating them. This can provide these objects with some extra functionality, that would be next to impossible to do by just using the new keyword.

Finally, dependencies can be replaced with instances of other objects at the time these objects are created. This is where Magento's crazy-powerful ability to extend shines, but is also where most of its complexity comes from. But being able to swap out these classes gives us an amazing amount of control & power.

Don’t use the "new" keyword to create objects

It’s important to know why you shouldn’t use the new keyword to create objects in Magento.

Right now, our controller's execute() method just returns a call to the die() function, but that's not what we want to do. Instead, we want to send back a normal Magento page response.

It's very easy to create an object with PHP. Just use the new keyword to name a class. Here, we are using the new keyword to create a Session object from Magento’s Customer model class.

Let's use var_dump to show this on the screen. We’ll also add an echo '<pre>' to make the output easier to read, and call the die() function afterwards to stop the rest of the code from running:

app/code/Macademy/articles/Controller/Post/Detail.php
<?php
 
declare(strict_types=1);
 
namespace Macademy\Blog\Controller\Post;
 
use Magento\Customer\Model\Session;
use Magento\Framework\App\Action\HttpGetActionInterface;
 
class Detail implements HttpGetActionInterface
{
    public function execute()
    {
        echo '<pre>';
        var_dump(new Session());
        die();
    }
}

If we try to refresh this page though, we’ll see an ArgumentCountError error that says:

ArgumentCountError: Too few arguments to function Magento\Customer\Model\Session::__construct(), 0 passed in /var/www/html/app/code/Macademy/articles/Controller/Post/Detail.php on line 13 and at least 21 expected in /var/www/html/vendor/magento/module-customer/Model/Session.php:138

For this Session class to be created, we need to pass in all of its related arguments.

Let's take a look at the class of the object we are trying to create, and we'll see that this class constructor has a lot going on:

vendor/magento/module-customer/Model/Session.php
...
/**
 * Session constructor.
 *
 * @param \Magento\Framework\App\Request\Http $request
 * @param \Magento\Framework\Session\SidResolverInterface $sidResolver
 * @param \Magento\Framework\Session\Config\ConfigInterface $sessionConfig
 * @param \Magento\Framework\Session\SaveHandlerInterface $saveHandler
 * @param \Magento\Framework\Session\ValidatorInterface $validator
 * @param \Magento\Framework\Session\StorageInterface $storage
 * @param \Magento\Framework\Stdlib\CookieManagerInterface $cookieManager
 * @param \Magento\Framework\Stdlib\Cookie\CookieMetadataFactory $cookieMetadataFactory
 * @param \Magento\Framework\App\State $appState
 * @param Share $configShare
 * @param \Magento\Framework\Url\Helper\Data $coreUrl
 * @param Url $customerUrl
 * @param ResourceCustomer $customerResource
 * @param CustomerFactory $customerFactory
 * @param \Magento\Framework\UrlFactory $urlFactory
 * @param Generic $session
 * @param \Magento\Framework\Event\ManagerInterface $eventManager
 * @param \Magento\Framework\App\Http\Context $httpContext
 * @param CustomerRepositoryInterface $customerRepository
 * @param GroupManagementInterface $groupManagement
 * @param \Magento\Framework\App\Response\Http $response
 * @param AccountConfirmation $accountConfirmation
 * @throws \Magento\Framework\Exception\SessionException
 * @SuppressWarnings(PHPMD.ExcessiveParameterList)
 */
public function __construct(
    \Magento\Framework\App\Request\Http $request,
    \Magento\Framework\Session\SidResolverInterface $sidResolver,
    \Magento\Framework\Session\Config\ConfigInterface $sessionConfig,
    \Magento\Framework\Session\SaveHandlerInterface $saveHandler,
    \Magento\Framework\Session\ValidatorInterface $validator,
    \Magento\Framework\Session\StorageInterface $storage,
    \Magento\Framework\Stdlib\CookieManagerInterface $cookieManager,
    \Magento\Framework\Stdlib\Cookie\CookieMetadataFactory $cookieMetadataFactory,
    \Magento\Framework\App\State $appState,
    Config\Share $configShare,
    \Magento\Framework\Url\Helper\Data $coreUrl,
    \Magento\Customer\Model\Url $customerUrl,
    ResourceCustomer $customerResource,
    CustomerFactory $customerFactory,
    \Magento\Framework\UrlFactory $urlFactory,
    Generic $session,
    \Magento\Framework\Event\ManagerInterface $eventManager,
    \Magento\Framework\App\Http\Context $httpContext,
    CustomerRepositoryInterface $customerRepository,
    GroupManagementInterface $groupManagement,
    \Magento\Framework\App\Response\Http $response,
    AccountConfirmation $accountConfirmation = null
) {
    $this->_coreUrl = $coreUrl;
    $this->_customerUrl = $customerUrl;
    $this->_configShare = $configShare;
    $this->_customerResource = $customerResource;
    $this->_customerFactory = $customerFactory;
    $this->_urlFactory = $urlFactory;
    $this->_session = $session;
    $this->customerRepository = $customerRepository;
    $this->_eventManager = $eventManager;
    $this->_httpContext = $httpContext;
    $this->groupManagement = $groupManagement;
    $this->response = $response;
    $this->accountConfirmation = $accountConfirmation ?: ObjectManager::getInstance()
        ->get(AccountConfirmation::class);
    parent::__construct(
        $request,
        $sidResolver,
        $sessionConfig,
        $saveHandler,
        $validator,
        $storage,
        $cookieManager,
        $cookieMetadataFactory,
        $appState
    );
    $this->_eventManager->dispatch('customer_session_init', ['customer_session' => $this]);
}
...

Classes are given to constructors as arguments, after which they are assigned to class properties. In Magento, we can't use the new keyword to make an instance of this class, because it would be abnormally complex to always need to inject all of these other dependencies every time we want to create a single object.

**Create an object with dependency injection**

Instead of using Object Manager, a much better practice is to use dependency injection to create instances of objects.

This is easier than you might think, especially now that PHP 8 has promoted properties. The first thing we need to do is make a constructor.

To do this in PHP, we'll make a public function called __construct():

public function __construct() {}

Then, we'll create a new private property that will be passed to the constructor as an argument. This will be a Session class instance, and the property we will assign it to will be called $session.

When this code is executed, this class will be passed to the Object Manager class in the background, which will then create an instance of the Session, passing the result back to the $session property.

public function __construct(
    private Session $session
) {}

Specifically for this session object, it's possible that an instance of it was already created when the Magento app was first set up. There can only be one instance of the session at any given time, and the design pattern behind this concept uses what is called a “Singleton”.

In this case, since the session object was previously created, Object Manager will pass back an instance of the object that it already has. We don't have to do anything else! That's kind of cool.

Using dependency injection also means that we no longer need to import of the ObjectManager class at the top of our files. Here’s the full implementation using DI:

app/code/Macademy/articles/Controller/Post/Detail.php
<?php
 
declare(strict_types=1);
 
namespace Macademy\Blog\Controller\Post;
 
use Magento\Customer\Model\Session;
use Magento\Framework\App\Action\HttpGetActionInterface;
 
class Detail implements HttpGetActionInterface
{
    public function __construct(
        private Session $session,
    ) {}
 
    public function execute()
    {
        echo '<pre>';
        var_dump($this->session->getData());
        die();
    }
}

This way of using dependency injection in PHP 8 is not only shorter than creating objects with Object Manager, but has less dependent classes. It no longer needs a direct reference to Object Manager, or any other classes that would otherwise be needed to create these objects with the new keyword.

Injecting dependences in PHP 7

If you are still using PHP 7, you will need to use a different method to create and inject dependencies.

First, you'll add a new private class property above the constructor. The name of the property will be $session and the property's scope will be private.

Then you will need to pass in the Session object as an argument to the constructor, relating it back to a new $session variable.

Finally in the constructor body, you will need to assign the $session variable back to the private class property with $this->session.

...
private $session;
 
public function __construct(
    Session $session,
) {
    $this->session = $session;
}
...

You’ll notice that this repeats $session three times in the code, as opposed to the PHP 8 version that just references it a single time:

...
public function __construct(
    private Session $session,
) {}
...

Both of these methods create the exact same result, so you can see how much Magento benefits from constructor property promotion in PHP 8.

Motivated to learn more about Magento? Consider these 3 valuable resources I have for you:

  1. Explore Magento 2 fundamentals & best practices course (1,000+ students)
  2. Grow your Magento expertise with all courses & lessons (700+ students)
  3. Learn visually with blocks of code & inline comments (3,000+ students)