Hiraeth’s flexibility and power comes from its dependency injection system which relies on the Hiraeth\Broker
, a PSR-11 compatible version of the auryn dependency injector. Dependency injection is a way to instantiate classes into objects such that the additional objects on which that class relies (depends) are automatically created and passed to its __construct()
method.
Hiraeth does not encourage (although may succumb to on occasion) the service locator anti-pattern. This occurs when the dependency injector (the “container” in this context) is injected directly into a service as opposed to the service being constructed and having its dependencies injected. The dependendent class then uses the container to request its dependencies directly. For example:
class Service
{
public function __construct(Psr\Container\ContainerInterface $container)
{
$this->dependency = $container->get(Dependency::class);
}
}
This, generally results in only marginally better decoupling than if you had simply done the following:
$this->dependency = new Dependency();
That said, if you’re working with a library that requires an instance of Psr\Container\ContainerInterface
the broker class will suffice. And if that class is itself dependency injected, the broker will be injected, as configured in config/packages/core.jin
:
[application]
aliases = {
"Psr\Container\ContainerInterface": "Hiraeth\Broker"
}
Additionally the Hiraeth\Application
provides an extended get()
that allows for specifying named argument overloads. An example of where this is used is for the hiraeth/volumes
package where the [&.options]
for a configured volume are passed to the adapter:
[volume]
class = League\Flysystem\Local\LocalFilesystemAdapter
[&.options]
location = dir(storage/private)
:visibility = Hiraeth\Volumes\Visibility\UmaskConverter
In the above example, this adapter is constructed by calling:
$app->get(League\Flysystem\Local\LocalFilesystemAdapter:class, [
'location' => 'storage/private',
':visibility' => Hiraeth\Volumes\Visibility\UmaskConverter::class,
]);
The location
is passed as an “as is” parameter, while the :
at the start of the visibility
parameter indicates the dependency injector should attempt to construct the specified class an inject that. This enables users to easily change the visibility for their volumes by simply editing the config and providing a different class that extends League\Flysystem\UnixVisibility\PortableVisibilityConverter
Dependency injection is not magic, it relies on objects being instantiated by the dependency injector itself through what is referred to as “inversion of control.” The autowiring features of Hiraeth’s dependency injector, additionally, require good object oriented design with strong typehinting. Using hiraeth/bootstrap
, all of the following will be dependency injected:
- Actions
- Responders
- Middleware
- Commands
Dependency injection is recursive by default. As such, the dependency injector will try to construct any class / interface typehinted dependencies for your dependencies.
In many cases, dependency autowiring is not enough. This is because not every argument required to construct a class is generally another class or interface that also requires no additional arguments. At some point, classes will require some actual information. This requires the dependency injector to know how to get that information and provide it when constructing those classes. This is resolved by creating delegates (for constructing a dependency) and providers (for calling methods on a newly constructed dependency to augment its behavior).