Skip to content
This repository was archived by the owner on Feb 6, 2020. It is now read-only.

Commit 94efd0f

Browse files
committed
Merge branch 'feature/146' into develop
Close #146
2 parents e413a7e + e3701b5 commit 94efd0f

File tree

10 files changed

+566
-2
lines changed

10 files changed

+566
-2
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ All notable changes to this project will be documented in this file, in reverse
66

77
### Added
88

9-
- Nothing.
9+
- [#146](https://github.com/zendframework/zend-servicemanager/pull/146) adds
10+
`Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory`, which enables a
11+
configuration-based approach to providing class dependencies when all
12+
dependencies are services known to the `ServiceManager`. Please see
13+
[the documentation](doc/book/config-abstract-factory.md) for details.
1014

1115
### Deprecated
1216

benchmarks/BenchAsset/Bar.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
namespace ZendBench\ServiceManager\BenchAsset;
3+
4+
class Bar
5+
{
6+
protected $options;
7+
8+
public function __construct($options = null)
9+
{
10+
$this->options = $options;
11+
}
12+
}

benchmarks/FetchNewServicesBench.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use PhpBench\Benchmark\Metadata\Annotations\Iterations;
66
use PhpBench\Benchmark\Metadata\Annotations\Revs;
77
use PhpBench\Benchmark\Metadata\Annotations\Warmup;
8+
use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;
89
use Zend\ServiceManager\ServiceManager;
910

1011
/**
@@ -30,14 +31,20 @@ public function __construct()
3031
],
3132
'services' => [
3233
'service1' => new \stdClass(),
34+
'config' => [
35+
ConfigAbstractFactory::class => [
36+
BenchAsset\Bar::class => [],
37+
],
38+
],
3339
],
3440
'aliases' => [
3541
'factoryAlias1' => 'factory1',
3642
'recursiveFactoryAlias1' => 'factoryAlias1',
3743
'recursiveFactoryAlias2' => 'recursiveFactoryAlias1',
3844
],
3945
'abstract_factories' => [
40-
BenchAsset\AbstractFactoryFoo::class
46+
BenchAsset\AbstractFactoryFoo::class,
47+
ConfigAbstractFactory::class,
4148
],
4249
]);
4350
}
@@ -145,4 +152,18 @@ public function benchBuildAbstractFactoryFoo()
145152

146153
$sm->build('foo');
147154
}
155+
156+
public function benchFetchConfigAbstractFactoryBar()
157+
{
158+
$sm = clone $this->sm;
159+
160+
$sm->get(BenchAsset\Bar::class);
161+
}
162+
163+
public function benchBuildConfigAbstractFactoryBar()
164+
{
165+
$sm = clone $this->sm;
166+
167+
$sm->build(BenchAsset\Bar::class);
168+
}
148169
}

doc/book/config-abstract-factory.md

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
# Config Abstract Factory
2+
3+
- Since 3.2.0
4+
5+
You can simplify the process of creating factories by registering
6+
`Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory` with your service
7+
manager instance. This allows you to define services using a configuration map,
8+
rather than having to create separate factories for each of your services.
9+
10+
## Enabling the ConfigAbstractFactory
11+
12+
Enable the `ConfigAbstractFactory` in the same way that you would enable
13+
any other abstract factory.
14+
15+
Programmatically:
16+
17+
```php
18+
$serviceManager = new ServiceManager();
19+
$serviceManager->addAbstractFactory(new ConfigAbstractFactory());
20+
```
21+
22+
Or within configuration:
23+
24+
```php
25+
return [
26+
// zend-mvc:
27+
'service_manager' => [
28+
'abstract_factories' => [
29+
ConfigAbstractFactory::class,
30+
],
31+
],
32+
33+
// zend-expressive or ConfigProvider consumers:
34+
'dependencies' => [
35+
'abstract_factories' => [
36+
ConfigAbstractFactory::class,
37+
],
38+
],
39+
];
40+
```
41+
42+
Like all abstract factories starting in version 3, you may also use the config
43+
abstract factory as a mapped factory, registering it as a factory for a specific
44+
class:
45+
46+
```php
47+
return [
48+
'service_manager' => [
49+
'factories' => [
50+
SomeCustomClass::class => ConfigAbstractFactory::class,
51+
],
52+
],
53+
];
54+
```
55+
56+
## Configuration
57+
58+
Configuration should be provided via the `config` service, which should return
59+
an array or `ArrayObject`. `ConfigAbstractFactory` looks for a top-level key in
60+
this service named after itself (i.e., `Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory`)
61+
that is an array value. Each item in the array:
62+
63+
- Should have a key representing the service name (typically the fully
64+
qualified class name)
65+
- Should have a value that is an array of each dependency, ordered using the
66+
constructor argument order, and using service names registered with the
67+
container.
68+
69+
As an example:
70+
71+
```php
72+
use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;
73+
74+
return [
75+
ConfigAbstractFactory::class => [
76+
MyInvokableClass::class => [],
77+
MySimpleClass::class => [
78+
Logger::class,
79+
],
80+
Logger::class => [
81+
Handler::class,
82+
],
83+
],
84+
];
85+
```
86+
87+
The definition tells the service manager how this abstract factory should manage
88+
dependencies in the classes defined. In the above example, `MySimpleClass` has a
89+
single dependency on a `Logger` instance. The abstract factory will simply look
90+
to fulfil that dependency by calling `get()` with that key on the container
91+
passed to it. In this way, you can create the correct tree of
92+
dependencies to successfully return any given service.
93+
94+
In the above example, note that the abstract factory configuration does not
95+
contain configuration for the `Handler` class. At first glance, this appears as
96+
if it will fail; however, if `Handler` is configured directly with the container
97+
already &mdash; for example, mapped to a custom factory &mdash; the service will
98+
be created and used as a dependency.
99+
100+
As another, more complete example, consider the following classes:
101+
102+
```php
103+
class UserMapper
104+
{
105+
public function __construct(Adapter $db, Cache $cache) {}
106+
}
107+
108+
class Adapter
109+
{
110+
public function __construct(array $config) {}
111+
}
112+
113+
class Cache
114+
{
115+
public function __construct(CacheAdapter $cacheAdapter) {}
116+
}
117+
118+
class CacheAdapter
119+
{
120+
}
121+
```
122+
123+
In this case, we can define the configuration for these classes as follows:
124+
125+
```php
126+
// config/autoload/dependencies.php or anywhere that gets merged into global config
127+
return [
128+
ConfigAbstractFactory::class => [
129+
CacheAdapter::class => [], // no dependencies
130+
Cache::class => [
131+
CacheAdapter::class, // dependency on the CacheAdapter key defined above
132+
],
133+
UserMapper::class => [
134+
Adapter::class, // will be called using normal factory defined below
135+
Cache::class, // defined above and will be created using this abstract factory
136+
],
137+
],
138+
'service_manager' => [
139+
'factories' => [
140+
Adapter::class => AdapterFactory::class, // normal factory not using above config
141+
],
142+
],
143+
],
144+
```

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pages:
88
- Delegators: delegators.md
99
- 'Lazy services': lazy-services.md
1010
- 'Plugin managers': plugin-managers.md
11+
- 'Configuration-based Abstract Factory': config-abstract-factory.md
1112
- 'Migration Guide': migration.md
1213
site_name: zend-servicemanager
1314
site_description: 'zend-servicemanager: factory-driven dependency injection container'
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
/**
3+
* Zend Framework (http://framework.zend.com/)
4+
*
5+
* @link http://github.com/zendframework/zf2 for the canonical source repository
6+
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7+
* @license http://framework.zend.com/license/new-bsd New BSD License
8+
*/
9+
10+
namespace Zend\ServiceManager\AbstractFactory;
11+
12+
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
13+
use Zend\ServiceManager\Factory\AbstractFactoryInterface;
14+
15+
final class ConfigAbstractFactory implements AbstractFactoryInterface
16+
{
17+
18+
/**
19+
* Factory can create the service if there is a key for it in the config
20+
*
21+
* {@inheritdoc}
22+
*/
23+
public function canCreate(\Interop\Container\ContainerInterface $container, $requestedName)
24+
{
25+
if (!$container->has('config') || !array_key_exists(self::class, $container->get('config'))) {
26+
return false;
27+
}
28+
$config = $container->get('config');
29+
$dependencies = $config[self::class];
30+
31+
return is_array($dependencies) && array_key_exists($requestedName, $dependencies);
32+
}
33+
34+
/**
35+
* {@inheritDoc}
36+
*/
37+
public function __invoke(\Interop\Container\ContainerInterface $container, $requestedName, array $options = null)
38+
{
39+
if (!$container->has('config')) {
40+
throw new ServiceNotCreatedException('Cannot find a config array in the container');
41+
}
42+
43+
$config = $container->get('config');
44+
45+
if (!is_array($config)) {
46+
throw new ServiceNotCreatedException('Config must be an array');
47+
}
48+
49+
if (!array_key_exists(self::class, $config)) {
50+
throw new ServiceNotCreatedException('Cannot find a `' . self::class . '` key in the config array');
51+
}
52+
53+
$dependencies = $config[self::class];
54+
55+
if (!is_array($dependencies)
56+
|| !array_key_exists($requestedName, $dependencies)
57+
|| !is_array($dependencies[$requestedName])
58+
) {
59+
throw new ServiceNotCreatedException('Dependencies config must exist and be an array');
60+
}
61+
62+
$serviceDependencies = $dependencies[$requestedName];
63+
64+
if ($serviceDependencies !== array_values(array_map('strval', $serviceDependencies))) {
65+
$problem = json_encode(array_map('gettype', $serviceDependencies));
66+
throw new ServiceNotCreatedException('Service message must be an array of strings, ' . $problem . ' given');
67+
}
68+
69+
$arguments = array_map([$container, 'get'], $serviceDependencies);
70+
71+
return new $requestedName(...$arguments);
72+
}
73+
}

0 commit comments

Comments
 (0)