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

Commit 367fd0c

Browse files
committed
Merge pull request #146 from GeeH/feature/config-abstract-factory
Adds a new Config Abstract Factory that allows service creation by config
2 parents e413a7e + 3283c9a commit 367fd0c

File tree

8 files changed

+536
-1
lines changed

8 files changed

+536
-1
lines changed

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: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# Config Abstract Factory
2+
3+
You can simplify the process of creating factories by adding the
4+
`ConfigAbstractFactory` to your service manager. This allows you to define
5+
services using a configuration map, rather than having to create separate
6+
factories for all your services.
7+
8+
## Enabling
9+
You can enable the `ConfigAbstractFactory` in the same way that you would enable
10+
any other abstract factory - in your own code:
11+
12+
```php
13+
$serviceManager = new ServiceManager();
14+
$serviceManager->addAbstractFactory(new ConfigAbstractFactory());
15+
```
16+
17+
Or within any config provider using:
18+
19+
```php
20+
return [
21+
'service_manager' => [
22+
'abstract_factories' => [
23+
ConfigAbstractFactory::class,
24+
],
25+
],
26+
];
27+
```
28+
29+
It is also possible to use the config abstract factory in the traditional way; by registering
30+
it as a factory for a specific class. This marginally improves performance but loses
31+
the benefit of not needing to create a factory key for all of you configured factories:
32+
33+
```php
34+
return [
35+
'service_manager' => [
36+
'factories' => [
37+
SomeCustomClass::class => ConfigAbstractFactory::class,
38+
],
39+
],
40+
];
41+
```
42+
43+
## Configuring
44+
45+
Configuration is done through the `config` service manager key, in an array with
46+
the key `Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory`. If you are using
47+
config merging from the MVC/ModuleManager, in this just means that you can
48+
add a `ConfigAbstractFactory::class` key to your merged config which contains service
49+
definitions, where the key is the service name (typically the FQNS of the class you are
50+
defining), and the value is an array of it's dependencies, also defined as container keys.
51+
52+
```php
53+
use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;
54+
55+
return [
56+
ConfigAbstractFactory::class => [
57+
MyInvokableClass::class => [],
58+
MySimpleClass::class => [
59+
Logger::class,
60+
],
61+
Logger::class => [
62+
Handler::class,
63+
],
64+
],
65+
];
66+
```
67+
68+
The definition tells the service manager how this abstract factory should manage dependencies in
69+
the classes defined. In the above example, `MySimpleClass` has a single dependency on a `Logger`
70+
instance. The abstract factory will simply look to fulfil that dependency by calling a `get`
71+
call with that key on the service manager it is attached to. In this way, you can create the
72+
correct tree of dependencies to successfully return any given service. Note that `Handler` does not have a
73+
configuration for the abstract factory, but this would work if `Handler` had a traditional factory and
74+
can be created by this service manager.
75+
76+
For a better example, consider the following classes:
77+
78+
```php
79+
class UserMapper
80+
{
81+
public function __construct(Adapter $db, Cache $cache) {}
82+
}
83+
84+
class Adapter
85+
{
86+
public function __construct(array $config) {}
87+
}
88+
89+
class Cache
90+
{
91+
public function __construct(CacheAdapter $cacheAdapter) {}
92+
}
93+
94+
class CacheAdapter
95+
{
96+
}
97+
```
98+
99+
In this case, we can define the configuration for these classes as follows:
100+
101+
```php
102+
// config/autoload/dependencies.php or anywhere that gets merged into global config
103+
return [
104+
ConfigAbstractFactory::class => [
105+
CacheAdapter::class => [], // no dependencies
106+
Cache::class => [
107+
CacheAdapter::class, // dependency on the CacheAdapter key defined above
108+
],
109+
UserMapper::class => [
110+
Adapter::class, // will be called using normal factory defined below
111+
Cache::class, // defined above and will be created using this abstract factory
112+
],
113+
],
114+
'service_manager' => [
115+
'factories' => [
116+
Adapter::class => AdapterFactory::class, // normal factory not using above config
117+
],
118+
],
119+
],
120+
```
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)