3 * Zend Framework (http://framework.zend.com/)
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
10 namespace Zend\ServiceManager
;
12 use Interop\Container\ContainerInterface
;
13 use Exception
as BaseException
;
17 * ServiceManager implementation for managing plugins
19 * Automatically registers an initializer which should be used to verify that
20 * a plugin instance is of a valid type. Additionally, allows plugins to accept
21 * an array of options for the constructor, which can be used to configure
22 * the plugin when retrieved. Finally, enables the allowOverride property by
23 * default to allow registering factories, aliases, and invokables to take
24 * the place of those provided by the implementing class.
26 abstract class AbstractPluginManager
extends ServiceManager
implements ServiceLocatorAwareInterface
29 * Allow overriding by default
33 protected $allowOverride = true;
36 * Whether or not to auto-add a class as an invokable class if it exists
40 protected $autoAddInvokableClass = true;
43 * Options to use when creating an instance
47 protected $creationOptions = null;
50 * The main service locator
52 * @var ServiceLocatorInterface
54 protected $serviceLocator;
59 * Add a default initializer to ensure the plugin is valid after instance
62 * Additionally, the constructor provides forwards compatibility with v3 by
63 * overloading the initial argument. v2 usage expects either null or a
64 * ConfigInterface instance, and will ignore any other arguments. v3 expects
65 * a ContainerInterface instance, and will use an array of configuration to
66 * seed the current instance with services. In most cases, you can ignore the
67 * constructor unless you are writing a specialized factory for your plugin
68 * manager or overriding it.
70 * @param null|ConfigInterface|ContainerInterface $configOrContainerInstance
71 * @param array $v3config If $configOrContainerInstance is a container, this
72 * value will be passed to the parent constructor.
73 * @throws Exception\InvalidArgumentException if $configOrContainerInstance
74 * is neither null, nor a ConfigInterface, nor a ContainerInterface.
76 public function __construct($configOrContainerInstance = null, array $v3config = [])
78 if (null !== $configOrContainerInstance
79 && ! $configOrContainerInstance instanceof ConfigInterface
80 && ! $configOrContainerInstance instanceof ContainerInterface
82 throw new Exception\
InvalidArgumentException(sprintf(
83 '%s expects a ConfigInterface instance or ContainerInterface instance; received %s',
85 (is_object($configOrContainerInstance)
86 ?
get_class($configOrContainerInstance)
87 : gettype($configOrContainerInstance)
92 if ($configOrContainerInstance instanceof ContainerInterface
) {
93 if (property_exists($this, 'serviceLocator')) {
94 if (! empty($v3config)) {
95 parent
::__construct(new Config($v3config));
97 $this->serviceLocator
= $configOrContainerInstance;
100 if (property_exists($this, 'creationContext')) {
101 if (! empty($v3config)) {
102 parent
::__construct($v3config);
104 $this->creationContext
= $configOrContainerInstance;
108 if ($configOrContainerInstance instanceof ConfigInterface
) {
109 parent
::__construct($configOrContainerInstance);
112 $this->addInitializer(function ($instance) {
113 if ($instance instanceof ServiceLocatorAwareInterface
) {
114 $instance->setServiceLocator($this);
120 * Validate the plugin
122 * Checks that the filter loaded is either a valid callback or an instance
123 * of FilterInterface.
125 * @param mixed $plugin
127 * @throws Exception\RuntimeException if invalid
129 abstract public function validatePlugin($plugin);
132 * Retrieve a service from the manager by name
134 * Allows passing an array of options to use when creating the instance.
135 * createFromInvokable() will use these and pass them to the instance
136 * constructor if not null and a non-empty array.
138 * @param string $name
139 * @param array $options
140 * @param bool $usePeeringServiceManagers
144 * @throws Exception\ServiceNotFoundException
145 * @throws Exception\ServiceNotCreatedException
146 * @throws Exception\RuntimeException
148 public function get($name, $options = [], $usePeeringServiceManagers = true)
150 $isAutoInvokable = false;
152 $sharedInstance = null;
154 // Allow specifying a class name directly; registers as an invokable class
155 if (!$this->has($name) && $this->autoAddInvokableClass
&& class_exists($name)) {
156 $isAutoInvokable = true;
158 $this->setInvokableClass($name, $name);
161 $this->creationOptions
= $options;
163 // If creation options were provided, we want to force creation of a
165 if (! empty($this->creationOptions
)) {
166 $cName = isset($this->canonicalNames
[$name])
167 ?
$this->canonicalNames
[$name]
168 : $this->canonicalizeName($name);
170 if (isset($this->instances
[$cName])) {
171 $sharedInstance = $this->instances
[$cName];
172 unset($this->instances
[$cName]);
177 $instance = parent
::get($name, $usePeeringServiceManagers);
178 } catch (Exception\ServiceNotFoundException
$exception) {
179 if ($sharedInstance) {
180 $this->instances
[$cName] = $sharedInstance;
182 $this->creationOptions
= null;
183 $this->tryThrowingServiceLocatorUsageException($name, $isAutoInvokable, $exception);
184 } catch (Exception\ServiceNotCreatedException
$exception) {
185 if ($sharedInstance) {
186 $this->instances
[$cName] = $sharedInstance;
188 $this->creationOptions
= null;
189 $this->tryThrowingServiceLocatorUsageException($name, $isAutoInvokable, $exception);
192 $this->creationOptions
= null;
194 // If we had a previously shared instance, restore it.
195 if ($sharedInstance) {
196 $this->instances
[$cName] = $sharedInstance;
200 $this->validatePlugin($instance);
201 } catch (Exception\RuntimeException
$exception) {
202 $this->tryThrowingServiceLocatorUsageException($name, $isAutoInvokable, $exception);
205 // If we created a new instance using creation options, and it was
206 // marked to share, we remove the shared instance
207 // (options === cannot share)
209 && isset($this->instances
[$cName])
210 && $this->instances
[$cName] === $instance
212 unset($this->instances
[$cName]);
219 * Register a service with the locator.
221 * Validates that the service object via validatePlugin() prior to
222 * attempting to register it.
224 * @param string $name
225 * @param mixed $service
226 * @param bool $shared
227 * @return AbstractPluginManager
228 * @throws Exception\InvalidServiceNameException
230 public function setService($name, $service, $shared = true)
233 $this->validatePlugin($service);
235 parent
::setService($name, $service, $shared);
241 * Set the main service locator so factories can have access to it to pull deps
243 * @param ServiceLocatorInterface $serviceLocator
244 * @return AbstractPluginManager
246 public function setServiceLocator(ServiceLocatorInterface
$serviceLocator)
248 $this->serviceLocator
= $serviceLocator;
254 * Get the main plugin manager. Useful for fetching dependencies from within factories.
256 * @return ServiceLocatorInterface
258 public function getServiceLocator()
260 return $this->serviceLocator
;
264 * Attempt to create an instance via an invokable class
266 * Overrides parent implementation by passing $creationOptions to the
267 * constructor, if non-null.
269 * @param string $canonicalName
270 * @param string $requestedName
271 * @return null|\stdClass
272 * @throws Exception\ServiceNotCreatedException If resolved class does not exist
274 protected function createFromInvokable($canonicalName, $requestedName)
276 $invokable = $this->invokableClasses
[$canonicalName];
278 if (!class_exists($invokable)) {
279 throw new Exception\
ServiceNotFoundException(sprintf(
280 '%s: failed retrieving "%s%s" via invokable class "%s"; class does not exist',
281 get_class($this) . '::' . __FUNCTION__
,
283 ($requestedName ?
'(alias: ' . $requestedName . ')' : ''),
288 if (null === $this->creationOptions
289 ||
(is_array($this->creationOptions
) && empty($this->creationOptions
))
291 $instance = new $invokable();
293 $instance = new $invokable($this->creationOptions
);
300 * Attempt to create an instance via a factory class
302 * Overrides parent implementation by passing $creationOptions to the
303 * constructor, if non-null.
305 * @param string $canonicalName
306 * @param string $requestedName
308 * @throws Exception\ServiceNotCreatedException If factory is not callable
310 protected function createFromFactory($canonicalName, $requestedName)
312 $factory = $this->factories
[$canonicalName];
313 $hasCreationOptions = !(null === $this->creationOptions ||
(is_array($this->creationOptions
) && empty($this->creationOptions
)));
315 if (is_string($factory) && class_exists($factory, true)) {
316 if (!$hasCreationOptions) {
317 $factory = new $factory();
319 $factory = new $factory($this->creationOptions
);
322 $this->factories
[$canonicalName] = $factory;
325 if ($factory instanceof FactoryInterface
) {
326 $instance = $this->createServiceViaCallback([$factory, 'createService'], $canonicalName, $requestedName);
327 } elseif (is_callable($factory)) {
328 $instance = $this->createServiceViaCallback($factory, $canonicalName, $requestedName);
330 throw new Exception\
ServiceNotCreatedException(sprintf(
331 'While attempting to create %s%s an invalid factory was registered for this instance type.',
333 ($requestedName ?
'(alias: ' . $requestedName . ')' : '')
341 * Create service via callback
343 * @param callable $callable
344 * @param string $cName
345 * @param string $rName
346 * @throws Exception\ServiceNotCreatedException
347 * @throws Exception\ServiceNotFoundException
348 * @throws Exception\CircularDependencyFoundException
351 protected function createServiceViaCallback($callable, $cName, $rName)
353 if (is_object($callable)) {
354 $factory = $callable;
355 } elseif (is_array($callable)) {
356 // reset both rewinds and returns the value of the first array element
357 $factory = reset($callable);
362 if ($factory instanceof Factory\InvokableFactory
) {
363 // InvokableFactory::setCreationOptions has a different signature than
364 // MutableCreationOptionsInterface; allows null value.
365 $options = is_array($this->creationOptions
) && ! empty($this->creationOptions
)
366 ?
$this->creationOptions
368 $factory->setCreationOptions($options);
369 } elseif ($factory instanceof MutableCreationOptionsInterface
) {
370 // MutableCreationOptionsInterface expects an array, always; pass an
371 // empty array for lack of creation options.
372 $options = is_array($this->creationOptions
) && ! empty($this->creationOptions
)
373 ?
$this->creationOptions
375 $factory->setCreationOptions($options);
376 } elseif (isset($factory)
377 && method_exists($factory, 'setCreationOptions')
379 // duck-type MutableCreationOptionsInterface for forward compatibility
381 $options = $this->creationOptions
;
383 // If we have empty creation options, we have to find out if a default
384 // value is present and use that; otherwise, we should use an empty
385 // array, as that's the standard type-hint.
386 if (! is_array($options) ||
empty($options)) {
387 $r = new ReflectionMethod($factory, 'setCreationOptions');
388 $params = $r->getParameters();
389 $optionsParam = array_shift($params);
390 $options = $optionsParam->isDefaultValueAvailable() ?
$optionsParam->getDefaultValue() : [];
393 $factory->setCreationOptions($options);
396 return parent
::createServiceViaCallback($callable, $cName, $rName);
400 * @param string $serviceName
401 * @param bool $isAutoInvokable
402 * @param BaseException $exception
404 * @throws BaseException
405 * @throws Exception\ServiceLocatorUsageException
407 private function tryThrowingServiceLocatorUsageException(
410 BaseException
$exception
412 if ($isAutoInvokable) {
413 $this->unregisterService($this->canonicalizeName($serviceName));
416 $serviceLocator = $this->getServiceLocator();
418 if ($serviceLocator && $serviceLocator->has($serviceName)) {
419 throw Exception\ServiceLocatorUsageException
::fromInvalidPluginManagerRequestedServiceName(