Menu

Dependency Injection & Configuration

Dependency injection controls the way how extension objects are constructed for usage by PHPStan.

PHPStan is like any other application that utilizes dependency injection. The objects that exist during PHPStan’s run fall into two categories: services and value objects.

Services #

Objects that exist throughout the whole analysis. They’re created at the beginning and destroyed at the end. There’s usually only a single service object per class - there’s very little reason for multiple objects of the same class to exist. Some examples of service objects are: ReflectionProvider, and FileTypeMapper.

All extension types require us to register them as services.

If we want to obtain a different service in our service class, we ask for it through the constructor:

private ReflectionProvider $reflectionProvider;

public function __construct(
ReflectionProvider $reflectionProvider
)
{
$this->reflectionProvider = $reflectionProvider;
}

Value objects #

Value objects carry values. Many value objects are created and destroyed throughout the analysis. It make sense for many objects of the same class to exist at the same time. Some examples of value objects are: instances of Type implementations like ObjectType, instances of reflection objects like MethodReflection.

In contrast with services, value objects are not obtained by asking for them through constructor. They’re either created with the new operator, or obtained from places like Scope or ReflectionProvider.

Dependency injection container #

The dependency injection container is responsible for creating our service classes and supplying the requested services to the constructor. This is happening entirely in the background. Developers can configure what the container does through the configuration file.

Registering services #

PHPStan utilizes nette/di as its dependency injection container. All the nette/di documentation about services section applies to PHPStan’s .neon files as well.

A new service object is registered by adding a new entry into services section:

services:
-
class: App\MyExtension

All the extension types have associated tags needed to recognize the extension:

	-
class: App\MyExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension

Autowiring #

Service objects can ask for other service objects through their constructor. No additional configuration is needed if there’s a single registered service of that type. The dependency injection container will supply it automatically. That’s called autowiring.

If there are multiple services of the requested type, it’s not obvious which one should be injected into the constructor. The services entry can contain arguments section to specify which service should be injected:

services:
serviceOne:
class: App\MyService

serviceTwo:
class: App\MyService

-
# App\MyExtension has "myService" constructor parameter
class: App\MyExtension
arguments:
myService: @serviceTwo

Non-object constructor parameters have to always be specified in the arguments section. Let’s say we’re interested in a built-in PHPStan parameter checkUninitializedProperties:

services:
-
# App\MyExtension has "checkUninitializedProperties" constructor parameter
class: App\MyExtension
arguments:
checkUninitializedProperties: %checkUninitializedProperties%

Custom parameters #

PHPStan extensions can introduce custom parameters so that users can influence the extension behaviour in their project’s configuration file:

parameters:
myExtension:
myOwnParameter: true

services:
-
class: App\MyExtension
arguments:
myOwnParameter: %myExtension.myOwnParameter%

But that’s not sufficient. In order to prevent typos, PHPStan requires custom parameters to be defined in parametersSchema section of the configuration file:

parametersSchema:
myExtension: structure([
myOwnParameter: bool()
])

The schema is enforced using the nette/schema library. See how PHPStan’s own schema is defined to get an idea how to define yours.

Edit this page on GitHub

© 2016–2021 Ondřej Mirtes