Property Injection
Overview
Property injection enables the automatic injection of dependencies directly into class properties. By marking a property with the #[Inject] attribute, the container identifies it as requiring dependency injection during the autowiring process. This reduces boilerplate code and simplifies dependency initialization, particularly when constructor injection is not feasible.
Key Concepts
Attribute-Based Injection:
Properties marked with the PHP attribute #[Inject] are automatically flagged for dependency injection.
Automatic Resolution:
During object construction, the container scans for properties with the Inject attribute, uses reflection to resolve the type-hinted dependency, and assigns the resolved instance to the property.
Reflection Integration:
The container’s autowiring mechanism leverages PHP’s Reflection API (using methods like injectProperties()) to perform the injection, including adjusting accessibility for non-public properties.
Implementation Details
The Injection Process
During the build process, the container calls the injectProperties() method to inspect each property of the constructed object. For every property marked with the Inject attribute:
- The method checks the property’s declared type.
- If the type is a single non-builtin named type, it resolves the dependency using the container’s
make()method. - The property’s value is then set to the resolved instance. For a
finalclass with aprivateproperty, the container binds a closure to the instance to set the value, since reflection cannot make a private property of afinalclass accessible directly; for every other case,ReflectionProperty::setValue()is used.
Validation Rules
An injectable property must declare exactly one non-built-in named type. The container rejects anything else with a ContainerException that identifies the offending property (Class::$property):
| Property declaration | Result |
|---|---|
| No type declared | Rejected — must declare a non-built-in named type. |
Built-in type (string, int, array, …) | Rejected — must declare a non-built-in named type. |
Union type (Foo|Bar) | Rejected — union types are not supported for injection. |
Intersection type (Foo&Bar) | Rejected — intersection types are not supported. |
readonly property | Rejected — readonly properties cannot be injected. |
| Single class/interface type, not readonly | Injected. |
These checks run for every property carrying #[Inject], regardless of visibility.
Example Usage
Below is an example demonstrating how property injection works in practice:
use DomainFlow\Container;
use DomainFlow\Container\Attribute\Inject;
class DatabaseConnection {
public function connect() {
echo "Database connected.";
}
}
class UserRepository {
#[Inject] // This attribute marks the property for automatic dependency injection.
private DatabaseConnection $dbConnection;
public function fetchAll() {
// Use the injected dependency.
$this->dbConnection->connect();
echo "Fetching all users...";
}
}
// Create the container and bind the dependency.
$container = new Container();
$container->bind(DatabaseConnection::class, DatabaseConnection::class);
// Resolve the UserRepository.
// During instantiation, the container automatically injects an instance of DatabaseConnection.
$userRepo = $container->get(UserRepository::class);
$userRepo->fetchAll();
In this example:
- The
UserRepositoryclass has a private property$dbConnectionmarked with the#[Inject]attribute. - When
UserRepositoryis resolved by the container, theinjectProperties()method automatically sets$dbConnectionto an instance ofDatabaseConnection. - The
fetchAll()method then uses this injected dependency without any manual wiring.
Benefits
-
Reduced Boilerplate:
Eliminates the need to manually pass dependencies via the constructor or setters. -
Cleaner Code:
Keeps class constructors uncluttered, particularly for classes with many dependencies. -
Flexibility:
Enables injection even in scenarios where constructor injection is impractical, such as in legacy code or with immutable objects.
Summary
Property injection, powered by the #[Inject] attribute and supported by the container’s reflection-based autowiring, offers an elegant solution for dependency management. It streamlines the injection process by automatically resolving and assigning dependencies to marked properties, reducing manual configuration and simplifying code maintenance.