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\ModuleManager; 11 12use Traversable; 13use Zend\EventManager\EventManager; 14use Zend\EventManager\EventManagerInterface; 15 16/** 17 * Module manager 18 */ 19class ModuleManager implements ModuleManagerInterface 20{ 21 /**#@+ 22 * Reference to Zend\Mvc\MvcEvent::EVENT_BOOTSTRAP 23 */ 24 const EVENT_BOOTSTRAP = 'bootstrap'; 25 /**#@-*/ 26 27 /** 28 * @var array An array of Module classes of loaded modules 29 */ 30 protected $loadedModules = array(); 31 32 /** 33 * @var EventManagerInterface 34 */ 35 protected $events; 36 37 /** 38 * @var ModuleEvent 39 */ 40 protected $event; 41 42 /** 43 * @var bool 44 */ 45 protected $loadFinished; 46 47 /** 48 * modules 49 * 50 * @var array|Traversable 51 */ 52 protected $modules = array(); 53 54 /** 55 * True if modules have already been loaded 56 * 57 * @var bool 58 */ 59 protected $modulesAreLoaded = false; 60 61 /** 62 * Constructor 63 * 64 * @param array|Traversable $modules 65 * @param EventManagerInterface $eventManager 66 */ 67 public function __construct($modules, EventManagerInterface $eventManager = null) 68 { 69 $this->setModules($modules); 70 if ($eventManager instanceof EventManagerInterface) { 71 $this->setEventManager($eventManager); 72 } 73 } 74 75 /** 76 * Handle the loadModules event 77 * 78 * @return void 79 */ 80 public function onLoadModules() 81 { 82 if (true === $this->modulesAreLoaded) { 83 return $this; 84 } 85 86 foreach ($this->getModules() as $moduleName => $module) { 87 if (is_object($module)) { 88 if (!is_string($moduleName)) { 89 throw new Exception\RuntimeException(sprintf( 90 'Module (%s) must have a key identifier.', 91 get_class($module) 92 )); 93 } 94 $module = array($moduleName => $module); 95 } 96 $this->loadModule($module); 97 } 98 99 $this->modulesAreLoaded = true; 100 } 101 102 /** 103 * Load the provided modules. 104 * 105 * @triggers loadModules 106 * @triggers loadModules.post 107 * @return ModuleManager 108 */ 109 public function loadModules() 110 { 111 if (true === $this->modulesAreLoaded) { 112 return $this; 113 } 114 115 $this->getEventManager()->trigger(ModuleEvent::EVENT_LOAD_MODULES, $this, $this->getEvent()); 116 117 /** 118 * Having a dedicated .post event abstracts the complexity of priorities from the user. 119 * Users can attach to the .post event and be sure that important 120 * things like config merging are complete without having to worry if 121 * they set a low enough priority. 122 */ 123 $this->getEventManager()->trigger(ModuleEvent::EVENT_LOAD_MODULES_POST, $this, $this->getEvent()); 124 125 return $this; 126 } 127 128 /** 129 * Load a specific module by name. 130 * 131 * @param string|array $module 132 * @throws Exception\RuntimeException 133 * @triggers loadModule.resolve 134 * @triggers loadModule 135 * @return mixed Module's Module class 136 */ 137 public function loadModule($module) 138 { 139 $moduleName = $module; 140 if (is_array($module)) { 141 $moduleName = key($module); 142 $module = current($module); 143 } 144 145 if (isset($this->loadedModules[$moduleName])) { 146 return $this->loadedModules[$moduleName]; 147 } 148 149 /* 150 * Keep track of nested module loading using the $loadFinished 151 * property. 152 * 153 * Increment the value for each loadModule() call and then decrement 154 * once the loading process is complete. 155 * 156 * To load a module, we clone the event if we are inside a nested 157 * loadModule() call, and use the original event otherwise. 158 */ 159 if (!isset($this->loadFinished)) { 160 $this->loadFinished = 0; 161 } 162 163 $event = ($this->loadFinished > 0) ? clone $this->getEvent() : $this->getEvent(); 164 $event->setModuleName($moduleName); 165 166 $this->loadFinished++; 167 168 if (!is_object($module)) { 169 $module = $this->loadModuleByName($event); 170 } 171 $event->setModule($module); 172 173 $this->loadedModules[$moduleName] = $module; 174 $this->getEventManager()->trigger(ModuleEvent::EVENT_LOAD_MODULE, $this, $event); 175 176 $this->loadFinished--; 177 178 return $module; 179 } 180 181 /** 182 * Load a module with the name 183 * @param \Zend\EventManager\EventInterface $event 184 * @return mixed module instance 185 * @throws Exception\RuntimeException 186 */ 187 protected function loadModuleByName($event) 188 { 189 $result = $this->getEventManager()->trigger(ModuleEvent::EVENT_LOAD_MODULE_RESOLVE, $this, $event, function ($r) { 190 return (is_object($r)); 191 }); 192 193 $module = $result->last(); 194 if (!is_object($module)) { 195 throw new Exception\RuntimeException(sprintf( 196 'Module (%s) could not be initialized.', 197 $event->getModuleName() 198 )); 199 } 200 201 return $module; 202 } 203 204 /** 205 * Get an array of the loaded modules. 206 * 207 * @param bool $loadModules If true, load modules if they're not already 208 * @return array An array of Module objects, keyed by module name 209 */ 210 public function getLoadedModules($loadModules = false) 211 { 212 if (true === $loadModules) { 213 $this->loadModules(); 214 } 215 216 return $this->loadedModules; 217 } 218 219 /** 220 * Get an instance of a module class by the module name 221 * 222 * @param string $moduleName 223 * @return mixed 224 */ 225 public function getModule($moduleName) 226 { 227 if (!isset($this->loadedModules[$moduleName])) { 228 return; 229 } 230 return $this->loadedModules[$moduleName]; 231 } 232 233 /** 234 * Get the array of module names that this manager should load. 235 * 236 * @return array 237 */ 238 public function getModules() 239 { 240 return $this->modules; 241 } 242 243 /** 244 * Set an array or Traversable of module names that this module manager should load. 245 * 246 * @param mixed $modules array or Traversable of module names 247 * @throws Exception\InvalidArgumentException 248 * @return ModuleManager 249 */ 250 public function setModules($modules) 251 { 252 if (is_array($modules) || $modules instanceof Traversable) { 253 $this->modules = $modules; 254 } else { 255 throw new Exception\InvalidArgumentException( 256 sprintf( 257 'Parameter to %s\'s %s method must be an array or implement the Traversable interface', 258 __CLASS__, 259 __METHOD__ 260 ) 261 ); 262 } 263 return $this; 264 } 265 266 /** 267 * Get the module event 268 * 269 * @return ModuleEvent 270 */ 271 public function getEvent() 272 { 273 if (!$this->event instanceof ModuleEvent) { 274 $this->setEvent(new ModuleEvent); 275 } 276 return $this->event; 277 } 278 279 /** 280 * Set the module event 281 * 282 * @param ModuleEvent $event 283 * @return ModuleManager 284 */ 285 public function setEvent(ModuleEvent $event) 286 { 287 $this->event = $event; 288 return $this; 289 } 290 291 /** 292 * Set the event manager instance used by this module manager. 293 * 294 * @param EventManagerInterface $events 295 * @return ModuleManager 296 */ 297 public function setEventManager(EventManagerInterface $events) 298 { 299 $events->setIdentifiers(array( 300 __CLASS__, 301 get_class($this), 302 'module_manager', 303 )); 304 $this->events = $events; 305 $this->attachDefaultListeners(); 306 return $this; 307 } 308 309 /** 310 * Retrieve the event manager 311 * 312 * Lazy-loads an EventManager instance if none registered. 313 * 314 * @return EventManagerInterface 315 */ 316 public function getEventManager() 317 { 318 if (!$this->events instanceof EventManagerInterface) { 319 $this->setEventManager(new EventManager()); 320 } 321 return $this->events; 322 } 323 324 /** 325 * Register the default event listeners 326 * 327 * @return ModuleManager 328 */ 329 protected function attachDefaultListeners() 330 { 331 $events = $this->getEventManager(); 332 $events->attach(ModuleEvent::EVENT_LOAD_MODULES, array($this, 'onLoadModules')); 333 } 334} 335