Deprecation: #90803 - ObjectManager::get in Extbase context

See forge#90803

Description

To help understand the deprecation of $objectManager->get(Service::class) let's first have a look at its domain: Dependency Injection and its history as well as the culprits to deal with.

With the introduction of Extbase over one decade ago, a lot of modern software development paradigms have been introduced into TYPO3. One of that paradigms is Dependency Injection (DI) which is an approach of handling dependencies different than the one the TYPO3 core followed ever since.

Given there is an EmailService class, which is responsible for sending emails, the usual approach of creating such a service was to create it the moment it was needed. TYPO3 never used the new keyword to create new objects, but GeneralUtility::makeInstance(), which pretty much does the same thing. So, one approach of creating dependencies is creating them in the current scope where the dependency is needed.

Tip

As a rule of thumb, you can remember the following: Whenever you are creating dependencies yourself with new or GeneralUtility::makeInstance(), you are not using Dependency Injection.

Extbase introduced the concept of Dependency Injection (DI) which means, that all dependencies are declared in a way, that the dependency chain is known before runtime. The most common way of implementing DI is to declare dependencies as constructor arguments. This means, in the scope of the current class, all dependencies are made visible as constructor arguments. As those dependencies need to be created outside the current scope, a service container implementation is responsible for the creation and management of service instances. Then, instead of calling new Service(...), the container needs to be queried for the needed service, e.g. by calling $container->get(Service::class). This also assures that the container provide the requested services with their dependencies, as they are created the same way.

There is an service container in Extbase but it's not exposed to the public. Instead, there is the ObjectManager class, which acts as a proxy for the container and also has a get method, to query instances of services.

Exactly that get() method is now deprecated in the extbase context because it should never be called directly.

The usual extbase context is a controller. All controllers are created by the object manager and therefore support DI. Whenever a dependency is needed in an extbase context, instead of calling $objectManager->get(Service::class), the usual DI approaches have to be used. Those approaches are constructor, method and property injection.

Migration

If you are using code similar to the following example, you should migrate to dependency injection:

class MainController
{
    public function listAction()
    {
        $service = $this->objectManager->get(Service::class);
        $service->doSomething();
    }
}

Examples how to use dependency injection:

Constructor Injection

class MainController
{
    private $service;

    public function __construct(Service $service)
    {
        $this->service = $service;
    }

    public function listAction()
    {
        $this->service->doSomething();
    }
}

Tip

Constructor injection is the preferred type of injection for dependencies.

Method Injection

class MainController
{
    private $service;

    public function injectService(Service $service)
    {
        $this->service = $service;
    }

    public function listAction()
    {
        $this->service->doSomething();
    }
}

Property Injection

class MainController
{
    /**
     * @var Service
     * @TYPO3\CMS\Extbase\Annotation\Inject
     */
    public $service;

    public function listAction()
    {
        $this->service->doSomething();
    }
}

Unfortunately, there is even more to consider here. Dependencies usually are services and services are objects which are shareable. TYPO3 users might be more used to the term Singleton, which means, that there is just one instance of a service during runtime which is shared across all scopes. Singletons are a great way to save resources but there is more to Singletons than just that. To be able to share the same instance of a class across all scopes, the instance cannot store information about its state in its properties. The idea of Singletons is to have an object that always behaves the same, no matter where it is used.

Let's have a look at classes that are no services. We can borrow the term prototype from the Java world. A commonly used prototype object is a model. Each instance of a model clearly has a different state and therefore a different functionality. Those objects can theoretically be injected but it's very uncommon to do so. Still, in Extbase, instances of prototypes (e.g. instances of models, or other instances that hold state) are very often created with the object manager, which is bad practice. new or GeneralUtility::makeInstance() should be used for instantiating prototypes.

However, when it comes to prototypes, there is a mechanic which cannot be implemented differently yet: the override of an implementation.

It means, that it's possible to tell the ObjectManager to create an instance of a different class than the one which is requested. One example of that is class TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbBackend, which can be fetched from the ObjectManager by requesting an instance of the TYPO3\CMS\Extbase\Persistence\Generic\Storage\BackendInterface interface. This feature should only be used for services as well but it is often used to override models of other extensions. For models you can either decide to simply instantiate via new, or if you want to provide support for overwriting models via XCLASSes configured in ext_localconf.php (configuration variable: $GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects']) you may also use GeneralUtility::makeInstance().

Tip

Conclusion:

Singletons (services without state) should be provided by Dependency Injection wherever possible.

To create prototypes (instances with state), use new or GeneralUtility::makeInstance().

ObjectManager->get() must no longer be used.

Impact

There is no impact yet. No PHP E_USER_DEPRECATED error is triggered in TYPO3 10. This will probably change in TYPO3 11.x.

Affected Installations

All installations that use ObjectManager->get() directly to create instances of dependencies in a scope that supports native Dependency Injection.

Migration

As mentioned above, constructor, method or property injection must be used instead.