Skip to main content

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 final class with a private property, the container binds a closure to the instance to set the value, since reflection cannot make a private property of a final class 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 declarationResult
No type declaredRejected — 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 propertyRejected — readonly properties cannot be injected.
Single class/interface type, not readonlyInjected.

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 UserRepository class has a private property $dbConnection marked with the #[Inject] attribute.
  • When UserRepository is resolved by the container, the injectProperties() method automatically sets $dbConnection to an instance of DatabaseConnection.
  • 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 for mutable services where a dependency cannot be supplied through the constructor. Readonly properties are not supported.


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.