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 10namespace Zend\ServiceManager; 11 12use Exception as BaseException; 13 14/** 15 * ServiceManager implementation for managing plugins 16 * 17 * Automatically registers an initializer which should be used to verify that 18 * a plugin instance is of a valid type. Additionally, allows plugins to accept 19 * an array of options for the constructor, which can be used to configure 20 * the plugin when retrieved. Finally, enables the allowOverride property by 21 * default to allow registering factories, aliases, and invokables to take 22 * the place of those provided by the implementing class. 23 */ 24abstract class AbstractPluginManager extends ServiceManager implements ServiceLocatorAwareInterface 25{ 26 /** 27 * Allow overriding by default 28 * 29 * @var bool 30 */ 31 protected $allowOverride = true; 32 33 /** 34 * Whether or not to auto-add a class as an invokable class if it exists 35 * 36 * @var bool 37 */ 38 protected $autoAddInvokableClass = true; 39 40 /** 41 * Options to use when creating an instance 42 * 43 * @var mixed 44 */ 45 protected $creationOptions = null; 46 47 /** 48 * The main service locator 49 * 50 * @var ServiceLocatorInterface 51 */ 52 protected $serviceLocator; 53 54 /** 55 * Constructor 56 * 57 * Add a default initializer to ensure the plugin is valid after instance 58 * creation. 59 * 60 * @param null|ConfigInterface $configuration 61 */ 62 public function __construct(ConfigInterface $configuration = null) 63 { 64 parent::__construct($configuration); 65 $self = $this; 66 $this->addInitializer(function ($instance) use ($self) { 67 if ($instance instanceof ServiceLocatorAwareInterface) { 68 $instance->setServiceLocator($self); 69 } 70 }); 71 } 72 73 /** 74 * Validate the plugin 75 * 76 * Checks that the filter loaded is either a valid callback or an instance 77 * of FilterInterface. 78 * 79 * @param mixed $plugin 80 * @return void 81 * @throws Exception\RuntimeException if invalid 82 */ 83 abstract public function validatePlugin($plugin); 84 85 /** 86 * Retrieve a service from the manager by name 87 * 88 * Allows passing an array of options to use when creating the instance. 89 * createFromInvokable() will use these and pass them to the instance 90 * constructor if not null and a non-empty array. 91 * 92 * @param string $name 93 * @param array $options 94 * @param bool $usePeeringServiceManagers 95 * 96 * @return object 97 * 98 * @throws Exception\ServiceNotFoundException 99 * @throws Exception\ServiceNotCreatedException 100 * @throws Exception\RuntimeException 101 */ 102 public function get($name, $options = array(), $usePeeringServiceManagers = true) 103 { 104 $isAutoInvokable = false; 105 106 // Allow specifying a class name directly; registers as an invokable class 107 if (!$this->has($name) && $this->autoAddInvokableClass && class_exists($name)) { 108 $isAutoInvokable = true; 109 110 $this->setInvokableClass($name, $name); 111 } 112 113 $this->creationOptions = $options; 114 115 try { 116 $instance = parent::get($name, $usePeeringServiceManagers); 117 } catch (Exception\ServiceNotFoundException $exception) { 118 $this->tryThrowingServiceLocatorUsageException($name, $isAutoInvokable, $exception); 119 } catch (Exception\ServiceNotCreatedException $exception) { 120 $this->tryThrowingServiceLocatorUsageException($name, $isAutoInvokable, $exception); 121 } 122 123 $this->creationOptions = null; 124 125 try { 126 $this->validatePlugin($instance); 127 } catch (Exception\RuntimeException $exception) { 128 $this->tryThrowingServiceLocatorUsageException($name, $isAutoInvokable, $exception); 129 } 130 131 return $instance; 132 } 133 134 /** 135 * Register a service with the locator. 136 * 137 * Validates that the service object via validatePlugin() prior to 138 * attempting to register it. 139 * 140 * @param string $name 141 * @param mixed $service 142 * @param bool $shared 143 * @return AbstractPluginManager 144 * @throws Exception\InvalidServiceNameException 145 */ 146 public function setService($name, $service, $shared = true) 147 { 148 if ($service) { 149 $this->validatePlugin($service); 150 } 151 parent::setService($name, $service, $shared); 152 153 return $this; 154 } 155 156 /** 157 * Set the main service locator so factories can have access to it to pull deps 158 * 159 * @param ServiceLocatorInterface $serviceLocator 160 * @return AbstractPluginManager 161 */ 162 public function setServiceLocator(ServiceLocatorInterface $serviceLocator) 163 { 164 $this->serviceLocator = $serviceLocator; 165 166 return $this; 167 } 168 169 /** 170 * Get the main plugin manager. Useful for fetching dependencies from within factories. 171 * 172 * @return ServiceLocatorInterface 173 */ 174 public function getServiceLocator() 175 { 176 return $this->serviceLocator; 177 } 178 179 /** 180 * Attempt to create an instance via an invokable class 181 * 182 * Overrides parent implementation by passing $creationOptions to the 183 * constructor, if non-null. 184 * 185 * @param string $canonicalName 186 * @param string $requestedName 187 * @return null|\stdClass 188 * @throws Exception\ServiceNotCreatedException If resolved class does not exist 189 */ 190 protected function createFromInvokable($canonicalName, $requestedName) 191 { 192 $invokable = $this->invokableClasses[$canonicalName]; 193 194 if (!class_exists($invokable)) { 195 throw new Exception\ServiceNotFoundException(sprintf( 196 '%s: failed retrieving "%s%s" via invokable class "%s"; class does not exist', 197 get_class($this) . '::' . __FUNCTION__, 198 $canonicalName, 199 ($requestedName ? '(alias: ' . $requestedName . ')' : ''), 200 $invokable 201 )); 202 } 203 204 if (null === $this->creationOptions 205 || (is_array($this->creationOptions) && empty($this->creationOptions)) 206 ) { 207 $instance = new $invokable(); 208 } else { 209 $instance = new $invokable($this->creationOptions); 210 } 211 212 return $instance; 213 } 214 215 /** 216 * Attempt to create an instance via a factory class 217 * 218 * Overrides parent implementation by passing $creationOptions to the 219 * constructor, if non-null. 220 * 221 * @param string $canonicalName 222 * @param string $requestedName 223 * @return mixed 224 * @throws Exception\ServiceNotCreatedException If factory is not callable 225 */ 226 protected function createFromFactory($canonicalName, $requestedName) 227 { 228 $factory = $this->factories[$canonicalName]; 229 $hasCreationOptions = !(null === $this->creationOptions || (is_array($this->creationOptions) && empty($this->creationOptions))); 230 231 if (is_string($factory) && class_exists($factory, true)) { 232 if (!$hasCreationOptions) { 233 $factory = new $factory(); 234 } else { 235 $factory = new $factory($this->creationOptions); 236 } 237 238 $this->factories[$canonicalName] = $factory; 239 } 240 241 if ($factory instanceof FactoryInterface) { 242 $instance = $this->createServiceViaCallback(array($factory, 'createService'), $canonicalName, $requestedName); 243 } elseif (is_callable($factory)) { 244 $instance = $this->createServiceViaCallback($factory, $canonicalName, $requestedName); 245 } else { 246 throw new Exception\ServiceNotCreatedException(sprintf( 247 'While attempting to create %s%s an invalid factory was registered for this instance type.', 248 $canonicalName, 249 ($requestedName ? '(alias: ' . $requestedName . ')' : '') 250 )); 251 } 252 253 return $instance; 254 } 255 256 /** 257 * Create service via callback 258 * 259 * @param callable $callable 260 * @param string $cName 261 * @param string $rName 262 * @throws Exception\ServiceNotCreatedException 263 * @throws Exception\ServiceNotFoundException 264 * @throws Exception\CircularDependencyFoundException 265 * @return object 266 */ 267 protected function createServiceViaCallback($callable, $cName, $rName) 268 { 269 if (is_object($callable)) { 270 $factory = $callable; 271 } elseif (is_array($callable)) { 272 // reset both rewinds and returns the value of the first array element 273 $factory = reset($callable); 274 } 275 276 if (isset($factory) 277 && ($factory instanceof MutableCreationOptionsInterface) 278 && is_array($this->creationOptions) 279 && !empty($this->creationOptions) 280 ) { 281 $factory->setCreationOptions($this->creationOptions); 282 } 283 284 return parent::createServiceViaCallback($callable, $cName, $rName); 285 } 286 287 /** 288 * @param string $serviceName 289 * @param bool $isAutoInvokable 290 * @param BaseException $exception 291 * 292 * @throws BaseException 293 * @throws Exception\ServiceLocatorUsageException 294 */ 295 private function tryThrowingServiceLocatorUsageException( 296 $serviceName, 297 $isAutoInvokable, 298 BaseException $exception 299 ) { 300 if ($isAutoInvokable) { 301 $this->unregisterService($this->canonicalizeName($serviceName)); 302 } 303 304 $serviceLocator = $this->getServiceLocator(); 305 306 if ($serviceLocator && $serviceLocator->has($serviceName)) { 307 throw Exception\ServiceLocatorUsageException::fromInvalidPluginManagerRequestedServiceName( 308 $this, 309 $serviceLocator, 310 $serviceName, 311 $exception 312 ); 313 } 314 315 throw $exception; 316 } 317} 318