Dependency injection

TYPO3 uses a dependency injection solution based on the corresponding PSR-11 compliant Symfony component to standardize object initialization throughout the Core as well as in extensions.

The recommended way of injecting dependencies is to use constructor injection:

public function __construct(Dependency $dependency)
{
    $this->dependency = $dependency;
}

By default all API services shipped by the TYPO3 Core system extensions are available for dependency injection.

Note

The following document aims at explaining dependency injection as used in TYPO3. For those new to the concepts and principles behind dependency injection, please read more general introductions on the topic first - for example at PHP The Right Way and at Fabien Potenciers blog.

When to use Dependency Injection in TYPO3

Deprecated since version 11.4: The class \TYPO3\CMS\Extbase\Object\ObjectManager has been deprecated with TYPO3 11.4. Classes should be updated to avoid both, \TYPO3\CMS\Extbase\Object\ObjectManager and \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance() whenever possible when requiring service dependencies.

Class dependencies to services should be injected via constructor injection or setter methods. Where possible, Symfony dependency injection should be used for all cases where DI is required.

Non-service “data objects” like Extbase model instances or DTOs should be instantiated via \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance() if they are non-final and support XCLASSing. For final classes without dependencies the new keyword can be used.

Configure dependency injection in Extensions

Extensions have to configure their classes to make use of the dependency injection. This can be done in Configuration/Services.yaml. Alternatively Configuration/Services.php can be used.

# Configuration/Services.yaml
services:
  _defaults:
    autowire: true
    autoconfigure: true
    public: false

  Your\Namespace\:
    resource: '../Classes/*'
    exclude: '../Classes/Domain/Model/*'

This is how a basic Services.yaml of an extension looks like. The meaning of autowire, autoconfigure and public will be explained below.

Note

Whenever service configuration or class dependencies change, the Core cache needs to be flushed in the Install Tool to rebuild the compiled Symfony container. Flushing all caches from the cache clear menu does not flush the compiled Symfony container.

Note

The path exclusion exclude: '../Classes/Domain/Model/*' excludes your models from the DI container, which means you cannot inject them or inject dependencies into them. Models are not services and should therefore not require dependency injection. Also, these objects are created by the Extbase persistence layer which does not support the DI container.

Autowire

autowire: true instructs the dependency injection component to calculate the required dependencies from type declarations. This works for constructor and inject methods. The calculation yields to a service initialization recipe which is cached in php code (in TYPO3 Core cache).

Note

An extension doesn’t need to use autowiring, it is free to manually wire dependencies in the service configuration file.

Autoconfigure

It is suggested to enable autoconfigure: true as this will automatically add Symfony service tags based on implemented interfaces or base classes. For example autoconfiguration ensures that classes which implement \TYPO3\CMS\Core\SingletonInterface will be publicly available from the Symfony container.

Arguments

In case you turned autowire off or need special arguments set, you can configure those as well. This means you could set autowire: false for an extension but provide the needed arguments via config specifically for classes you want to. This can be done in chronological order or by naming them.

# Configuration/Services.yaml
  Vendor\MyExtension\UserFunction\ClassA:
    arguments:
      $argA: '@TYPO3\CMS\Core\Database\ConnectionPool'

  Vendor\MyExtension\UserFunction\ClassB:
    arguments:
      - '@TYPO3\CMS\Core\Database\ConnectionPool'

This enables you to inject concrete objects like the QueryBuilder or Database Connection:

# Configuration/Services.yaml
  querybuilder.pages:
    class: 'TYPO3\CMS\Core\Database\Query\QueryBuilder'
    factory:
      - '@TYPO3\CMS\Core\Database\ConnectionPool'
      - 'getQueryBuilderForTable'
    arguments:
      - 'pages'

  Vendor\MyExtension\UserFunction\ClassA:
    public: true
    arguments:
      - '@querybuilder.pages'

Now you can access the QueryBuilder instance within ClassA. With this you can call your queries without further instantiation. Be aware to clone your object or resetting the query parts to prevent side effects in case of multiple usages.

This method of injecting Objects does also work with e.g. extension configurations or TypoScript settings.

Public

public: false is a performance optimization and is therefore suggested to be set in extensions. This settings controls which services are available through the dependency injection container which is used internally by GeneralUtility::makeInstance().

However some classes that need to be public will be marked public automatically due to autoconfigure: true.

These classes include singletons, because they need to be shared with code that uses GeneralUtility::makeInstance() and Extbase controllers.

Knowing what to make public

Direct access includes instantiation via GeneralUtility::makeInstance() with constructor arguments.

This means every class that needs dependency injection and is retrieved directly, e.g. using GeneralUtility::makeInstance() must be marked as public.

Any other class which needs dependency injection and is retrieved by dependency injection itself can be private.

Instances of \TYPO3\CMS\Core\SingletonInterface and Extbase controllers are automatically marked as public because they are retrieved using GeneralUtility::makeInstance().

More examples for classes which must be marked as public:

  • User functions
  • Non-Extbase controllers
  • Classes registered in hooks
  • Authentication services
  • Fluid data processors

For such classes an extension can override the global public: false configuration in the Configuration/Services.yaml for each affected class.

# Configuration/Services.yaml
services:
  _defaults:
    autowire: true
    autoconfigure: true
    public: false

  Vendor\MyExtension\:
    resource: '../Classes/*'
    exclude: '../Classes/Domain/Model/*'

  Vendor\MyExtension\UserFunction\ClassA:
    public: true

With this configuration you can use dependency injection in \Vendor\MyExtension\UserFunction\ClassA when it is created e.g. in the context of a USER TypoScript object which would not be possible if this class was private.

Errors resulting from wrong configuration

When objects using dependency injection are not configured properly, one or more of the following issues can be the result. In such a case, check whether the class has to be configured as public: true.

ArgumentCountError is raised on missing dependency injection for Constructor injection:

(1/1) ArgumentCountError

Too few arguments to function Vendor\ExtName\Namespace\Class::__construct(),
0 passed in typo3/sysext/core/Classes/Utility/GeneralUtility.php on line 3461 and exactly 1 expected

An Error is raised on missing dependency injection for Method injection, once the dependency is used within the code:

(1/1) Error

Call to a member function methodName() on null

Supported ways of dependency injection

Constructor injection

A class dependency can simply be specified as a constructor argument:

public function __construct(Dependency $dependency)
{
    $this->dependency = $dependency;
}

Method injection

As an alternative to constructor injection injectDependency() methods can be used. Additionally a setDependency() will also work if it has the annotation @required:

/**
 * @param MyDependency $myDependency
 */
public function injectMyDependency(MyDependency $myDependency)
{
    $this->myDependency = $myDependency;
}

/**
 * @param MyOtherDependency $myOtherDependency
 * @required
 */
public function setMyOtherDependency(MyOtherDependency $myOtherDependency)
{
    $this->myOtherDependency = $myOtherDependency;
}

Interface injection

It is possible to inject interfaces as well. If there is only one implementation for a certain interface the interface injection is simply resolved to this implementation:

public function __construct(DependencyInterface $dependency)
{
    $this->dependency = $dependency;
}

When multiple implementation of the same interface exist, an extension needs to specify which implementation should be injected when the interface is type hinted. Find out more about how this is achieved in the official Symfony documentation.

Dependency injection in an XCLASSed class

If an existing class (for example an Extbase controller) is extended using XCLASS and additional dependencies are injected using constructor injection, it must be ensured that a reference to the extended class is added in the Configuration/Services.yaml file of the extending extension as shown in the example below:

TYPO3\CMS\Belog\Controller\BackendLogController: '@Namespace\Extension\Controller\ExtendedBackendLogController'