1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
<?php
/**
* @link http://github.com/zendframework/zend-servicemanager for the canonical source repository
* @copyright Copyright (c) 2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\ServiceManager\AbstractFactory;
use Interop\Container\ContainerInterface;
use ReflectionClass;
use ReflectionParameter;
use Zend\ServiceManager\Exception\ServiceNotFoundException;
use Zend\ServiceManager\Factory\AbstractFactoryInterface;
/**
* Reflection-based factory.
*
* To ease development, this factory may be used for classes with
* type-hinted arguments that resolve to services in the application
* container; this allows omitting the step of writing a factory for
* each controller.
*
* You may use it as either an abstract factory:
*
* <code>
* 'service_manager' => [
* 'abstract_factories' => [
* ReflectionBasedAbstractFactory::class,
* ],
* ],
* </code>
*
* Or as a factory, mapping a class name to it:
*
* <code>
* 'service_manager' => [
* 'factories' => [
* MyClassWithDependencies::class => ReflectionBasedAbstractFactory::class,
* ],
* ],
* </code>
*
* The latter approach is more explicit, and also more performant.
*
* The factory has the following constraints/features:
*
* - A parameter named `$config` typehinted as an array will receive the
* application "config" service (i.e., the merged configuration).
* - Parameters type-hinted against array, but not named `$config` will
* be injected with an empty array.
* - Scalar parameters will result in an exception being thrown, unless
* a default value is present; if the default is present, that will be used.
* - If a service cannot be found for a given typehint, the factory will
* raise an exception detailing this.
* - Some services provided by Zend Framework components do not have
* entries based on their class name (for historical reasons); the
* factory allows defining a map of these class/interface names to the
* corresponding service name to allow them to resolve.
*
* `$options` passed to the factory are ignored in all cases, as we cannot
* make assumptions about which argument(s) they might replace.
*
* Based on the LazyControllerAbstractFactory from zend-mvc.
*/
class ReflectionBasedAbstractFactory implements AbstractFactoryInterface
{
/**
* Maps known classes/interfaces to the service that provides them; only
* required for those services with no entry based on the class/interface
* name.
*
* Extend the class if you wish to add to the list.
*
* Example:
*
* <code>
* [
* \Zend\Filter\FilterPluginManager::class => 'FilterManager',
* \Zend\Validator\ValidatorPluginManager::class => 'ValidatorManager',
* ]
* </code>
*
* @var string[]
*/
protected $aliases = [];
/**
* Constructor.
*
* Allows overriding the internal list of aliases. These should be of the
* form `class name => well-known service name`; see the documentation for
* the `$aliases` property for details on what is accepted.
*
* @param string[] $aliases
*/
public function __construct(array $aliases = [])
{
if (! empty($aliases)) {
$this->aliases = $aliases;
}
}
/**
* {@inheritDoc}
*
* @return DispatchableInterface
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$reflectionClass = new ReflectionClass($requestedName);
if (null === ($constructor = $reflectionClass->getConstructor())) {
return new $requestedName();
}
$reflectionParameters = $constructor->getParameters();
if (empty($reflectionParameters)) {
return new $requestedName();
}
$resolver = $container->has('config')
? $this->resolveParameterWithConfigService($container, $requestedName)
: $this->resolveParameterWithoutConfigService($container, $requestedName);
$parameters = array_map($resolver, $reflectionParameters);
return new $requestedName(...$parameters);
}
/**
* {@inheritDoc}
*/
public function canCreate(ContainerInterface $container, $requestedName)
{
return class_exists($requestedName);
}
/**
* Resolve a parameter to a value.
*
* Returns a callback for resolving a parameter to a value, but without
* allowing mapping array `$config` arguments to the `config` service.
*
* @param ContainerInterface $container
* @param string $requestedName
* @return callable
*/
private function resolveParameterWithoutConfigService(ContainerInterface $container, $requestedName)
{
/**
* @param ReflectionParameter $parameter
* @return mixed
* @throws ServiceNotFoundException If type-hinted parameter cannot be
* resolved to a service in the container.
*/
return function (ReflectionParameter $parameter) use ($container, $requestedName) {
return $this->resolveParameter($parameter, $container, $requestedName);
};
}
/**
* Returns a callback for resolving a parameter to a value, including mapping 'config' arguments.
*
* Unlike resolveParameter(), this version will detect `$config` array
* arguments and have them return the 'config' service.
*
* @param ContainerInterface $container
* @param string $requestedName
* @return callable
*/
private function resolveParameterWithConfigService(ContainerInterface $container, $requestedName)
{
/**
* @param ReflectionParameter $parameter
* @return mixed
* @throws ServiceNotFoundException If type-hinted parameter cannot be
* resolved to a service in the container.
*/
return function (ReflectionParameter $parameter) use ($container, $requestedName) {
if ($parameter->isArray() && $parameter->getName() === 'config') {
return $container->get('config');
}
return $this->resolveParameter($parameter, $container, $requestedName);
};
}
/**
* Logic common to all parameter resolution.
*
* @param ReflectionParameter $parameter
* @param ContainerInterface $container
* @param string $requestedName
* @return mixed
* @throws ServiceNotFoundException If type-hinted parameter cannot be
* resolved to a service in the container.
*/
private function resolveParameter(ReflectionParameter $parameter, ContainerInterface $container, $requestedName)
{
if ($parameter->isArray()) {
return [];
}
if (! $parameter->getClass()) {
if (! $parameter->isDefaultValueAvailable()) {
throw new ServiceNotFoundException(sprintf(
'Unable to create service "%s"; unable to resolve parameter "%s" '
. 'to a class, interface, or array type',
$requestedName,
$parameter->getName()
));
}
return $parameter->getDefaultValue();
}
$type = $parameter->getClass()->getName();
$type = isset($this->aliases[$type]) ? $this->aliases[$type] : $type;
if ($container->has($type)) {
return $container->get($type);
}
if (! $parameter->isOptional()) {
throw new ServiceNotFoundException(sprintf(
'Unable to create service "%s"; unable to resolve parameter "%s" using type hint "%s"',
$requestedName,
$parameter->getName(),
$type
));
}
// Type not available in container, but the value is optional and has a
// default defined.
return $parameter->getDefaultValue();
}
}