This repository has been archived by the owner on Jan 29, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 196
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Provide a specification for container configuration
Per #588 and https://discourse.zendframework.com/t/zend-expressive-3-dependencies-config-format/531, this patch provides a document with the specification for container configuration that implementations MUST support in order to work with Expressive applications.
- Loading branch information
1 parent
f573009
commit 6fa8c82
Showing
2 changed files
with
214 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
# Container configuration | ||
|
||
> This chapter is primarily written for container providers, so that they know | ||
> what configuration features must be compatible, and what compatibility | ||
> ultimately means within the project. | ||
[PSR-11](https://www.php-fig.org/psr/psr-11/) defines an interface for | ||
dependency injection containers, and that interface is geared towards | ||
_consumption_ of the container — not _population_ of it. | ||
|
||
Expressive _consumes_ a PSR-11 container, but also provides _configuration_ for | ||
a container: it defines what services it needs, and how to create them. | ||
|
||
As such, any container consumed by Expressive must also understand its | ||
configuration format, and deliver consistent understanding of that format when | ||
providing services based on it. | ||
|
||
This document describes the configuration format, and details expectations for | ||
implementations. | ||
|
||
## The format | ||
|
||
Container configuration is provided within the `dependencies` key of | ||
configuration. That key is structured as follows: | ||
|
||
``` | ||
return [ | ||
'dependencies' => [ | ||
'services' => [ | ||
// name => instance pairs | ||
], | ||
'aliases' => [ | ||
// alias => target pairs | ||
], | ||
'factories' => [ | ||
// service => factory pairs | ||
], | ||
'invokables' => [ | ||
// service => instantiable class pairs | ||
], | ||
'delegators' => [ | ||
// service => array of delegator factories | ||
], | ||
], | ||
]; | ||
``` | ||
|
||
## Services | ||
|
||
_Services_ are actual instances you want to retrieve later from the container. | ||
These are generally provided at initial creation; the `config` service is | ||
populated in this way. | ||
|
||
When retrieving a service mapped in this way, you will always receive the | ||
initial instance. | ||
|
||
## Aliases | ||
|
||
_Aliases_ map a service _alias_ to another service, and are provided as | ||
key/value pairs. As an example: | ||
|
||
```php | ||
'aliases' => [ | ||
'Zend\Expressive\Delegate\DefaultDelegate' => \Zend\Expressive\Handler\NotFoundHandler::class, | ||
], | ||
``` | ||
|
||
In this case, if the service named "Zend\\Expressive\\Delegate\\DefaultDelegate" | ||
is requested, the container should resolve that to the service | ||
`Zend\Expressive\Handler\NotFoundHandler` and return that instead. | ||
|
||
When returning an aliased service, the container MUST return the same instance | ||
as if the target service were retrieved. | ||
|
||
Aliases may reference other aliases, as well. In such cases, the above rules | ||
apply to the final resolved service, and not any intermediary aliases. | ||
|
||
## Factories | ||
|
||
_Factories_ map a service name to the factory capable of producing the instance. | ||
|
||
A _factory_ is any PHP callable capable of producing the instance. They may also | ||
be the _class name_ of a directly instantiable class (no constructor arguments) | ||
that defines `__invoke()`. Generally, this latter convention is used, as class | ||
names are serializable, while closures are not. | ||
|
||
Factories are guaranteed to receive the PSR-11 container as an argument, | ||
allowing you to pull other services from the container as necessary to fulfill | ||
dependencies of the class being created and returned. Additionally, containers | ||
SHOULD pass the service name requested as the second argument; factories can | ||
determine whether that argument is necessary. | ||
|
||
A typical container will generally ignore the second argument: | ||
|
||
```php | ||
use Psr\Container\ContainerInterface; | ||
use Zend\Expressive\Template\TemplateRendererInterface; | ||
|
||
function (ContainerInterface $container) | ||
{ | ||
return new SomePageHandler( | ||
$container->get(TemplateRendererInterface::class) | ||
); | ||
} | ||
``` | ||
|
||
You can, however, re-use a factory for multiple services by accepting the second | ||
argument and varying creation based on it: | ||
|
||
```php | ||
use Psr\Container\ContainerInterface; | ||
use Zend\Expressive\Template\TemplateRendererInterface; | ||
|
||
class PageFactory | ||
{ | ||
public function __invoke(ContainerInterface $container, string $serviceName) | ||
{ | ||
$name = strtolower($serviceName); | ||
return new PageHandler( | ||
$container->get(TemplateRendererInterface::class), | ||
$name | ||
); | ||
}; | ||
} | ||
``` | ||
|
||
The above could be mapped for several services: | ||
|
||
```php | ||
return [ | ||
'dependencies' => [ | ||
'factories' => [ | ||
'hello-world' => PageFactory::class, | ||
'about' => PageFactory::class, | ||
], | ||
], | ||
]; | ||
``` | ||
|
||
In general, services should be cached by the container after initial creation; | ||
factories should only be called once for any given service name. | ||
|
||
## Invokables | ||
|
||
_Invokables_ refer to any class that may be instantiated without any constructor | ||
arguments. In other words, one should be able to create an instance solely be | ||
calling `new $className()`. | ||
|
||
Configuration for invokables looks verbose; it's a map of the service name to | ||
the class name to instantiate, and, generally, these are the same values. | ||
|
||
However, you can _also_ provide a different service name. In those situations, | ||
containers MUST treat the service name as an alias to the final class name, and | ||
allow retrieving the service by EITHER the alias OR the class name. | ||
|
||
As an example, given the following configuration: | ||
|
||
```php | ||
'dependencies' => [ | ||
'invokables' => [ | ||
'HelloWorld' => PageAction::class, | ||
], | ||
], | ||
``` | ||
|
||
the container should allow retrieval of both the services "HelloWorld" as well | ||
as the "PageAction" class. | ||
|
||
## Delegator Factories | ||
|
||
Delegator factories are factories that may be used to _decorate_ or _manipulate_ | ||
a service before returning it from the container. They are covered in detail [in | ||
another chapter](delegator-factories.md), and delegator factories have the | ||
following signature: | ||
|
||
```php | ||
use Psr\Container\ContainerInterface; | ||
|
||
function ( | ||
ContainerInterface $container, | ||
string $serviceName, | ||
callable $callback | ||
) | ||
``` | ||
|
||
Configuration for delegator factories is using the "delegators" sub-key of the | ||
"dependencies" configuration. Each entry is a service name pointing to an | ||
_array_ of delegator factories. Delegator factories are called in the order they | ||
appear in configuration; when you call the `$callback` argument, it effectively | ||
calls the previous delegator to get the instance to work on; the first | ||
delegator's `$callback` argument invokes whatever functionality is required to | ||
create the initial instance (be it as a [service](#services), | ||
[invokable](#invokables), or [factory](#factories), after [alias](#aliases) | ||
resolution). | ||
|
||
Delegators MUST only be called when initially creating the service, and not | ||
each time a service is retrieved. | ||
|
||
## Other capabilities | ||
|
||
Selection of a dependency injection container should be based on capabilities | ||
that implementation provides. This may be performance, or it may be additional | ||
features beyond those specified here. We encourage _application developers_ to | ||
make full use of the container they select. The only caveat is that the above | ||
features MUST be supported by implementations for compatibility purposes, and | ||
the above are the only features _package providers_ may count on when providing | ||
container configuration. | ||
|
||
Examples of how the above capabilities may be implemented include: | ||
|
||
- https://github.com/zendframework/zend-auradi-config | ||
- https://github.com/zendframework/zend-pimple-config | ||
- https://github.com/jsoumelidis/zend-sf-di-config |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters