Skip to content
This repository has been archived by the owner on Jan 29, 2020. It is now read-only.

Commit

Permalink
Provide a specification for container configuration
Browse files Browse the repository at this point in the history
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
weierophinney committed Mar 20, 2018
1 parent f573009 commit 6fa8c82
Show file tree
Hide file tree
Showing 2 changed files with 214 additions and 0 deletions.
213 changes: 213 additions & 0 deletions docs/book/v3/features/container/config.md
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
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pages:
- 'Using zend-servicemanager': v3/features/container/zend-servicemanager.md
- 'Using Pimple': v3/features/container/pimple.md
- 'Using Aura.Di': v3/features/container/aura-di.md
- 'Container configuration': v3/features/container/config.md
- 'Routing Adapters':
- Introduction: v3/features/router/intro.md
- 'Routing Interface': v3/features/router/interface.md
Expand Down

0 comments on commit 6fa8c82

Please sign in to comment.