composer package updates
[openemr.git] / vendor / zendframework / zend-servicemanager / src / AbstractPluginManager.php
blob087df5a2dcd1872d77f6cce8d8e9b7cdc5f8fadd
1 <?php
2 /**
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
8 */
10 namespace Zend\ServiceManager;
12 use Interop\Container\ContainerInterface;
13 use Exception as BaseException;
14 use ReflectionMethod;
16 /**
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
28 /**
29 * Allow overriding by default
31 * @var bool
33 protected $allowOverride = true;
35 /**
36 * Whether or not to auto-add a class as an invokable class if it exists
38 * @var bool
40 protected $autoAddInvokableClass = true;
42 /**
43 * Options to use when creating an instance
45 * @var mixed
47 protected $creationOptions = null;
49 /**
50 * The main service locator
52 * @var ServiceLocatorInterface
54 protected $serviceLocator;
56 /**
57 * Constructor
59 * Add a default initializer to ensure the plugin is valid after instance
60 * creation.
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
81 ) {
82 throw new Exception\InvalidArgumentException(sprintf(
83 '%s expects a ConfigInterface instance or ContainerInterface instance; received %s',
84 get_class($this),
85 (is_object($configOrContainerInstance)
86 ? get_class($configOrContainerInstance)
87 : gettype($configOrContainerInstance)
89 ));
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
126 * @return void
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
142 * @return object
144 * @throws Exception\ServiceNotFoundException
145 * @throws Exception\ServiceNotCreatedException
146 * @throws Exception\RuntimeException
148 public function get($name, $options = [], $usePeeringServiceManagers = true)
150 $isAutoInvokable = false;
151 $cName = null;
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
164 // new instance.
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]);
176 try {
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;
199 try {
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)
208 if ($cName
209 && isset($this->instances[$cName])
210 && $this->instances[$cName] === $instance
212 unset($this->instances[$cName]);
215 return $instance;
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)
232 if ($service) {
233 $this->validatePlugin($service);
235 parent::setService($name, $service, $shared);
237 return $this;
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;
250 return $this;
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__,
282 $canonicalName,
283 ($requestedName ? '(alias: ' . $requestedName . ')' : ''),
284 $invokable
288 if (null === $this->creationOptions
289 || (is_array($this->creationOptions) && empty($this->creationOptions))
291 $instance = new $invokable();
292 } else {
293 $instance = new $invokable($this->creationOptions);
296 return $instance;
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
307 * @return mixed
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();
318 } else {
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);
329 } else {
330 throw new Exception\ServiceNotCreatedException(sprintf(
331 'While attempting to create %s%s an invalid factory was registered for this instance type.',
332 $canonicalName,
333 ($requestedName ? '(alias: ' . $requestedName . ')' : '')
337 return $instance;
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
349 * @return object
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);
358 } else {
359 $factory = null;
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
367 : null;
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
374 : [];
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(
408 $serviceName,
409 $isAutoInvokable,
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(
420 $this,
421 $serviceLocator,
422 $serviceName,
423 $exception
427 throw $exception;