diff --git a/src/Exception/ExceptionInterface.php b/src/Exception/ExceptionInterface.php
new file mode 100644
index 0000000..4ead054
--- /dev/null
+++ b/src/Exception/ExceptionInterface.php
@@ -0,0 +1,18 @@
+setOptions(new ListenerOptions);
+ } else {
+ $this->setOptions($options);
+ }
+ }
+
+ /**
+ * Get options.
+ *
+ * @return ListenerOptions
+ */
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
+ /**
+ * Set options.
+ *
+ * @param ListenerOptions $options the value to be set
+ * @return AbstractListener
+ */
+ public function setOptions(ListenerOptions $options)
+ {
+ $this->options = $options;
+ return $this;
+ }
+
+ /**
+ * Write a simple array of scalars to a file
+ *
+ * @param string $filePath
+ * @param array $array
+ * @return AbstractListener
+ */
+ protected function writeArrayToFile($filePath, $array)
+ {
+ $content = "getModule();
+ if (!$module instanceof AutoloaderProviderInterface
+ && !method_exists($module, 'getAutoloaderConfig')
+ ) {
+ return;
+ }
+ $autoloaderConfig = $module->getAutoloaderConfig();
+ AutoloaderFactory::factory($autoloaderConfig);
+ }
+}
diff --git a/src/Listener/ConfigListener.php b/src/Listener/ConfigListener.php
new file mode 100644
index 0000000..f204ade
--- /dev/null
+++ b/src/Listener/ConfigListener.php
@@ -0,0 +1,406 @@
+hasCachedConfig()) {
+ $this->skipConfig = true;
+ $this->setMergedConfig($this->getCachedConfig());
+ } else {
+ $this->addConfigGlobPaths($this->getOptions()->getConfigGlobPaths());
+ $this->addConfigStaticPaths($this->getOptions()->getConfigStaticPaths());
+ }
+ }
+
+ /**
+ * __invoke proxy to loadModule for easier attaching
+ *
+ * @param ModuleEvent $e
+ * @return ConfigListener
+ */
+ public function __invoke(ModuleEvent $e)
+ {
+ return $this->loadModule($e);
+ }
+
+ /**
+ * Attach one or more listeners
+ *
+ * @param EventManagerInterface $events
+ * @return ConfigListener
+ */
+ public function attach(EventManagerInterface $events)
+ {
+ $this->listeners[] = $events->attach('loadModule', array($this, 'loadModule'), 1000);
+ $this->listeners[] = $events->attach('loadModules.pre', array($this, 'loadModulesPre'), 9000);
+ $this->listeners[] = $events->attach('loadModules.post', array($this, 'loadModulesPost'), 9000);
+ return $this;
+ }
+
+ /**
+ * Pass self to the ModuleEvent object early so everyone has access.
+ *
+ * @param ModuleEvent $e
+ * @return ConfigListener
+ */
+ public function loadModulesPre(ModuleEvent $e)
+ {
+ $e->setConfigListener($this);
+ return $this;
+ }
+
+ /**
+ * Merge the config for each module
+ *
+ * @param ModuleEvent $e
+ * @return ConfigListener
+ */
+ public function loadModule(ModuleEvent $e)
+ {
+ if (true === $this->skipConfig) {
+ return;
+ }
+ $module = $e->getParam('module');
+ if (is_callable(array($module, 'getConfig'))) {
+ $this->mergeModuleConfig($module);
+ }
+ return $this;
+ }
+
+ /**
+ * Merge all config files matched by the given glob()s
+ *
+ * This should really only be called by the module manager.
+ *
+ * @param ModuleEvent $e
+ * @return ConfigListener
+ */
+ public function loadModulesPost(ModuleEvent $e)
+ {
+ if (true === $this->skipConfig) {
+ return $this;
+ }
+ foreach ($this->paths as $path) {
+ $this->mergePath($path);
+ }
+ return $this;
+ }
+
+ /**
+ * Detach all previously attached listeners
+ *
+ * @param EventManagerInterface $events
+ * @return ConfigListener
+ */
+ public function detach(EventManagerInterface $events)
+ {
+ foreach ($this->listeners as $key => $listener) {
+ $events->detach($listener);
+ unset($this->listeners[$key]);
+ }
+ $this->listeners = array();
+ return $this;
+ }
+
+ /**
+ * getMergedConfig
+ *
+ * @param bool $returnConfigAsObject
+ * @return mixed
+ */
+ public function getMergedConfig($returnConfigAsObject = true)
+ {
+ if ($returnConfigAsObject === true) {
+ if ($this->mergedConfigObject === null) {
+ $this->mergedConfigObject = new Config($this->mergedConfig);
+ }
+ return $this->mergedConfigObject;
+ } else {
+ return $this->mergedConfig;
+ }
+ }
+
+ /**
+ * setMergedConfig
+ *
+ * @param array $config
+ * @return ConfigListener
+ */
+ public function setMergedConfig(array $config)
+ {
+ $this->mergedConfig = $config;
+ $this->mergedConfigObject = null;
+ return $this;
+ }
+
+ /**
+ * Add a path of config files to merge after loading modules
+ *
+ * @param string $path
+ * @return ConfigListener
+ */
+ protected function addConfigPath($path, $type)
+ {
+ if (!is_string($path)) {
+ throw new Exception\InvalidArgumentException(
+ sprintf('Parameter to %s::%s() must be a string; %s given.',
+ __CLASS__, __METHOD__, gettype($path))
+ );
+ }
+ $this->paths[] = array('type' => $type, 'path' => $path);
+ return $this;
+ }
+
+ /**
+ * Add an array of glob paths of config files to merge after loading modules
+ *
+ * @param mixed $globPaths
+ * @return ConfigListener
+ */
+ public function addConfigGlobPaths($globPaths)
+ {
+ $this->addConfigPaths($globPaths, self::GLOB_PATH);
+ return $this;
+ }
+
+ /**
+ * Add a glob path of config files to merge after loading modules
+ *
+ * @param string $globPath
+ * @return ConfigListener
+ */
+ public function addConfigGlobPath($globPath)
+ {
+ $this->addConfigPath($globPath, self::GLOB_PATH);
+ return $this;
+ }
+
+ /**
+ * Add an array of static paths of config files to merge after loading modules
+ *
+ * @param mixed $staticPaths
+ * @return ConfigListener
+ */
+ public function addConfigStaticPaths($staticPaths)
+ {
+ $this->addConfigPaths($staticPaths, self::STATIC_PATH);
+ return $this;
+ }
+
+ /**
+ * Add a static path of config files to merge after loading modules
+ *
+ * @param string $globPath
+ * @return ConfigListener
+ */
+ public function addConfigStaticPath($staticPath)
+ {
+ $this->addConfigPath($staticPath, self::STATIC_PATH);
+ return $this;
+ }
+
+ /**
+ * Add an array of paths of config files to merge after loading modules
+ *
+ * @param mixed $paths
+ * @return ConfigListener
+ */
+ protected function addConfigPaths($paths, $type)
+ {
+ if ($paths instanceof Traversable) {
+ $paths = ArrayUtils::iteratorToArray($paths);
+ }
+
+ if (!is_array($paths)) {
+ throw new Exception\InvalidArgumentException(
+ sprintf('Argument passed to %::%s() must be an array, '
+ . 'implement the \Traversable interface, or be an '
+ . 'instance of Zend\Config\Config. %s given.',
+ __CLASS__, __METHOD__, gettype($paths))
+ );
+ }
+
+ foreach ($paths as $path) {
+ $this->addConfigPath($path, $type);
+ }
+ }
+
+ /**
+ * Merge all config files matching a glob
+ *
+ * @param mixed $path
+ * @return ConfigListener
+ */
+ protected function mergePath($path)
+ {
+ switch ($path['type']) {
+ case self::STATIC_PATH:
+ $config = ConfigFactory::fromFile($path['path']);
+ break;
+
+ case self::GLOB_PATH:
+ $config = ConfigFactory::fromFiles(Glob::glob($path['path'], Glob::GLOB_BRACE));
+ break;
+ }
+ $this->mergeTraversableConfig($config);
+ if ($this->getOptions()->getConfigCacheEnabled()) {
+ $this->updateCache();
+ }
+ return $this;
+ }
+
+ /**
+ * mergeModuleConfig
+ *
+ * @param mixed $module
+ * @return ConfigListener
+ */
+ protected function mergeModuleConfig($module)
+ {
+ if (false !== $this->skipConfig
+ || (!$module instanceof ConfigProviderInterface
+ && !is_callable(array($module, 'getConfig')))
+ ) {
+ return $this;
+ }
+
+ $config = $module->getConfig();
+ try {
+ $this->mergeTraversableConfig($config);
+ } catch (Exception\InvalidArgumentException $e) {
+ // Throw a more descriptive exception
+ throw new Exception\InvalidArgumentException(
+ sprintf('getConfig() method of %s must be an array, '
+ . 'implement the \Traversable interface, or be an '
+ . 'instance of Zend\Config\Config. %s given.',
+ get_class($module), gettype($config))
+ );
+ }
+
+ if ($this->getOptions()->getConfigCacheEnabled()) {
+ $this->updateCache();
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param $config
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ protected function mergeTraversableConfig($config)
+ {
+ if ($config instanceof Traversable) {
+ $config = ArrayUtils::iteratorToArray($config);
+ }
+ if (!is_array($config)) {
+ throw new Exception\InvalidArgumentException(
+ sprintf('Config being merged must be an array, '
+ . 'implement the \Traversable interface, or be an '
+ . 'instance of Zend\Config\Config. %s given.', gettype($config))
+ );
+ }
+ $this->setMergedConfig(ArrayUtils::merge($this->mergedConfig, $config));
+ }
+
+ /**
+ * @return bool
+ */
+ protected function hasCachedConfig()
+ {
+ if (($this->getOptions()->getConfigCacheEnabled())
+ && (file_exists($this->getOptions()->getConfigCacheFile()))
+ ) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @return mixed
+ */
+ protected function getCachedConfig()
+ {
+ return include $this->getOptions()->getConfigCacheFile();
+ }
+
+ /**
+ * @return ConfigListener
+ */
+ protected function updateCache()
+ {
+ if (($this->getOptions()->getConfigCacheEnabled())
+ && (false === $this->skipConfig)
+ ) {
+ $configFile = $this->getOptions()->getConfigCacheFile();
+ $this->writeArrayToFile($configFile, $this->getMergedConfig(false));
+ }
+ return $this;
+ }
+}
diff --git a/src/Listener/ConfigMergerInterface.php b/src/Listener/ConfigMergerInterface.php
new file mode 100644
index 0000000..ec14619
--- /dev/null
+++ b/src/Listener/ConfigMergerInterface.php
@@ -0,0 +1,37 @@
+getOptions();
+ $configListener = $this->getConfigListener();
+ $locatorRegistrationListener = new LocatorRegistrationListener($options);
+ $moduleAutoloader = new ModuleAutoloader($options->getModulePaths());
+
+ $this->listeners[] = $events->attach('loadModules.pre', array($moduleAutoloader, 'register'), 1000);
+ $this->listeners[] = $events->attach('loadModule.resolve', new ModuleResolverListener, 1000);
+ $this->listeners[] = $events->attach('loadModule', new AutoloaderListener($options), 2000);
+ $this->listeners[] = $events->attach('loadModule', new InitTrigger($options), 1000);
+ $this->listeners[] = $events->attach('loadModule', new OnBootstrapListener($options), 1000);
+ $this->listeners[] = $events->attach($locatorRegistrationListener);
+ $this->listeners[] = $events->attach($configListener);
+ return $this;
+ }
+
+ /**
+ * Detach all previously attached listeners
+ *
+ * @param EventManagerInterface $events
+ * @return void
+ */
+ public function detach(EventManagerInterface $events)
+ {
+ foreach ($this->listeners as $key => $listener) {
+ $detached = false;
+ if ($listener === $this) {
+ continue;
+ }
+ if ($listener instanceof ListenerAggregateInterface) {
+ $detached = $listener->detach($events);
+ } elseif ($listener instanceof CallbackHandler) {
+ $detached = $events->detach($listener);
+ }
+
+ if ($detached) {
+ unset($this->listeners[$key]);
+ }
+ }
+ }
+
+ /**
+ * Get the config merger.
+ *
+ * @return ConfigMergerInterface
+ */
+ public function getConfigListener()
+ {
+ if (!$this->configListener instanceof ConfigMergerInterface) {
+ $this->setConfigListener(new ConfigListener($this->getOptions()));
+ }
+ return $this->configListener;
+ }
+
+ /**
+ * Set the config merger to use.
+ *
+ * @param ConfigMergerInterface $configListener
+ * @return DefaultListenerAggregate
+ */
+ public function setConfigListener(ConfigMergerInterface $configListener)
+ {
+ $this->configListener = $configListener;
+ return $this;
+ }
+}
diff --git a/src/Listener/Exception/ExceptionInterface.php b/src/Listener/Exception/ExceptionInterface.php
new file mode 100644
index 0000000..dc52766
--- /dev/null
+++ b/src/Listener/Exception/ExceptionInterface.php
@@ -0,0 +1,23 @@
+getModule();
+ if (!$module instanceof InitProviderInterface
+ && !method_exists($module, 'init')
+ ) {
+ return;
+ }
+
+ $module->init($e->getTarget());
+ }
+}
diff --git a/src/Listener/ListenerOptions.php b/src/Listener/ListenerOptions.php
new file mode 100644
index 0000000..bf518bb
--- /dev/null
+++ b/src/Listener/ListenerOptions.php
@@ -0,0 +1,238 @@
+modulePaths;
+ }
+
+ /**
+ * Set an array of paths where modules reside
+ *
+ * @param array|Traversable $modulePaths
+ * @return ListenerOptions
+ */
+ public function setModulePaths($modulePaths)
+ {
+ if (!is_array($modulePaths) && !$modulePaths instanceof Traversable) {
+ throw new Exception\InvalidArgumentException(
+ sprintf('Argument passed to %s::%s() must be an array, '
+ . 'implement the \Traversable interface, or be an '
+ . 'instance of Zend\Config\Config. %s given.',
+ __CLASS__, __METHOD__, gettype($modulePaths))
+ );
+ }
+ $this->modulePaths = $modulePaths;
+ return $this;
+ }
+
+ /**
+ * Get the glob patterns to load additional config files
+ *
+ * @return array
+ */
+ public function getConfigGlobPaths()
+ {
+ return $this->configGlobPaths;
+ }
+
+ /**
+ * Get the static paths to load additional config files
+ *
+ * @return array
+ */
+ public function getConfigStaticPaths()
+ {
+ return $this->configStaticPaths;
+ }
+
+ /**
+ * Set the glob patterns to use for loading additional config files
+ *
+ * @param array $configGlobPaths
+ * @return ListenerOptions
+ */
+ public function setConfigGlobPaths($configGlobPaths)
+ {
+ if (!is_array($configGlobPaths) && !$configGlobPaths instanceof Traversable) {
+ throw new Exception\InvalidArgumentException(
+ sprintf('Argument passed to %s::%s() must be an array, '
+ . 'implement the \Traversable interface, or be an '
+ . 'instance of Zend\Config\Config. %s given.',
+ __CLASS__, __METHOD__, gettype($configGlobPaths))
+ );
+ }
+ $this->configGlobPaths = $configGlobPaths;
+ }
+
+ /**
+ * Set the static paths to use for loading additional config files
+ *
+ * @param array $configStaticPaths
+ * @return ListenerOptions
+ */
+ public function setConfigStaticPaths($configStaticPaths)
+ {
+ if (!is_array($configStaticPaths) && !$configStaticPaths instanceof Traversable) {
+ throw new Exception\InvalidArgumentException(
+ sprintf('Argument passed to %s::%s() must be an array, '
+ . 'implement the \Traversable interface, or be an '
+ . 'instance of Zend\Config\Config. %s given.',
+ __CLASS__, __METHOD__, gettype($configStaticPaths))
+ );
+ }
+ $this->configStaticPaths = $configStaticPaths;
+ }
+
+ /**
+ * Check if the config cache is enabled
+ *
+ * @return bool
+ */
+ public function getConfigCacheEnabled()
+ {
+ return $this->configCacheEnabled;
+ }
+
+ /**
+ * Set if the config cache should be enabled or not
+ *
+ * @param bool $enabled
+ * @return ListenerOptions
+ */
+ public function setConfigCacheEnabled($enabled)
+ {
+ $this->configCacheEnabled = (bool) $enabled;
+ return $this;
+ }
+
+ /**
+ * Get key used to create the cache file name
+ *
+ * @return string
+ */
+ public function getConfigCacheKey()
+ {
+ return (string) $this->configCacheKey;
+ }
+
+ /**
+ * Set key used to create the cache file name
+ *
+ * @param string $configCacheKey the value to be set
+ * @return ListenerOptions
+ */
+ public function setConfigCacheKey($configCacheKey)
+ {
+ $this->configCacheKey = $configCacheKey;
+ return $this;
+ }
+
+ /**
+ * Get the path to the config cache
+ *
+ * Should this be an option, or should the dir option include the
+ * filename, or should it simply remain hard-coded? Thoughts?
+ *
+ * @return string
+ */
+ public function getConfigCacheFile()
+ {
+ return $this->getCacheDir() . '/module-config-cache.'.$this->getConfigCacheKey().'.php';
+ }
+
+ /**
+ * Get the path where cache file(s) are stored
+ *
+ * @return string
+ */
+ public function getCacheDir()
+ {
+ return $this->cacheDir;
+ }
+
+ /**
+ * Set the path where cache files can be stored
+ *
+ * @param string $cacheDir the value to be set
+ * @return ListenerOptions
+ */
+ public function setCacheDir($cacheDir)
+ {
+ if (null === $cacheDir) {
+ $this->cacheDir = $cacheDir;
+ } else {
+ $this->cacheDir = static::normalizePath($cacheDir);
+ }
+ return $this;
+ }
+
+ /**
+ * Normalize a path for insertion in the stack
+ *
+ * @param string $path
+ * @return string
+ */
+ public static function normalizePath($path)
+ {
+ $path = rtrim($path, '/');
+ $path = rtrim($path, '\\');
+ return $path;
+ }
+}
diff --git a/src/Listener/LocatorRegistrationListener.php b/src/Listener/LocatorRegistrationListener.php
new file mode 100644
index 0000000..65d576a
--- /dev/null
+++ b/src/Listener/LocatorRegistrationListener.php
@@ -0,0 +1,138 @@
+getModule() instanceof LocatorRegisteredInterface) {
+ return;
+ }
+ $this->modules[] = $e->getModule();
+ }
+
+ /**
+ * loadModulesPost
+ *
+ * Once all the modules are loaded, loop
+ *
+ * @param Event $e
+ * @return void
+ */
+ public function loadModulesPost(Event $e)
+ {
+ $moduleManager = $e->getTarget();
+ $events = $moduleManager->events()->getSharedManager();
+
+ // Shared instance for module manager
+ $events->attach('application', 'bootstrap', function ($e) use ($moduleManager) {
+ $moduleClassName = get_class($moduleManager);
+ $application = $e->getApplication();
+ $services = $application->getServiceManager();
+ if (!$services->has($moduleClassName)) {
+ $services->setService($moduleClassName, $moduleManager);
+ }
+ }, 1000);
+
+ if (0 === count($this->modules)) {
+ return;
+ }
+
+ // Attach to the bootstrap event if there are modules we need to process
+ $events->attach('application', 'bootstrap', array($this, 'onBootstrap'), 1000);
+ }
+
+ /**
+ * Bootstrap listener
+ *
+ * This is ran during the MVC bootstrap event because it requires access to
+ * the DI container.
+ *
+ * @TODO: Check the application / locator / etc a bit better to make sure
+ * the env looks how we're expecting it to?
+ * @param Event $e
+ * @return void
+ */
+ public function onBootstrap(Event $e)
+ {
+ $application = $e->getApplication();
+ $services = $application->getServiceManager();
+
+ foreach ($this->modules as $module) {
+ $moduleClassName = get_class($module);
+ if (!$services->has($moduleClassName)) {
+ $services->setService($moduleClassName, $module);
+ }
+ }
+ }
+
+ /**
+ * Attach one or more listeners
+ *
+ * @param EventManagerInterface $events
+ * @return void
+ */
+ public function attach(EventManagerInterface $events)
+ {
+ $this->listeners[] = $events->attach('loadModule', array($this, 'loadModule'), 1000);
+ $this->listeners[] = $events->attach('loadModules.post', array($this, 'loadModulesPost'), 9000);
+ return $this;
+ }
+
+ /**
+ * Detach all previously attached listeners
+ *
+ * @param EventManagerInterface $events
+ * @return void
+ */
+ public function detach(EventManagerInterface $events)
+ {
+ foreach ($this->listeners as $key => $listener) {
+ if ($events->detach($listener)) {
+ unset($this->listeners[$key]);
+ }
+ }
+ }
+}
diff --git a/src/Listener/ModuleResolverListener.php b/src/Listener/ModuleResolverListener.php
new file mode 100644
index 0000000..5093f30
--- /dev/null
+++ b/src/Listener/ModuleResolverListener.php
@@ -0,0 +1,38 @@
+getModuleName();
+ $class = $moduleName . '\Module';
+
+ if (!class_exists($class)) {
+ return false;
+ }
+
+ $module = new $class;
+ return $module;
+ }
+}
diff --git a/src/Listener/OnBootstrapListener.php b/src/Listener/OnBootstrapListener.php
new file mode 100644
index 0000000..087c2f5
--- /dev/null
+++ b/src/Listener/OnBootstrapListener.php
@@ -0,0 +1,44 @@
+getModule();
+ if (!$module instanceof BootstrapListenerInterface
+ && !method_exists($module, 'onBootstrap')
+ ) {
+ return;
+ }
+
+ $moduleManager = $e->getTarget();
+ $events = $moduleManager->events();
+ $sharedEvents = $events->getSharedManager();
+ $sharedEvents->attach('application', 'bootstrap', array($module, 'onBootstrap'));
+ }
+}
diff --git a/src/Listener/ServiceListener.php b/src/Listener/ServiceListener.php
new file mode 100644
index 0000000..72f8cff
--- /dev/null
+++ b/src/Listener/ServiceListener.php
@@ -0,0 +1,208 @@
+ array(),
+ 'aliases' => array(),
+ 'factories' => array(),
+ 'invokables' => array(),
+ 'services' => array(),
+ 'shared' => array(),
+ );
+
+ /**
+ * @var ServiceLocatorInterface
+ */
+ protected $services;
+
+ /**
+ * @param ServiceLocatorInterface $services
+ * @return void
+ */
+ public function __construct(ServiceLocatorInterface $services)
+ {
+ $this->services = $services;
+ }
+
+ /**
+ * @param EventManagerInterface $events
+ * @return void
+ */
+ public function attach(EventManagerInterface $events)
+ {
+ $this->listeners[] = $events->attach('loadModule', array($this, 'onLoadModule'), 1500);
+ $this->listeners[] = $events->attach('loadModules.post', array($this, 'onLoadModulesPost'), 8500);
+ return $this;
+ }
+
+ /**
+ * @param EventManagerInterface $events
+ * @return void
+ */
+ public function detach(EventManagerInterface $events)
+ {
+ foreach ($this->listeners as $key => $listener) {
+ if ($events->detach($listener)) {
+ unset($this->listeners[$key]);
+ }
+ }
+ }
+
+ /**
+ * Retrieve service manager configuration from module, and
+ * configure the service manager.
+ *
+ * If the module does not implement ServiceProviderInterface and does not
+ * implement the "getServiceConfiguration()" method, does nothing. Also,
+ * if the return value of that method is not a ServiceConfiguration object,
+ * or not an array or Traversable that can seed one, does nothing.
+ *
+ * @param ModuleEvent $e
+ * @return void
+ */
+ public function onLoadModule(ModuleEvent $e)
+ {
+ $module = $e->getModule();
+ if (!$module instanceof ServiceProviderInterface
+ && !method_exists($module, 'getServiceConfiguration')
+ ) {
+ return;
+ }
+
+ $config = $module->getServiceConfiguration();
+
+ if ($config instanceof ServiceConfiguration) {
+ $this->mergeServiceConfiguration($config);
+ return;
+ }
+
+ if ($config instanceof Traversable) {
+ $config = ArrayUtils::iteratorToArray($config);
+ }
+
+ if (!is_array($config)) {
+ // If we don't have an array by this point, nothing left to do.
+ return;
+ }
+
+ $this->serviceConfig = ArrayUtils::merge($this->serviceConfig, $config);
+ }
+
+ /**
+ * Use merged configuration to configure service manager
+ *
+ * If the merged configuration has a non-empty, array 'service_manager'
+ * key, it will be passed to a ServiceManager Configuration object, and
+ * used to configure the service manager.
+ *
+ * @param ModuleEvent $e
+ * @return void
+ */
+ public function onLoadModulesPost(ModuleEvent $e)
+ {
+ $configListener = $e->getConfigListener();
+ $config = $configListener->getMergedConfig(false);
+ if (isset($config['service_manager'])
+ && is_array($config['service_manager'])
+ && !empty($config['service_manager'])
+ ) {
+ $this->serviceConfig = ArrayUtils::merge($this->serviceConfig, $config['service_manager']);
+ }
+
+ $this->configureServiceManager();
+ }
+
+ /**
+ * Configure the service manager
+ *
+ * Configures the service manager based on the internal, merged
+ * service configuration.
+ *
+ * @return void
+ */
+ public function configureServiceManager()
+ {
+ if ($this->configured) {
+ // Don't configure twice
+ return;
+ }
+ $serviceConfig = new ServiceConfiguration($this->serviceConfig);
+ $serviceConfig->configureServiceManager($this->services);
+ $this->configured = true;
+ }
+
+ /**
+ * Merge a service configuration container
+ *
+ * Extracts the various service configuration arrays, and then merges with
+ * the internal service configuration.
+ *
+ * @param ServiceConfiguration $config
+ * @return void
+ */
+ protected function mergeServiceConfiguration(ServiceConfiguration $config)
+ {
+ $serviceConfig = array(
+ 'abstract_factories' => $config->getAbstractFactories(),
+ 'aliases' => $config->getAliases(),
+ 'factories' => $config->getFactories(),
+ 'invokables' => $config->getInvokables(),
+ 'services' => $config->getServices(),
+ 'shared' => $config->getShared(),
+ );
+ $this->serviceConfig = ArrayUtils::merge($this->serviceConfig, $serviceConfig);
+ }
+}
diff --git a/src/ModuleEvent.php b/src/ModuleEvent.php
new file mode 100644
index 0000000..deb1c0e
--- /dev/null
+++ b/src/ModuleEvent.php
@@ -0,0 +1,101 @@
+getParam('moduleName');
+ }
+
+ /**
+ * Set the name of a given module
+ *
+ * @param string $moduleName
+ * @return ModuleEvent
+ */
+ public function setModuleName($moduleName)
+ {
+ if (!is_string($moduleName)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s expects a string as an argument; %s provided'
+ ,__METHOD__, gettype($moduleName)
+ ));
+ }
+ $this->setParam('moduleName', $moduleName);
+ return $this;
+ }
+
+ /**
+ * Get module object
+ *
+ * @return null|object
+ */
+ public function getModule()
+ {
+ return $this->getParam('module');
+ }
+
+ /**
+ * Set module object to compose in this event
+ *
+ * @param object $module
+ * @return ModuleEvent
+ */
+ public function setModule($module)
+ {
+ if (!is_object($module)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s expects a module object as an argument; %s provided'
+ ,__METHOD__, gettype($module)
+ ));
+ }
+ $this->setParam('module', $module);
+ return $this;
+ }
+
+ /**
+ * Get the config listner
+ *
+ * @return null|Listener\ConfigMergerInterface
+ */
+ public function getConfigListener()
+ {
+ return $this->getParam('configListener');
+ }
+
+ /**
+ * Set module object to compose in this event
+ *
+ * @param Listener\ConfigMergerInterface $configListener
+ * @return ModuleEvent
+ */
+ public function setConfigListener(Listener\ConfigMergerInterface $configListener)
+ {
+ $this->setParam('configListener', $configListener);
+ return $this;
+ }
+}
diff --git a/src/ModuleManager.php b/src/ModuleManager.php
new file mode 100644
index 0000000..2f70f5e
--- /dev/null
+++ b/src/ModuleManager.php
@@ -0,0 +1,243 @@
+setModules($modules);
+ if ($eventManager instanceof EventManagerInterface) {
+ $this->setEventManager($eventManager);
+ }
+ }
+
+ /**
+ * Load the provided modules.
+ *
+ * @triggers loadModules.pre
+ * @triggers loadModules.post
+ * @return ModuleManager
+ */
+ public function loadModules()
+ {
+ if (true === $this->modulesAreLoaded) {
+ return $this;
+ }
+
+ $this->events()->trigger(__FUNCTION__ . '.pre', $this, $this->getEvent());
+
+ foreach ($this->getModules() as $moduleName) {
+ $this->loadModule($moduleName);
+ }
+
+ $this->events()->trigger(__FUNCTION__ . '.post', $this, $this->getEvent());
+
+ $this->modulesAreLoaded = true;
+ return $this;
+ }
+
+ /**
+ * Load a specific module by name.
+ *
+ * @param string $moduleName
+ * @triggers loadModule.resolve
+ * @triggers loadModule
+ * @return mixed Module's Module class
+ */
+ public function loadModule($moduleName)
+ {
+ if (isset($this->loadedModules[$moduleName])) {
+ return $this->loadedModules[$moduleName];
+ }
+
+ $event = $this->getEvent();
+ $event->setModuleName($moduleName);
+
+ $result = $this->events()->trigger(__FUNCTION__ . '.resolve', $this, $event, function ($r) {
+ return (is_object($r));
+ });
+
+ $module = $result->last();
+
+ if (!is_object($module)) {
+ throw new Exception\RuntimeException(sprintf(
+ 'Module (%s) could not be initialized.',
+ $moduleName
+ ));
+ }
+ $event->setModule($module);
+
+ $this->events()->trigger(__FUNCTION__, $this, $event);
+ $this->loadedModules[$moduleName] = $module;
+ return $module;
+ }
+
+ /**
+ * Get an array of the loaded modules.
+ *
+ * @param bool $loadModules If true, load modules if they're not already
+ * @return array An array of Module objects, keyed by module name
+ */
+ public function getLoadedModules($loadModules = false)
+ {
+ if (true === $loadModules) {
+ $this->loadModules();
+ }
+ return $this->loadedModules;
+ }
+
+ /**
+ * Get an instance of a module class by the module name
+ *
+ * @param string $moduleName
+ * @return mixed
+ */
+ public function getModule($moduleName)
+ {
+ if (!isset($this->loadedModules[$moduleName])) {
+ return null;
+ }
+ return $this->loadedModules[$moduleName];
+ }
+
+ /**
+ * Get the array of module names that this manager should load.
+ *
+ * @return array
+ */
+ public function getModules()
+ {
+ return $this->modules;
+ }
+
+ /**
+ * Set an array or Traversable of module names that this module manager should load.
+ *
+ * @param mixed $modules array or Traversable of module names
+ * @return ModuleManager
+ */
+ public function setModules($modules)
+ {
+ if (is_array($modules) || $modules instanceof Traversable) {
+ $this->modules = $modules;
+ } else {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Parameter to %s\'s %s method must be an array or implement the \\Traversable interface',
+ __CLASS__, __METHOD__
+ ));
+ }
+ return $this;
+ }
+
+ /**
+ * Get the module event
+ *
+ * @return ModuleEvent
+ */
+ public function getEvent()
+ {
+ if (!$this->event instanceof ModuleEvent) {
+ $this->setEvent(new ModuleEvent);
+ }
+ return $this->event;
+ }
+
+ /**
+ * Set the module event
+ *
+ * @param ModuleEvent $event
+ * @return ModuleManager
+ */
+ public function setEvent(ModuleEvent $event)
+ {
+ $this->event = $event;
+ return $this;
+ }
+
+ /**
+ * Set the event manager instance used by this module manager.
+ *
+ * @param EventManagerInterface $events
+ * @return ModuleManager
+ */
+ public function setEventManager(EventManagerInterface $events)
+ {
+ $events->setIdentifiers(array(
+ __CLASS__,
+ get_called_class(),
+ 'module_manager',
+ ));
+ $this->events = $events;
+ return $this;
+ }
+
+ /**
+ * Retrieve the event manager
+ *
+ * Lazy-loads an EventManager instance if none registered.
+ *
+ * @return EventManagerInterface
+ */
+ public function events()
+ {
+ if (!$this->events instanceof EventManagerInterface) {
+ $this->setEventManager(new EventManager());
+ }
+ return $this->events;
+ }
+}
diff --git a/src/ModuleManagerInterface.php b/src/ModuleManagerInterface.php
new file mode 100644
index 0000000..a041d34
--- /dev/null
+++ b/src/ModuleManagerInterface.php
@@ -0,0 +1,61 @@
+loaders = spl_autoload_functions();
+ if (!is_array($this->loaders)) {
+ // spl_autoload_functions does not return empty array when no
+ // autoloaders registered...
+ $this->loaders = array();
+ }
+
+ // Store original include_path
+ $this->includePath = get_include_path();
+
+ $autoloader = new ModuleAutoloader(array(
+ dirname(__DIR__) . '/TestAsset',
+ ));
+ $autoloader->register();
+
+ $this->moduleManager = new ModuleManager(array());
+ $this->moduleManager->events()->attach('loadModule.resolve', new ModuleResolverListener, 1000);
+ $this->moduleManager->events()->attach('loadModule', new AutoloaderListener, 2000);
+ }
+
+ public function tearDown()
+ {
+ // Restore original autoloaders
+ AutoloaderFactory::unregisterAutoloaders();
+ $loaders = spl_autoload_functions();
+ if (is_array($loaders)) {
+ foreach ($loaders as $loader) {
+ spl_autoload_unregister($loader);
+ }
+ }
+
+ foreach ($this->loaders as $loader) {
+ spl_autoload_register($loader);
+ }
+
+ // Restore original include_path
+ set_include_path($this->includePath);
+ }
+
+ public function testAutoloadersRegisteredByAutoloaderListener()
+ {
+ $moduleManager = $this->moduleManager;
+ $moduleManager->setModules(array('ListenerTestModule'));
+ $moduleManager->loadModules();
+ $modules = $moduleManager->getLoadedModules();
+ $this->assertTrue($modules['ListenerTestModule']->getAutoloaderConfigCalled);
+ $this->assertTrue(class_exists('Foo\Bar'));
+ }
+
+ public function testAutoloadersRegisteredIfModuleDoesNotInheritAutoloaderProviderInterfaceButDefinesGetAutoloaderConfigMethod()
+ {
+ $moduleManager = $this->moduleManager;
+ $moduleManager->setModules(array('NotAutoloaderModule'));
+ $moduleManager->loadModules();
+ $modules = $moduleManager->getLoadedModules();
+ $this->assertTrue($modules['NotAutoloaderModule']->getAutoloaderConfigCalled);
+ $this->assertTrue(class_exists('Foo\Bar'));
+ }
+}
diff --git a/test/Listener/ConfigListenerTest.php b/test/Listener/ConfigListenerTest.php
new file mode 100644
index 0000000..f080c40
--- /dev/null
+++ b/test/Listener/ConfigListenerTest.php
@@ -0,0 +1,397 @@
+tmpdir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'zend_module_cache_dir';
+ @mkdir($this->tmpdir);
+ $this->configCache = $this->tmpdir . DIRECTORY_SEPARATOR . 'config.cache.php';
+ // Store original autoloaders
+ $this->loaders = spl_autoload_functions();
+ if (!is_array($this->loaders)) {
+ // spl_autoload_functions does not return empty array when no
+ // autoloaders registered...
+ $this->loaders = array();
+ }
+
+ // Store original include_path
+ $this->includePath = get_include_path();
+
+ $autoloader = new ModuleAutoloader(array(
+ dirname(__DIR__) . '/TestAsset',
+ ));
+ $autoloader->register();
+
+ $this->moduleManager = new ModuleManager(array());
+ $this->moduleManager->events()->attach('loadModule.resolve', new ModuleResolverListener, 1000);
+ }
+
+ public function tearDown()
+ {
+ $file = glob($this->tmpdir . DIRECTORY_SEPARATOR . '*');
+ @unlink($file[0]); // change this if there's ever > 1 file
+ @rmdir($this->tmpdir);
+ // Restore original autoloaders
+ AutoloaderFactory::unregisterAutoloaders();
+ $loaders = spl_autoload_functions();
+ if (is_array($loaders)) {
+ foreach ($loaders as $loader) {
+ spl_autoload_unregister($loader);
+ }
+ }
+
+ foreach ($this->loaders as $loader) {
+ spl_autoload_register($loader);
+ }
+
+ // Restore original include_path
+ set_include_path($this->includePath);
+ }
+
+ public function testMultipleConfigsAreMerged()
+ {
+ $configListener = new ConfigListener;
+
+ $moduleManager = $this->moduleManager;
+ $moduleManager->events()->attach('loadModule', $configListener);
+ $moduleManager->setModules(array('SomeModule', 'ListenerTestModule'));
+ $moduleManager->loadModules();
+
+ $config = $configListener->getMergedConfig(false);
+ $this->assertSame(2, count($config));
+ $this->assertSame('test', $config['listener']);
+ $this->assertSame('thing', $config['some']);
+ $configObject = $configListener->getMergedConfig();
+ $this->assertInstanceOf('Zend\Config\Config', $configObject);
+ }
+
+ public function testCanCacheMergedConfig()
+ {
+ $options = new ListenerOptions(array(
+ 'cache_dir' => $this->tmpdir,
+ 'config_cache_enabled' => true,
+ ));
+ $configListener = new ConfigListener($options);
+
+ $moduleManager = $this->moduleManager;
+ $moduleManager->setModules(array('SomeModule', 'ListenerTestModule'));
+ $moduleManager->events()->attach('loadModule', $configListener);
+ $moduleManager->loadModules(); // This should cache the config
+
+ $modules = $moduleManager->getLoadedModules();
+ $this->assertTrue($modules['ListenerTestModule']->getConfigCalled);
+
+ // Now we check to make sure it uses the config and doesn't hit
+ // the module objects getConfig() method(s)
+ $moduleManager = new ModuleManager(array('SomeModule', 'ListenerTestModule'));
+ $moduleManager->events()->attach('loadModule.resolve', new ModuleResolverListener, 1000);
+ $configListener = new ConfigListener($options);
+ $moduleManager->events()->attach('loadModule', $configListener);
+ $moduleManager->loadModules();
+ $modules = $moduleManager->getLoadedModules();
+ $this->assertFalse($modules['ListenerTestModule']->getConfigCalled);
+ }
+
+ public function testBadConfigValueThrowsInvalidArgumentException()
+ {
+ $this->setExpectedException('InvalidArgumentException');
+
+ $configListener = new ConfigListener;
+
+ $moduleManager = $this->moduleManager;
+ $moduleManager->setModules(array('BadConfigModule', 'SomeModule'));
+ $moduleManager->events()->attach('loadModule', $configListener);
+ $moduleManager->loadModules();
+ }
+
+ public function testBadGlobPathTrowsInvalidArgumentException()
+ {
+ $this->setExpectedException('InvalidArgumentException');
+ $configListener = new ConfigListener;
+ $configListener->addConfigGlobPath(array('asd'));
+ }
+
+ public function testBadGlobPathArrayTrowsInvalidArgumentException()
+ {
+ $this->setExpectedException('InvalidArgumentException');
+ $configListener = new ConfigListener;
+ $configListener->addConfigGlobPaths('asd');
+ }
+
+ public function testBadStaticPathArrayTrowsInvalidArgumentException()
+ {
+ $this->setExpectedException('InvalidArgumentException');
+ $configListener = new ConfigListener;
+ $configListener->addConfigStaticPaths('asd');
+ }
+
+ public function testCanMergeConfigFromGlob()
+ {
+ $configListener = new ConfigListener;
+ $configListener->addConfigGlobPath(__DIR__ . '/_files/good/*.{ini,php,xml}');
+
+ $moduleManager = $this->moduleManager;
+ $moduleManager->setModules(array('SomeModule'));
+
+ $moduleManager->events()->attachAggregate($configListener);
+
+ $moduleManager->loadModules();
+ $configObjectCheck = $configListener->getMergedConfig();
+
+ // Test as object
+ $configObject = $configListener->getMergedConfig();
+ $this->assertSame(spl_object_hash($configObjectCheck), spl_object_hash($configObject));
+ $this->assertSame('loaded', $configObject->ini);
+ $this->assertSame('loaded', $configObject->php);
+ $this->assertSame('loaded', $configObject->xml);
+ // Test as array
+ $config = $configListener->getMergedConfig(false);
+ $this->assertSame('loaded', $config['ini']);
+ $this->assertSame('loaded', $config['php']);
+ $this->assertSame('loaded', $config['xml']);
+ }
+
+ public function testCanMergeConfigFromStaticPath()
+ {
+ $configListener = new ConfigListener;
+ $configListener->addConfigStaticPath(__DIR__ . '/_files/good/config.ini');
+ $configListener->addConfigStaticPath(__DIR__ . '/_files/good/config.php');
+ $configListener->addConfigStaticPath(__DIR__ . '/_files/good/config.xml');
+
+ $moduleManager = $this->moduleManager;
+ $moduleManager->setModules(array('SomeModule'));
+
+ $moduleManager->events()->attachAggregate($configListener);
+
+ $moduleManager->loadModules();
+ $configObjectCheck = $configListener->getMergedConfig();
+
+ // Test as object
+ $configObject = $configListener->getMergedConfig();
+ $this->assertSame(spl_object_hash($configObjectCheck), spl_object_hash($configObject));
+ $this->assertSame('loaded', $configObject->ini);
+ $this->assertSame('loaded', $configObject->php);
+ $this->assertSame('loaded', $configObject->xml);
+ // Test as array
+ $config = $configListener->getMergedConfig(false);
+ $this->assertSame('loaded', $config['ini']);
+ $this->assertSame('loaded', $config['php']);
+ $this->assertSame('loaded', $config['xml']);
+ }
+
+ public function testCanMergeConfigFromStaticPaths()
+ {
+ $configListener = new ConfigListener;
+ $configListener->addConfigStaticPaths(array(
+ __DIR__ . '/_files/good/config.ini',
+ __DIR__ . '/_files/good/config.php',
+ __DIR__ . '/_files/good/config.xml')
+ );
+
+ $moduleManager = $this->moduleManager;
+ $moduleManager->setModules(array('SomeModule'));
+
+ $moduleManager->events()->attachAggregate($configListener);
+
+ $moduleManager->loadModules();
+ $configObjectCheck = $configListener->getMergedConfig();
+
+ // Test as object
+ $configObject = $configListener->getMergedConfig();
+ $this->assertSame(spl_object_hash($configObjectCheck), spl_object_hash($configObject));
+ $this->assertSame('loaded', $configObject->ini);
+ $this->assertSame('loaded', $configObject->php);
+ $this->assertSame('loaded', $configObject->xml);
+ // Test as array
+ $config = $configListener->getMergedConfig(false);
+ $this->assertSame('loaded', $config['ini']);
+ $this->assertSame('loaded', $config['php']);
+ $this->assertSame('loaded', $config['xml']);
+ }
+
+ public function testCanCacheMergedConfigFromGlob()
+ {
+ $options = new ListenerOptions(array(
+ 'cache_dir' => $this->tmpdir,
+ 'config_cache_enabled' => true,
+ ));
+ $configListener = new ConfigListener($options);
+ $configListener->addConfigGlobPath(__DIR__ . '/_files/good/*.{ini,php,xml}');
+
+ $moduleManager = $this->moduleManager;
+ $moduleManager->setModules(array('SomeModule'));
+
+ $moduleManager->events()->attachAggregate($configListener);
+
+ $moduleManager->loadModules();
+ $configObjectFromGlob = $configListener->getMergedConfig();
+
+ // This time, don't add the glob path
+ $configListener = new ConfigListener($options);
+ $moduleManager = new ModuleManager(array('SomeModule'));
+ $moduleManager->events()->attach('loadModule.resolve', new ModuleResolverListener, 1000);
+
+ $moduleManager->events()->attachAggregate($configListener);
+
+ $moduleManager->loadModules();
+
+ // Check if values from glob object and cache object are the same
+ $configObjectFromCache = $configListener->getMergedConfig();
+ $this->assertNotNull($configObjectFromGlob->ini);
+ $this->assertSame($configObjectFromGlob->ini, $configObjectFromCache->ini);
+ $this->assertNotNull($configObjectFromGlob->php);
+ $this->assertSame($configObjectFromGlob->php, $configObjectFromCache->php);
+ $this->assertNotNull($configObjectFromGlob->xml);
+ $this->assertSame($configObjectFromGlob->xml, $configObjectFromCache->xml);
+ }
+
+ public function testCanCacheMergedConfigFromStatic()
+ {
+ $options = new ListenerOptions(array(
+ 'cache_dir' => $this->tmpdir,
+ 'config_cache_enabled' => true,
+ ));
+ $configListener = new ConfigListener($options);
+ $configListener->addConfigStaticPaths(array(
+ __DIR__ . '/_files/good/config.ini',
+ __DIR__ . '/_files/good/config.php',
+ __DIR__ . '/_files/good/config.xml')
+ );
+
+ $moduleManager = $this->moduleManager;
+ $moduleManager->setModules(array('SomeModule'));
+
+ $moduleManager->events()->attachAggregate($configListener);
+
+ $moduleManager->loadModules();
+ $configObjectFromGlob = $configListener->getMergedConfig();
+
+ // This time, don't add the glob path
+ $configListener = new ConfigListener($options);
+ $moduleManager = new ModuleManager(array('SomeModule'));
+ $moduleManager->events()->attach('loadModule.resolve', new ModuleResolverListener, 1000);
+
+ $moduleManager->events()->attachAggregate($configListener);
+
+ $moduleManager->loadModules();
+
+ // Check if values from glob object and cache object are the same
+ $configObjectFromCache = $configListener->getMergedConfig();
+ $this->assertNotNull($configObjectFromGlob->ini);
+ $this->assertSame($configObjectFromGlob->ini, $configObjectFromCache->ini);
+ $this->assertNotNull($configObjectFromGlob->php);
+ $this->assertSame($configObjectFromGlob->php, $configObjectFromCache->php);
+ $this->assertNotNull($configObjectFromGlob->xml);
+ $this->assertSame($configObjectFromGlob->xml, $configObjectFromCache->xml);
+ }
+
+ public function testCanMergeConfigFromArrayOfGlobs()
+ {
+ $configListener = new ConfigListener;
+ $configListener->addConfigGlobPaths(new ArrayObject(array(
+ __DIR__ . '/_files/good/*.ini',
+ __DIR__ . '/_files/good/*.php',
+ __DIR__ . '/_files/good/*.xml',
+ )));
+
+ $moduleManager = $this->moduleManager;
+ $moduleManager->setModules(array('SomeModule'));
+
+ $moduleManager->events()->attachAggregate($configListener);
+ $moduleManager->loadModules();
+
+ // Test as object
+ $configObject = $configListener->getMergedConfig();
+ $this->assertSame('loaded', $configObject->ini);
+ $this->assertSame('loaded', $configObject->php);
+ $this->assertSame('loaded', $configObject->xml);
+ }
+
+ public function testCanMergeConfigFromArrayOfStatic()
+ {
+ $configListener = new ConfigListener;
+ $configListener->addConfigStaticPaths(new ArrayObject(array(
+ __DIR__ . '/_files/good/config.ini',
+ __DIR__ . '/_files/good/config.php',
+ __DIR__ . '/_files/good/config.xml',
+ )));
+
+ $moduleManager = $this->moduleManager;
+ $moduleManager->setModules(array('SomeModule'));
+
+ $moduleManager->events()->attachAggregate($configListener);
+ $moduleManager->loadModules();
+
+ // Test as object
+ $configObject = $configListener->getMergedConfig();
+ $this->assertSame('loaded', $configObject->ini);
+ $this->assertSame('loaded', $configObject->php);
+ $this->assertSame('loaded', $configObject->xml);
+ }
+
+ public function testMergesWithMergeAndReplaceBehavior()
+ {
+ $configListener = new ConfigListener();
+
+ $moduleManager = $this->moduleManager;
+ $moduleManager->setModules(array('SomeModule'));
+
+ $configListener->addConfigStaticPaths(array(
+ __DIR__ . '/_files/good/merge1.php',
+ __DIR__ . '/_files/good/merge2.php',
+ ));
+
+ $moduleManager->events()->attachAggregate($configListener);
+ $moduleManager->loadModules();
+
+ $mergedConfig = $configListener->getMergedConfig(false);
+ $this->assertSame(array('foo', 'bar'), $mergedConfig['indexed']);
+ $this->assertSame('bar', $mergedConfig['keyed']);
+ }
+
+ public function testConfigListenerFunctionsAsAggregateListener()
+ {
+ $configListener = new ConfigListener;
+
+ $moduleManager = $this->moduleManager;
+ $this->assertEquals(1, count($moduleManager->events()->getEvents()));
+
+ $configListener->attach($moduleManager->events());
+ $this->assertEquals(4, count($moduleManager->events()->getEvents()));
+
+ $configListener->detach($moduleManager->events());
+ $this->assertEquals(1, count($moduleManager->events()->getEvents()));
+ }
+}
diff --git a/test/Listener/DefaultListenerAggregateTest.php b/test/Listener/DefaultListenerAggregateTest.php
new file mode 100644
index 0000000..4e7a507
--- /dev/null
+++ b/test/Listener/DefaultListenerAggregateTest.php
@@ -0,0 +1,128 @@
+loaders = spl_autoload_functions();
+ if (!is_array($this->loaders)) {
+ // spl_autoload_functions does not return empty array when no
+ // autoloaders registered...
+ $this->loaders = array();
+ }
+
+ // Store original include_path
+ $this->includePath = get_include_path();
+
+ $this->defaultListeners = new DefaultListenerAggregate(
+ new ListenerOptions(array(
+ 'module_paths' => array(
+ realpath(__DIR__ . '/TestAsset'),
+ ),
+ ))
+ );
+ }
+
+ public function tearDown()
+ {
+ // Restore original autoloaders
+ AutoloaderFactory::unregisterAutoloaders();
+ $loaders = spl_autoload_functions();
+ if (is_array($loaders)) {
+ foreach ($loaders as $loader) {
+ spl_autoload_unregister($loader);
+ }
+ }
+
+ foreach ($this->loaders as $loader) {
+ spl_autoload_register($loader);
+ }
+
+ // Restore original include_path
+ set_include_path($this->includePath);
+ }
+
+ public function testDefaultListenerAggregateCanAttachItself()
+ {
+ $moduleManager = new ModuleManager(array('ListenerTestModule'));
+ $moduleManager->events()->attachAggregate(new DefaultListenerAggregate);
+
+ $events = $moduleManager->events()->getEvents();
+ $expectedEvents = array(
+ 'loadModules.pre' => array(
+ 'Zend\Loader\ModuleAutoloader',
+ 'Zend\ModuleManager\Listener\ConfigListener',
+ ),
+ 'loadModule.resolve' => array(
+ 'Zend\ModuleManager\Listener\ModuleResolverListener',
+ ),
+ 'loadModule' => array(
+ 'Zend\ModuleManager\Listener\AutoloaderListener',
+ 'Zend\ModuleManager\Listener\InitTrigger',
+ 'Zend\ModuleManager\Listener\OnBootstrapListener',
+ 'Zend\ModuleManager\Listener\ConfigListener',
+ 'Zend\ModuleManager\Listener\LocatorRegistrationListener',
+ ),
+ 'loadModules.post' => array(
+ 'Zend\ModuleManager\Listener\ConfigListener',
+ 'Zend\ModuleManager\Listener\LocatorRegistrationListener',
+ ),
+ );
+ foreach ($expectedEvents as $event => $expectedListeners) {
+ $this->assertContains($event, $events);
+ $listeners = $moduleManager->events()->getListeners($event);
+ $this->assertSame(count($expectedListeners), count($listeners));
+ foreach ($listeners as $listener) {
+ $callback = $listener->getCallback();
+ if (is_array($callback)) {
+ $callback = $callback[0];
+ }
+ $listenerClass = get_class($callback);
+ $this->assertContains($listenerClass, $expectedListeners);
+ }
+ }
+ }
+
+ public function testDefaultListenerAggregateCanDetachItself()
+ {
+ $listenerAggregate = new DefaultListenerAggregate;
+ $moduleManager = new ModuleManager(array('ListenerTestModule'));
+
+ $listenerAggregate->attach($moduleManager->events());
+ $this->assertEquals(4, count($moduleManager->events()->getEvents()));
+
+ $listenerAggregate->detach($moduleManager->events());
+ $this->assertEquals(0, count($moduleManager->events()->getEvents()));
+ }
+}
diff --git a/test/Listener/InitTriggerTest.php b/test/Listener/InitTriggerTest.php
new file mode 100644
index 0000000..41af809
--- /dev/null
+++ b/test/Listener/InitTriggerTest.php
@@ -0,0 +1,83 @@
+loaders = spl_autoload_functions();
+ if (!is_array($this->loaders)) {
+ // spl_autoload_functions does not return empty array when no
+ // autoloaders registered...
+ $this->loaders = array();
+ }
+
+ // Store original include_path
+ $this->includePath = get_include_path();
+
+ $autoloader = new ModuleAutoloader(array(
+ dirname(__DIR__) . '/TestAsset',
+ ));
+ $autoloader->register();
+
+ $this->moduleManager = new ModuleManager(array());
+ $this->moduleManager->events()->attach('loadModule.resolve', new ModuleResolverListener, 1000);
+ $this->moduleManager->events()->attach('loadModule', new InitTrigger, 2000);
+ }
+
+ public function tearDown()
+ {
+ // Restore original autoloaders
+ AutoloaderFactory::unregisterAutoloaders();
+ $loaders = spl_autoload_functions();
+ if (is_array($loaders)) {
+ foreach ($loaders as $loader) {
+ spl_autoload_unregister($loader);
+ }
+ }
+
+ foreach ($this->loaders as $loader) {
+ spl_autoload_register($loader);
+ }
+
+ // Restore original include_path
+ set_include_path($this->includePath);
+ }
+
+ public function testInitMethodCalledByInitTriggerListener()
+ {
+ $moduleManager = $this->moduleManager;
+ $moduleManager->setModules(array('ListenerTestModule'));
+ $moduleManager->loadModules();
+ $modules = $moduleManager->getLoadedModules();
+ $this->assertTrue($modules['ListenerTestModule']->initCalled);
+ }
+}
diff --git a/test/Listener/ListenerOptionsTest.php b/test/Listener/ListenerOptionsTest.php
new file mode 100644
index 0000000..b415e8d
--- /dev/null
+++ b/test/Listener/ListenerOptionsTest.php
@@ -0,0 +1,107 @@
+ __DIR__,
+ 'config_cache_enabled' => true,
+ 'config_cache_key' => 'foo',
+ 'module_paths' => array('module','paths'),
+ 'config_glob_paths' => array('glob','paths'),
+ 'config_static_paths' => array('static','custom_paths'),
+ ));
+ $this->assertSame($options->getCacheDir(), __DIR__);
+ $this->assertTrue($options->getConfigCacheEnabled());
+ $this->assertNotNull(strstr($options->getConfigCacheFile(), __DIR__));
+ $this->assertNotNull(strstr($options->getConfigCacheFile(), '.php'));
+ $this->assertSame('foo', $options->getConfigCacheKey());
+ $this->assertSame(array('module', 'paths'), $options->getModulePaths());
+ $this->assertSame(array('glob', 'paths'), $options->getConfigGlobPaths());
+ $this->assertSame(array('static', 'custom_paths'), $options->getConfigStaticPaths());
+ }
+
+ public function testCanAccessKeysAsProperties()
+ {
+ $options = new ListenerOptions(array(
+ 'cache_dir' => __DIR__,
+ 'config_cache_enabled' => true,
+ 'config_cache_key' => 'foo',
+ 'module_paths' => array('module','paths'),
+ 'config_glob_paths' => array('glob','paths'),
+ 'config_static_paths' => array('static','custom_paths'),
+ ));
+ $this->assertSame($options->cache_dir, __DIR__);
+ $options->cache_dir = 'foo';
+ $this->assertSame($options->cache_dir, 'foo');
+ $this->assertTrue(isset($options->cache_dir));
+ unset($options->cache_dir);
+ $this->assertFalse(isset($options->cache_dir));
+
+ $this->assertTrue($options->config_cache_enabled);
+ $options->config_cache_enabled = false;
+ $this->assertFalse($options->config_cache_enabled);
+ $this->assertEquals('foo', $options->config_cache_key);
+ $this->assertSame(array('module', 'paths'), $options->module_paths);
+ $this->assertSame(array('glob', 'paths'), $options->config_glob_paths);
+ $this->assertSame(array('static', 'custom_paths'), $options->config_static_paths);
+ }
+
+ public function testSetModulePathsAcceptsConfigOrTraverable()
+ {
+ $config = new Config(array(__DIR__));
+ $options = new ListenerOptions;
+ $options->setModulePaths($config);
+ $this->assertSame($config, $options->getModulePaths());
+ }
+
+ public function testSetModulePathsThrowsInvalidArgumentException()
+ {
+ $this->setExpectedException('InvalidArgumentException');
+ $options = new ListenerOptions;
+ $options->setModulePaths('asd');
+ }
+
+ public function testSetConfigGlobPathsAcceptsConfigOrTraverable()
+ {
+ $config = new Config(array(__DIR__));
+ $options = new ListenerOptions;
+ $options->setConfigGlobPaths($config);
+ $this->assertSame($config, $options->getConfigGlobPaths());
+ }
+
+ public function testSetConfigGlobPathsThrowsInvalidArgumentException()
+ {
+ $this->setExpectedException('InvalidArgumentException');
+ $options = new ListenerOptions;
+ $options->setConfigGlobPaths('asd');
+ }
+}
diff --git a/test/Listener/LocatorRegistrationListenerTest.php b/test/Listener/LocatorRegistrationListenerTest.php
new file mode 100644
index 0000000..b43bbd0
--- /dev/null
+++ b/test/Listener/LocatorRegistrationListenerTest.php
@@ -0,0 +1,136 @@
+loaders = spl_autoload_functions();
+ if (!is_array($this->loaders)) {
+ // spl_autoload_functions does not return empty array when no
+ // autoloaders registered...
+ $this->loaders = array();
+ }
+
+ // Store original include_path
+ $this->includePath = get_include_path();
+
+ $autoloader = new ModuleAutoloader(array(
+ dirname(__DIR__) . '/TestAsset',
+ ));
+ $autoloader->register();
+
+ $this->sharedEvents = new SharedEventManager();
+
+ $this->moduleManager = new ModuleManager(array('ListenerTestModule'));
+ $this->moduleManager->events()->setSharedManager($this->sharedEvents);
+ $this->moduleManager->events()->attach('loadModule.resolve', new ModuleResolverListener, 1000);
+
+ $this->application = new MockApplication;
+ $events = new EventManager(array('Zend\Mvc\Application', 'ZendTest\Module\TestAsset\MockApplication', 'application'));
+ $events->setSharedManager($this->sharedEvents);
+ $this->application->setEventManager($events);
+
+ $this->serviceManager = new ServiceManager();
+ $this->serviceManager->setService('ModuleManager', $this->moduleManager);
+ $this->application->setServiceManager($this->serviceManager);
+ }
+
+ public function tearDown()
+ {
+ // Restore original autoloaders
+ AutoloaderFactory::unregisterAutoloaders();
+ $loaders = spl_autoload_functions();
+ if (is_array($loaders)) {
+ foreach ($loaders as $loader) {
+ spl_autoload_unregister($loader);
+ }
+ }
+
+ foreach ($this->loaders as $loader) {
+ spl_autoload_register($loader);
+ }
+
+ // Restore original include_path
+ set_include_path($this->includePath);
+ }
+
+ public function testModuleClassIsRegisteredWithDiAndInjectedWithSharedInstances()
+ {
+ $locator = $this->serviceManager;
+ $locator->setFactory('Foo\Bar', function($s) {
+ $module = $s->get('ListenerTestModule\Module');
+ $manager = $s->get('Zend\ModuleManager\ModuleManager');
+ $instance = new \Foo\Bar($module, $manager);
+ return $instance;
+ });
+
+ $locatorRegistrationListener = new LocatorRegistrationListener;
+ $this->moduleManager->events()->attachAggregate($locatorRegistrationListener);
+ $test = $this;
+ $this->moduleManager->events()->attach('loadModule', function ($e) use ($test) {
+ $test->module = $e->getModule();
+ }, -1000);
+ $this->moduleManager->loadModules();
+
+ $this->application->bootstrap();
+ $sharedInstance1 = $locator->get('ListenerTestModule\Module');
+ $sharedInstance2 = $locator->get('Zend\ModuleManager\ModuleManager');
+
+ $this->assertInstanceOf('ListenerTestModule\Module', $sharedInstance1);
+ $foo = false;
+ $message = '';
+ try {
+ $foo = $locator->get('Foo\Bar');
+ } catch (\Exception $e) {
+ $message = $e->getMessage();
+ while ($e = $e->getPrevious()) {
+ $message .= "\n" . $e->getMessage();
+ }
+ }
+ if (!$foo) {
+ $this->fail($message);
+ }
+ $this->assertSame($this->module, $foo->module);
+
+ $this->assertInstanceOf('Zend\ModuleManager\ModuleManager', $sharedInstance2);
+ $this->assertSame($this->moduleManager, $locator->get('Foo\Bar')->moduleManager);
+ }
+}
diff --git a/test/Listener/ModuleResolverListenerTest.php b/test/Listener/ModuleResolverListenerTest.php
new file mode 100644
index 0000000..834ae4a
--- /dev/null
+++ b/test/Listener/ModuleResolverListenerTest.php
@@ -0,0 +1,81 @@
+loaders = spl_autoload_functions();
+ if (!is_array($this->loaders)) {
+ // spl_autoload_functions does not return empty array when no
+ // autoloaders registered...
+ $this->loaders = array();
+ }
+
+ // Store original include_path
+ $this->includePath = get_include_path();
+
+ $autoloader = new ModuleAutoloader(array(
+ dirname(__DIR__) . '/TestAsset',
+ ));
+ $autoloader->register();
+ }
+
+ public function tearDown()
+ {
+ // Restore original autoloaders
+ AutoloaderFactory::unregisterAutoloaders();
+ $loaders = spl_autoload_functions();
+ if (is_array($loaders)) {
+ foreach ($loaders as $loader) {
+ spl_autoload_unregister($loader);
+ }
+ }
+
+ foreach ($this->loaders as $loader) {
+ spl_autoload_register($loader);
+ }
+
+ // Restore original include_path
+ set_include_path($this->includePath);
+ }
+
+ public function testModuleResolverListenerCanResolveModuleClasses()
+ {
+ $moduleResolver = new ModuleResolverListener;
+ $e = new ModuleEvent;
+
+ $e->setModuleName('ListenerTestModule');
+ $this->assertInstanceOf('ListenerTestModule\Module', $moduleResolver($e));
+
+ $e->setModuleName('DoesNotExist');
+ $this->assertFalse($moduleResolver($e));
+ }
+}
diff --git a/test/Listener/OnBootstrapListenerTest.php b/test/Listener/OnBootstrapListenerTest.php
new file mode 100644
index 0000000..627402b
--- /dev/null
+++ b/test/Listener/OnBootstrapListenerTest.php
@@ -0,0 +1,98 @@
+loaders = spl_autoload_functions();
+ if (!is_array($this->loaders)) {
+ // spl_autoload_functions does not return empty array when no
+ // autoloaders registered...
+ $this->loaders = array();
+ }
+
+ // Store original include_path
+ $this->includePath = get_include_path();
+
+ $autoloader = new ModuleAutoloader(array(
+ dirname(__DIR__) . '/TestAsset',
+ ));
+ $autoloader->register();
+
+ $sharedEvents = new SharedEventManager();
+ $this->moduleManager = new ModuleManager(array());
+ $this->moduleManager->events()->setSharedManager($sharedEvents);
+ $this->moduleManager->events()->attach('loadModule.resolve', new ModuleResolverListener, 1000);
+ $this->moduleManager->events()->attach('loadModule', new OnBootstrapListener, 1000);
+
+ $this->application = new MockApplication;
+ $events = new EventManager(array('Zend\Mvc\Application', 'ZendTest\Module\TestAsset\MockApplication', 'application'));
+ $events->setSharedManager($sharedEvents);
+ $this->application->setEventManager($events);
+ }
+
+ public function tearDown()
+ {
+ // Restore original autoloaders
+ AutoloaderFactory::unregisterAutoloaders();
+ $loaders = spl_autoload_functions();
+ if (is_array($loaders)) {
+ foreach ($loaders as $loader) {
+ spl_autoload_unregister($loader);
+ }
+ }
+
+ foreach ($this->loaders as $loader) {
+ spl_autoload_register($loader);
+ }
+
+ // Restore original include_path
+ set_include_path($this->includePath);
+ }
+
+ public function testOnBootstrapMethodCalledByOnBootstrapListener()
+ {
+ $moduleManager = $this->moduleManager;
+ $moduleManager->setModules(array('ListenerTestModule'));
+ $moduleManager->loadModules();
+ $this->application->bootstrap();
+ $modules = $moduleManager->getLoadedModules();
+ $this->assertTrue($modules['ListenerTestModule']->onBootstrapCalled);
+ }
+}
diff --git a/test/Listener/ServiceListenerTest.php b/test/Listener/ServiceListenerTest.php
new file mode 100644
index 0000000..38feb63
--- /dev/null
+++ b/test/Listener/ServiceListenerTest.php
@@ -0,0 +1,153 @@
+services = new ServiceManager();
+ $this->listener = new ServiceListener($this->services);
+ $this->event = new ModuleEvent();
+ }
+
+ public function testPassingInvalidModuleDoesNothing()
+ {
+ $module = new stdClass();
+ $this->event->setModule($module);
+ $this->listener->onLoadModule($this->event);
+
+ foreach ($this->serviceManagerProps as $prop) {
+ $this->assertAttributeEquals(array(), $prop, $this->services);
+ }
+ }
+
+ public function testInvalidReturnFromModuleDoesNothing()
+ {
+ $module = new TestAsset\ServiceInvalidReturnModule();
+ $this->event->setModule($module);
+ $this->listener->onLoadModule($this->event);
+
+ foreach ($this->serviceManagerProps as $prop) {
+ $this->assertAttributeEquals(array(), $prop, $this->services);
+ }
+ }
+
+ public function getServiceConfiguration()
+ {
+ return array(
+ 'invokables' => array(__CLASS__ => __CLASS__),
+ 'factories' => array(
+ 'foo' => function($sm) { },
+ ),
+ 'abstract_factories' => array(
+ new \Zend\ServiceManager\Di\DiAbstractServiceFactory(new \Zend\Di\Di()),
+ ),
+ 'shared' => array(
+ 'foo' => false,
+ 'zendtestmodulemanagerlistenerservicelistenertest' => true,
+ ),
+ 'aliases' => array(
+ 'bar' => 'foo',
+ ),
+ );
+ }
+
+ public function assertServiceManagerIsConfigured()
+ {
+ $this->listener->configureServiceManager();
+ foreach ($this->getServiceConfiguration() as $prop => $expected) {
+ if ($prop == 'invokables') {
+ $prop = 'invokableClasses';
+ foreach ($expected as $key => $value) {
+ $normalized = strtolower($key);
+ $normalized = str_replace(array('\\', '_'), '', $normalized);
+ unset($expected[$key]);
+ $expected[$normalized] = $value;
+ }
+ }
+ if ($prop == 'abstract_factories') {
+ $prop = 'abstractFactories';
+ }
+ $this->assertAttributeEquals($expected, $prop, $this->services, "$prop assertion failed");
+ }
+ }
+
+ public function testModuleReturningArrayConfiguresServiceManager()
+ {
+ $config = $this->getServiceConfiguration();
+ $module = new TestAsset\ServiceProviderModule($config);
+ $this->event->setModule($module);
+ $this->listener->onLoadModule($this->event);
+ $this->assertServiceManagerIsConfigured();
+ }
+
+ public function testModuleReturningTraversableConfiguresServiceManager()
+ {
+ $config = $this->getServiceConfiguration();
+ $config = new ArrayObject($config);
+ $module = new TestAsset\ServiceProviderModule($config);
+ $this->event->setModule($module);
+ $this->listener->onLoadModule($this->event);
+ $this->assertServiceManagerIsConfigured();
+ }
+
+ public function testModuleReturningServiceConfigurationConfiguresServiceManager()
+ {
+ $config = $this->getServiceConfiguration();
+ $config = new ServiceConfiguration($config);
+ $module = new TestAsset\ServiceProviderModule($config);
+ $this->event->setModule($module);
+ $this->listener->onLoadModule($this->event);
+ $this->assertServiceManagerIsConfigured();
+ }
+
+ public function testMergedConfigurationContainingServiceManagerKeyWillConfigureServiceManagerPostLoadModules()
+ {
+ $config = array('service_manager' => $this->getServiceConfiguration());
+ $configListener = new ConfigListener();
+ $configListener->setMergedConfig($config);
+ $this->event->setConfigListener($configListener);
+ $this->listener->onLoadModulesPost($this->event);
+ $this->assertServiceManagerIsConfigured();
+ }
+}
diff --git a/test/Listener/TestAsset/ServiceInvalidReturnModule.php b/test/Listener/TestAsset/ServiceInvalidReturnModule.php
new file mode 100644
index 0000000..6487690
--- /dev/null
+++ b/test/Listener/TestAsset/ServiceInvalidReturnModule.php
@@ -0,0 +1,39 @@
+config = $config;
+ }
+
+ public function getServiceConfiguration()
+ {
+ return $this->config;
+ }
+}
diff --git a/test/Listener/_files/bad/config.badext b/test/Listener/_files/bad/config.badext
new file mode 100644
index 0000000..e69de29
diff --git a/test/Listener/_files/bad/config.php b/test/Listener/_files/bad/config.php
new file mode 100644
index 0000000..fc3f376
--- /dev/null
+++ b/test/Listener/_files/bad/config.php
@@ -0,0 +1,3 @@
+ 'loaded',
+);
diff --git a/test/Listener/_files/good/config.xml b/test/Listener/_files/good/config.xml
new file mode 100644
index 0000000..fbdf865
--- /dev/null
+++ b/test/Listener/_files/good/config.xml
@@ -0,0 +1,4 @@
+
+
+ loaded
+
diff --git a/test/Listener/_files/good/config.yml b/test/Listener/_files/good/config.yml
new file mode 100644
index 0000000..d2da9b6
--- /dev/null
+++ b/test/Listener/_files/good/config.yml
@@ -0,0 +1 @@
+yml: loaded
diff --git a/test/Listener/_files/good/merge1.php b/test/Listener/_files/good/merge1.php
new file mode 100644
index 0000000..70bc649
--- /dev/null
+++ b/test/Listener/_files/good/merge1.php
@@ -0,0 +1,5 @@
+ array('foo'),
+ 'keyed' => 'foo',
+);
diff --git a/test/Listener/_files/good/merge2.php b/test/Listener/_files/good/merge2.php
new file mode 100644
index 0000000..2d032ca
--- /dev/null
+++ b/test/Listener/_files/good/merge2.php
@@ -0,0 +1,5 @@
+ array('bar'),
+ 'keyed' => 'bar',
+);
diff --git a/test/ModuleEventTest.php b/test/ModuleEventTest.php
new file mode 100644
index 0000000..1e32ddc
--- /dev/null
+++ b/test/ModuleEventTest.php
@@ -0,0 +1,96 @@
+event = new ModuleEvent();
+ }
+
+ public function testSettingModuleProxiesToParameters()
+ {
+ $module = new stdClass;
+ $this->event->setModule($module);
+ $test = $this->event->getParam('module');
+ $this->assertSame($module, $test);
+ }
+
+ public function testCanRetrieveModuleViaGetter()
+ {
+ $module = new stdClass;
+ $this->event->setModule($module);
+ $test = $this->event->getModule();
+ $this->assertSame($module, $test);
+ }
+
+ public function testPassingNonObjectToSetModuleRaisesException()
+ {
+ $this->setExpectedException('Zend\ModuleManager\Exception\InvalidArgumentException');
+ $this->event->setModule('foo');
+ }
+
+ public function testSettingModuleNameProxiesToParameters()
+ {
+ $moduleName = 'MyModule';
+ $this->event->setModuleName($moduleName);
+ $test = $this->event->getParam('moduleName');
+ $this->assertSame($moduleName, $test);
+ }
+
+ public function testCanRetrieveModuleNameViaGetter()
+ {
+ $moduleName = 'MyModule';
+ $this->event->setModuleName($moduleName);
+ $test = $this->event->getModuleName();
+ $this->assertSame($moduleName, $test);
+ }
+
+ public function testPassingNonStringToSetModuleNameRaisesException()
+ {
+ $this->setExpectedException('Zend\ModuleManager\Exception\InvalidArgumentException');
+ $this->event->setModuleName(new StdClass);
+ }
+
+ public function testSettingConfigListenerProxiesToParameters()
+ {
+ $configListener = new ConfigListener;
+ $this->event->setConfigListener($configListener);
+ $test = $this->event->getParam('configListener');
+ $this->assertSame($configListener, $test);
+ }
+
+ public function testCanRetrieveConfigListenerViaGetter()
+ {
+ $configListener = new ConfigListener;
+ $this->event->setConfigListener($configListener);
+ $test = $this->event->getConfigListener();
+ $this->assertSame($configListener, $test);
+ }
+}
diff --git a/test/ModuleManagerTest.php b/test/ModuleManagerTest.php
new file mode 100644
index 0000000..d3d7861
--- /dev/null
+++ b/test/ModuleManagerTest.php
@@ -0,0 +1,145 @@
+tmpdir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'zend_module_cache_dir';
+ @mkdir($this->tmpdir);
+ $this->configCache = $this->tmpdir . DIRECTORY_SEPARATOR . 'config.cache.php';
+ // Store original autoloaders
+ $this->loaders = spl_autoload_functions();
+ if (!is_array($this->loaders)) {
+ // spl_autoload_functions does not return empty array when no
+ // autoloaders registered...
+ $this->loaders = array();
+ }
+
+ // Store original include_path
+ $this->includePath = get_include_path();
+
+ $this->defaultListeners = new DefaultListenerAggregate(
+ new ListenerOptions(array(
+ 'module_paths' => array(
+ realpath(__DIR__ . '/TestAsset'),
+ ),
+ ))
+ );
+ }
+
+ public function tearDown()
+ {
+ $file = glob($this->tmpdir . DIRECTORY_SEPARATOR . '*');
+ @unlink($file[0]); // change this if there's ever > 1 file
+ @rmdir($this->tmpdir);
+ // Restore original autoloaders
+ AutoloaderFactory::unregisterAutoloaders();
+ $loaders = spl_autoload_functions();
+ if (is_array($loaders)) {
+ foreach ($loaders as $loader) {
+ spl_autoload_unregister($loader);
+ }
+ }
+
+ foreach ($this->loaders as $loader) {
+ spl_autoload_register($loader);
+ }
+
+ // Restore original include_path
+ set_include_path($this->includePath);
+ }
+
+ public function testEventManagerIdentifiers()
+ {
+ $moduleManager = new ModuleManager(array());
+ $identifiers = $moduleManager->events()->getIdentifiers();
+ $expected = array('Zend\ModuleManager\ModuleManager', 'module_manager');
+ $this->assertEquals($expected, array_values($identifiers));
+ }
+
+ public function testCanLoadSomeModule()
+ {
+ $configListener = $this->defaultListeners->getConfigListener();
+ $moduleManager = new ModuleManager(array('SomeModule'), new EventManager);
+ $moduleManager->events()->attachAggregate($this->defaultListeners);
+ $moduleManager->loadModules();
+ $loadedModules = $moduleManager->getLoadedModules();
+ $this->assertInstanceOf('SomeModule\Module', $loadedModules['SomeModule']);
+ $config = $configListener->getMergedConfig();
+ $this->assertSame($config->some, 'thing');
+ }
+
+ public function testCanLoadMultipleModules()
+ {
+ $configListener = $this->defaultListeners->getConfigListener();
+ $moduleManager = new ModuleManager(array('BarModule', 'BazModule'));
+ $moduleManager->events()->attachAggregate($this->defaultListeners);
+ $moduleManager->loadModules();
+ $loadedModules = $moduleManager->getLoadedModules();
+ $this->assertInstanceOf('BarModule\Module', $loadedModules['BarModule']);
+ $this->assertInstanceOf('BazModule\Module', $loadedModules['BazModule']);
+ $this->assertInstanceOf('BarModule\Module', $moduleManager->getModule('BarModule'));
+ $this->assertInstanceOf('BazModule\Module', $moduleManager->getModule('BazModule'));
+ $this->assertNull($moduleManager->getModule('NotLoaded'));
+ $config = $configListener->getMergedConfig();
+ $this->assertSame('foo', $config->bar);
+ $this->assertSame('bar', $config->baz);
+ }
+
+ public function testModuleLoadingBehavior()
+ {
+ $moduleManager = new ModuleManager(array('BarModule'));
+ $moduleManager->events()->attachAggregate($this->defaultListeners);
+ $modules = $moduleManager->getLoadedModules();
+ $this->assertSame(0, count($modules));
+ $modules = $moduleManager->getLoadedModules(true);
+ $this->assertSame(1, count($modules));
+ $moduleManager->loadModules(); // should not cause any problems
+ $moduleManager->loadModule('BarModule'); // should not cause any problems
+ $modules = $moduleManager->getLoadedModules(true); // BarModule already loaded so nothing happens
+ $this->assertSame(1, count($modules));
+ }
+
+ public function testConstructorThrowsInvalidArgumentException()
+ {
+ $this->setExpectedException('InvalidArgumentException');
+ $moduleManager = new ModuleManager('stringShouldBeArray');
+ }
+
+ public function testNotFoundModuleThrowsRuntimeException()
+ {
+ $this->setExpectedException('RuntimeException');
+ $moduleManager = new ModuleManager(array('NotFoundModule'));
+ $moduleManager->loadModules();
+ }
+}
diff --git a/test/TestAsset/AutoInstallModule/Module.php b/test/TestAsset/AutoInstallModule/Module.php
new file mode 100644
index 0000000..fe3df25
--- /dev/null
+++ b/test/TestAsset/AutoInstallModule/Module.php
@@ -0,0 +1,38 @@
+install();
+ }
+
+ public function autoUpgrade($version = null)
+ {
+ return $this->upgrade($version);
+ }
+
+ public function install()
+ {
+ return static::$RESPONSE;
+ }
+
+ public function upgrade($version = null)
+ {
+ return static::$RESPONSE;
+ }
+
+ public function getProvides()
+ {
+ return array(
+ __NAMESPACE__ => array(
+ 'version' => static::$VERSION,
+ ),
+ );
+ }
+}
diff --git a/test/TestAsset/BadConfigModule/Module.php b/test/TestAsset/BadConfigModule/Module.php
new file mode 100644
index 0000000..5e37f3c
--- /dev/null
+++ b/test/TestAsset/BadConfigModule/Module.php
@@ -0,0 +1,11 @@
+initAutoloader();
+ }
+
+ protected function initAutoloader()
+ {
+ include __DIR__ . '/autoload_register.php';
+ }
+
+ public function getConfig()
+ {
+ return new Config(include __DIR__ . '/configs/config.php');
+ }
+
+ public function getProvides()
+ {
+ return array(
+ __NAMESPACE__ => array(
+ 'version' => $this->version,
+ ),
+ );
+ }
+
+ public function getDependencies()
+ {
+ return array(
+ 'php' => array(
+ 'version' => '5.3.0',
+ 'required' => true,
+ ),
+ 'ext/core' => array(
+ 'version' => '0.1',
+ 'required' => true,
+ ),
+ 'BooModule' => array(
+ 'required' => true,
+ ),
+ );
+ }
+}
diff --git a/test/TestAsset/BafModule/autoload_classmap.php b/test/TestAsset/BafModule/autoload_classmap.php
new file mode 100644
index 0000000..668cf05
--- /dev/null
+++ b/test/TestAsset/BafModule/autoload_classmap.php
@@ -0,0 +1,4 @@
+ __DIR__ . DIRECTORY_SEPARATOR . 'Module.php',
+);
diff --git a/test/TestAsset/BafModule/autoload_function.php b/test/TestAsset/BafModule/autoload_function.php
new file mode 100644
index 0000000..3ea81c4
--- /dev/null
+++ b/test/TestAsset/BafModule/autoload_function.php
@@ -0,0 +1,12 @@
+ 'foo',
+);
diff --git a/test/TestAsset/BamModule/Module.php b/test/TestAsset/BamModule/Module.php
new file mode 100644
index 0000000..8b9c754
--- /dev/null
+++ b/test/TestAsset/BamModule/Module.php
@@ -0,0 +1,49 @@
+initAutoloader();
+ }
+
+ protected function initAutoloader()
+ {
+ include __DIR__ . '/autoload_register.php';
+ }
+
+ public function getConfig()
+ {
+ return new Config(include __DIR__ . '/configs/config.php');
+ }
+
+ public function getProvides()
+ {
+ return array(
+ __NAMESPACE__ => array(
+ 'version' => $this->version,
+ ),
+ );
+ }
+
+ public function getDependencies()
+ {
+ return array(
+ 'php' => array(
+ 'version' => '5.3.0',
+ 'required' => true,
+ ),
+ 'ext/core' => array(
+ 'version' => '0.1',
+ 'required' => true,
+ ),
+ 'BooModule' => true,
+ );
+ }
+}
diff --git a/test/TestAsset/BamModule/autoload_classmap.php b/test/TestAsset/BamModule/autoload_classmap.php
new file mode 100644
index 0000000..477422f
--- /dev/null
+++ b/test/TestAsset/BamModule/autoload_classmap.php
@@ -0,0 +1,4 @@
+ __DIR__ . DIRECTORY_SEPARATOR . 'Module.php',
+);
diff --git a/test/TestAsset/BamModule/autoload_function.php b/test/TestAsset/BamModule/autoload_function.php
new file mode 100644
index 0000000..3ea81c4
--- /dev/null
+++ b/test/TestAsset/BamModule/autoload_function.php
@@ -0,0 +1,12 @@
+ 'foo',
+);
diff --git a/test/TestAsset/BarModule/Module.php b/test/TestAsset/BarModule/Module.php
new file mode 100644
index 0000000..01f245e
--- /dev/null
+++ b/test/TestAsset/BarModule/Module.php
@@ -0,0 +1,44 @@
+initAutoloader();
+ }
+
+ protected function initAutoloader()
+ {
+ include __DIR__ . '/autoload_register.php';
+ }
+
+ public function getConfig()
+ {
+ return new Config(include __DIR__ . '/configs/config.php');
+ }
+
+ public function getProvides()
+ {
+ return array(
+ __NAMESPACE__ => array(
+ 'version' => $this->version,
+ ),
+ );
+ }
+
+ public function getDependencies()
+ {
+ return array(
+ 'php' => array(
+ 'version' => '5.3.0',
+ 'required' => true,
+ ),
+ );
+ }
+}
diff --git a/test/TestAsset/BarModule/autoload_classmap.php b/test/TestAsset/BarModule/autoload_classmap.php
new file mode 100644
index 0000000..97b5539
--- /dev/null
+++ b/test/TestAsset/BarModule/autoload_classmap.php
@@ -0,0 +1,4 @@
+ __DIR__ . DIRECTORY_SEPARATOR . 'Module.php',
+);
diff --git a/test/TestAsset/BarModule/autoload_function.php b/test/TestAsset/BarModule/autoload_function.php
new file mode 100644
index 0000000..3ea81c4
--- /dev/null
+++ b/test/TestAsset/BarModule/autoload_function.php
@@ -0,0 +1,12 @@
+ 'foo',
+);
diff --git a/test/TestAsset/BazModule/Module.php b/test/TestAsset/BazModule/Module.php
new file mode 100644
index 0000000..cb627f4
--- /dev/null
+++ b/test/TestAsset/BazModule/Module.php
@@ -0,0 +1,23 @@
+initAutoloader();
+ }
+
+ protected function initAutoloader()
+ {
+ include __DIR__ . '/autoload_register.php';
+ }
+
+ public function getConfig()
+ {
+ return new Config(include __DIR__ . '/configs/config.php');
+ }
+}
diff --git a/test/TestAsset/BazModule/autoload_classmap.php b/test/TestAsset/BazModule/autoload_classmap.php
new file mode 100644
index 0000000..e514a02
--- /dev/null
+++ b/test/TestAsset/BazModule/autoload_classmap.php
@@ -0,0 +1,4 @@
+ __DIR__ . DIRECTORY_SEPARATOR . 'Module.php',
+);
diff --git a/test/TestAsset/BazModule/autoload_function.php b/test/TestAsset/BazModule/autoload_function.php
new file mode 100644
index 0000000..3ea81c4
--- /dev/null
+++ b/test/TestAsset/BazModule/autoload_function.php
@@ -0,0 +1,12 @@
+ 'bar',
+);
diff --git a/test/TestAsset/BooModule/Module.php b/test/TestAsset/BooModule/Module.php
new file mode 100644
index 0000000..ea2182a
--- /dev/null
+++ b/test/TestAsset/BooModule/Module.php
@@ -0,0 +1,48 @@
+initAutoloader();
+ }
+
+ protected function initAutoloader()
+ {
+ include __DIR__ . '/autoload_register.php';
+ }
+
+ public function getConfig()
+ {
+ return new Config(include __DIR__ . '/configs/config.php');
+ }
+
+ public function getProvides()
+ {
+ return array(
+ __NAMESPACE__ => array(
+ 'version' => $this->version,
+ ),
+ );
+ }
+
+ public function getDependencies()
+ {
+ return array(
+ 'php' => array(
+ 'version' => '5.3.0',
+ ),
+ 'ext/monkey' => array(
+ 'version' => '0.1',
+ ),
+ 'BarModule' => array(
+ )
+ );
+ }
+}
diff --git a/test/TestAsset/BooModule/autoload_classmap.php b/test/TestAsset/BooModule/autoload_classmap.php
new file mode 100644
index 0000000..cb7b5f5
--- /dev/null
+++ b/test/TestAsset/BooModule/autoload_classmap.php
@@ -0,0 +1,4 @@
+ __DIR__ . DIRECTORY_SEPARATOR . 'Module.php',
+);
diff --git a/test/TestAsset/BooModule/autoload_function.php b/test/TestAsset/BooModule/autoload_function.php
new file mode 100644
index 0000000..3ea81c4
--- /dev/null
+++ b/test/TestAsset/BooModule/autoload_function.php
@@ -0,0 +1,12 @@
+ 'foo',
+);
diff --git a/test/TestAsset/BorModule/Module.php b/test/TestAsset/BorModule/Module.php
new file mode 100644
index 0000000..8869f99
--- /dev/null
+++ b/test/TestAsset/BorModule/Module.php
@@ -0,0 +1,51 @@
+initAutoloader();
+ }
+
+ protected function initAutoloader()
+ {
+ include __DIR__ . '/autoload_register.php';
+ }
+
+ public function getConfig()
+ {
+ return new Config(include __DIR__ . '/configs/config.php');
+ }
+
+ public function getProvides()
+ {
+ return array(
+ __NAMESPACE__ => array(
+ 'version' => $this->version,
+ ),
+ );
+ }
+
+ public function getDependencies()
+ {
+ return array(
+ 'php' => array(
+ 'version' => '5.3.0',
+ 'required' => true,
+ ),
+ 'ext/monkey' => array(
+ 'version' => '0.1',
+ 'required' => true,
+ ),
+ 'BooModule' => array(
+ 'required' => true,
+ ),
+ );
+ }
+}
diff --git a/test/TestAsset/BorModule/autoload_classmap.php b/test/TestAsset/BorModule/autoload_classmap.php
new file mode 100644
index 0000000..976cc40
--- /dev/null
+++ b/test/TestAsset/BorModule/autoload_classmap.php
@@ -0,0 +1,4 @@
+ __DIR__ . DIRECTORY_SEPARATOR . 'Module.php',
+);
diff --git a/test/TestAsset/BorModule/autoload_function.php b/test/TestAsset/BorModule/autoload_function.php
new file mode 100644
index 0000000..3ea81c4
--- /dev/null
+++ b/test/TestAsset/BorModule/autoload_function.php
@@ -0,0 +1,12 @@
+ 'foo',
+);
diff --git a/test/TestAsset/DoubleModule/Module.php b/test/TestAsset/DoubleModule/Module.php
new file mode 100644
index 0000000..5ecfff0
--- /dev/null
+++ b/test/TestAsset/DoubleModule/Module.php
@@ -0,0 +1,43 @@
+initAutoloader();
+ }
+
+ protected function initAutoloader()
+ {
+ include __DIR__ . '/autoload_register.php';
+ }
+
+ public function getConfig()
+ {
+ return new Config(include __DIR__ . '/configs/config.php');
+ }
+
+ public function getProvides()
+ {
+ return array(
+ 'BarModule' => array(
+ 'version' => $this->version,
+ ),
+ );
+ }
+
+ public function getDependencies()
+ {
+ return array(
+ 'php' => array(
+ 'version' => '5.3.0',
+ )
+ );
+ }
+}
diff --git a/test/TestAsset/DoubleModule/autoload_classmap.php b/test/TestAsset/DoubleModule/autoload_classmap.php
new file mode 100644
index 0000000..1127c34
--- /dev/null
+++ b/test/TestAsset/DoubleModule/autoload_classmap.php
@@ -0,0 +1,4 @@
+ __DIR__ . DIRECTORY_SEPARATOR . 'Module.php',
+);
diff --git a/test/TestAsset/DoubleModule/autoload_function.php b/test/TestAsset/DoubleModule/autoload_function.php
new file mode 100644
index 0000000..3ea81c4
--- /dev/null
+++ b/test/TestAsset/DoubleModule/autoload_function.php
@@ -0,0 +1,12 @@
+ 'foo',
+);
diff --git a/test/TestAsset/ImpossibleModule/Module.php b/test/TestAsset/ImpossibleModule/Module.php
new file mode 100644
index 0000000..3279285
--- /dev/null
+++ b/test/TestAsset/ImpossibleModule/Module.php
@@ -0,0 +1,44 @@
+initAutoloader();
+ }
+
+ protected function initAutoloader()
+ {
+ include __DIR__ . '/autoload_register.php';
+ }
+
+ public function getConfig()
+ {
+ return new Config(include __DIR__ . '/configs/config.php');
+ }
+
+ public function getProvides()
+ {
+ return array(
+ __NAMESPACE__ => array(
+ 'version' => $this->version,
+ ),
+ );
+ }
+
+ public function getDependencies()
+ {
+ return array(
+ 'php' => array(
+ 'version' => '99.3.0',
+ ),
+ 'BarModule' => true,
+ );
+ }
+}
diff --git a/test/TestAsset/ImpossibleModule/autoload_classmap.php b/test/TestAsset/ImpossibleModule/autoload_classmap.php
new file mode 100644
index 0000000..bb989be
--- /dev/null
+++ b/test/TestAsset/ImpossibleModule/autoload_classmap.php
@@ -0,0 +1,4 @@
+ __DIR__ . DIRECTORY_SEPARATOR . 'Module.php',
+);
diff --git a/test/TestAsset/ImpossibleModule/autoload_function.php b/test/TestAsset/ImpossibleModule/autoload_function.php
new file mode 100644
index 0000000..3ea81c4
--- /dev/null
+++ b/test/TestAsset/ImpossibleModule/autoload_function.php
@@ -0,0 +1,12 @@
+ 'foo',
+);
diff --git a/test/TestAsset/ListenerTestModule/Module.php b/test/TestAsset/ListenerTestModule/Module.php
new file mode 100644
index 0000000..1d37203
--- /dev/null
+++ b/test/TestAsset/ListenerTestModule/Module.php
@@ -0,0 +1,75 @@
+initCalled = true;
+ }
+
+ public function getConfig()
+ {
+ $this->getConfigCalled = true;
+ return array(
+ 'listener' => 'test'
+ );
+ }
+
+ public function getAutoloaderConfig()
+ {
+ $this->getAutoloaderConfigCalled = true;
+ return array(
+ 'Zend\Loader\StandardAutoloader' => array(
+ 'namespaces' => array(
+ 'Foo' => __DIR__ . '/src/Foo',
+ ),
+ ),
+ );
+ }
+
+ public function onBootstrap(Event $e)
+ {
+ $this->onBootstrapCalled = true;
+ }
+}
diff --git a/test/TestAsset/ListenerTestModule/src/Foo/Bar.php b/test/TestAsset/ListenerTestModule/src/Foo/Bar.php
new file mode 100644
index 0000000..19b4a1c
--- /dev/null
+++ b/test/TestAsset/ListenerTestModule/src/Foo/Bar.php
@@ -0,0 +1,37 @@
+module = $module;
+ $this->moduleManager = $moduleManager;
+ }
+}
diff --git a/test/TestAsset/MockApplication.php b/test/TestAsset/MockApplication.php
new file mode 100644
index 0000000..8267a74
--- /dev/null
+++ b/test/TestAsset/MockApplication.php
@@ -0,0 +1,97 @@
+events = $events;
+ }
+
+ public function events()
+ {
+ return $this->events;
+ }
+
+ public function setServiceManager($serviceManager)
+ {
+ $this->serviceManager = $serviceManager;
+ }
+
+ /**
+ * Get the locator object
+ *
+ * @return \Zend\ServiceManager\ServiceLocatorInterface
+ */
+ public function getServiceManager()
+ {
+ return $this->serviceManager;
+ }
+
+ /**
+ * Get the request object
+ *
+ * @return Request
+ */
+ public function getRequest()
+ {
+ return $this->request;
+ }
+
+ /**
+ * Get the response object
+ *
+ * @return Response
+ */
+ public function getResponse()
+ {
+ return $this->response;
+ }
+
+ /**
+ * Run the application
+ *
+ * @return \Zend\Http\Response
+ */
+ public function run()
+ {
+ return $this->response;
+ }
+
+ public function bootstrap()
+ {
+ $event = new MvcEvent();
+ $event->setApplication($this);
+ $event->setTarget($this);
+ $this->events()->trigger('bootstrap', $event);
+ }
+}
diff --git a/test/TestAsset/NotAutoloaderModule/Module.php b/test/TestAsset/NotAutoloaderModule/Module.php
new file mode 100644
index 0000000..43e2d1c
--- /dev/null
+++ b/test/TestAsset/NotAutoloaderModule/Module.php
@@ -0,0 +1,20 @@
+getAutoloaderConfigCalled = true;
+ return array(
+ 'Zend\Loader\StandardAutoloader' => array(
+ 'namespaces' => array(
+ 'Foo' => __DIR__ . '/src/Foo',
+ ),
+ ),
+ );
+ }
+}
diff --git a/test/TestAsset/NotAutoloaderModule/src/Foo/Bar.php b/test/TestAsset/NotAutoloaderModule/src/Foo/Bar.php
new file mode 100644
index 0000000..10d441b
--- /dev/null
+++ b/test/TestAsset/NotAutoloaderModule/src/Foo/Bar.php
@@ -0,0 +1,6 @@
+initAutoloader();
+ }
+
+ protected function initAutoloader()
+ {
+ include __DIR__ . '/autoload_register.php';
+ }
+
+ public function getConfig()
+ {
+ return new Config(include __DIR__ . '/configs/config.php');
+ }
+}
diff --git a/test/TestAsset/SomeModule/autoload_classmap.php b/test/TestAsset/SomeModule/autoload_classmap.php
new file mode 100644
index 0000000..e514a02
--- /dev/null
+++ b/test/TestAsset/SomeModule/autoload_classmap.php
@@ -0,0 +1,4 @@
+ __DIR__ . DIRECTORY_SEPARATOR . 'Module.php',
+);
diff --git a/test/TestAsset/SomeModule/autoload_function.php b/test/TestAsset/SomeModule/autoload_function.php
new file mode 100644
index 0000000..3ea81c4
--- /dev/null
+++ b/test/TestAsset/SomeModule/autoload_function.php
@@ -0,0 +1,12 @@
+ 'thing',
+);
diff --git a/test/_files/config.bad b/test/_files/config.bad
new file mode 100644
index 0000000..e69de29
diff --git a/test/_files/config.ini b/test/_files/config.ini
new file mode 100644
index 0000000..9eb2b99
--- /dev/null
+++ b/test/_files/config.ini
@@ -0,0 +1,2 @@
+[all]
+ini = "yes"
diff --git a/test/_files/config.json b/test/_files/config.json
new file mode 100644
index 0000000..3aff3a9
--- /dev/null
+++ b/test/_files/config.json
@@ -0,0 +1 @@
+{"all":{"json":"yes"}}
diff --git a/test/_files/config.php b/test/_files/config.php
new file mode 100644
index 0000000..6e754c4
--- /dev/null
+++ b/test/_files/config.php
@@ -0,0 +1,6 @@
+ array(
+ 'php' => 'yes'
+ ),
+);
diff --git a/test/_files/config.xml b/test/_files/config.xml
new file mode 100644
index 0000000..1026da5
--- /dev/null
+++ b/test/_files/config.xml
@@ -0,0 +1,6 @@
+
+
+
+ yes
+
+
diff --git a/test/_files/config.yaml b/test/_files/config.yaml
new file mode 100644
index 0000000..eb36052
--- /dev/null
+++ b/test/_files/config.yaml
@@ -0,0 +1,2 @@
+all:
+ yaml: true