Skip to main content

Reflection-Based Autowiring

Overview

Reflection-based autowiring is a core feature of the DomainFlow Container that leverages PHP’s Reflection API to automatically resolve and inject dependencies. By inspecting constructor parameters and type hints, the container can instantiate classes without the need for explicit dependency declarations. This reduces boilerplate code and streamlines the dependency injection process.


Key Concepts

Automatic Dependency Resolution:

The container examines the constructor of a class, reads the type hints, and automatically determines which dependencies to inject.

Handling Advanced Types:

The autowiring mechanism supports:

  • Named Types: Automatically resolves dependencies based on their type hints.
  • Union Types: Resolves a union only when exactly one non-built-in candidate is resolvable, unless an explicit priority is configured. Multiple resolvable candidates are ambiguous.
  • Intersection Types: Resolves an intersection only when it contains a concrete class whose instance satisfies every interface in the intersection. An intersection made only of interfaces cannot be constructed automatically.

Error Handling:

If a dependency cannot be resolved (for example, a built-in type without a default value), the container throws a clear exception to alert you during development.


Implementation Details

The autowiring functionality includes two key aspects:

Building Instances:

The build() method utilizes reflection to instantiate classes while automatically resolving their constructor dependencies.

Parameter Resolution:

The resolveParameter() method examines each constructor parameter’s type and determines the appropriate dependency to inject. For more complex scenarios:

  • Union Types: The resolveUnionType() method checks all candidates and rejects ambiguous unions unless a priority is configured.
  • Intersection Types: The resolveIntersectionType() method constructs the concrete class member and verifies all remaining interface constraints.

Parameter Resolution Methods

MethodSignatureDescription
buildbuild(string $concrete, array $parameters = []): mixedInstantiates a class by resolving its constructor dependencies.
resolveParameterresolveParameter(ReflectionParameter $param, array $parameters, string $parentClass): mixedResolves a single constructor parameter based on type hints and default values.

Example Usage

Consider a scenario with a UserService that depends on an EmailService and a configuration array:

class EmailService {
public function send($recipient, $message) {
// Email sending logic...
}
}

class UserService {
public function __construct(EmailService $emailService, array $config) {
$this->emailService = $emailService;
$this->config = $config;
}
}

// Create the container and optionally bind configuration defaults.
$container = new Container();

// Resolve UserService. The container uses reflection to automatically:
// - Instantiate EmailService and inject it.
// - Use the explicitly supplied configuration array.
$userService = $container->make(UserService::class, [
'config' => ['smtp_host' => 'smtp.example.com'],
]);

In this example:

  • The container inspects UserService's constructor and automatically resolves an instance of EmailService.
  • It injects the configuration array supplied to make() into the second parameter. Built-in parameters are not resolved from string-keyed container entries by name.
  • No manual wiring is required as autowiring handles dependency resolution automatically.

Resolving Union Types

A constructor parameter typed as Foo|Bar is resolved by trying each candidate type:

  • If exactly one of the union's non-built-in types is bound or an existing class, that one is used.
  • If more than one candidate resolves, the container throws a ContainerException reporting the parameter as ambiguous, unless a priority order has been configured.
  • If none resolve and the parameter has a default value, the default is used instead.

setUnionTypePriority()

When a union-typed parameter has more than one resolvable candidate, register a priority order so the first resolvable type in that list wins instead of the container treating the case as ambiguous:

setUnionTypePriority(
string $key,
array $priorityList
): void
ParameterTypeDescription
$keystringParentClass::$paramName — identifies the exact constructor parameter this priority applies to.
$priorityListarray<string>Class/interface names from the union, in the order they should be tried.
class ReportSink {}
class DatabaseSink extends ReportSink {}
class FileSink extends ReportSink {}

class ReportWriter {
public function __construct(DatabaseSink|FileSink $sink) {}
}

$container->bind(DatabaseSink::class, DatabaseSink::class);
$container->bind(FileSink::class, FileSink::class);

// Both DatabaseSink and FileSink are resolvable — without a priority list
// this would throw as ambiguous. Prefer DatabaseSink for this parameter.
$container->setUnionTypePriority(ReportWriter::class . '::$sink', [DatabaseSink::class]);

$writer = $container->make(ReportWriter::class); // Uses DatabaseSink.

Contextual bindings (see Contextual Bindings) are still checked first for each candidate type, before the priority order or plain resolvability is considered.


Benefits

  • Reduced Boilerplate:
    Developers no longer need to manually specify every dependency in the container’s configuration.

  • Ease of Maintenance:
    Changes to class constructors are automatically managed, provided type hints remain consistent.

  • Robust Type Handling:
    Advanced support for union and intersection types ensures that even complex dependencies are resolved correctly.


Summary

Reflection-based autowiring simplifies dependency injection by leveraging PHP’s Reflection API to automatically inspect and resolve dependencies. This feature minimizes manual configuration, reduces boilerplate code, and improves maintainability by ensuring that dependencies are managed automatically. It is a fundamental aspect of the DomainFlow Container, making it easier to build modular and scalable applications.