Skip to content
This repository has been archived by the owner on Jan 29, 2020. It is now read-only.

Commit

Permalink
Merge branch 'hotfix/mvc-init-integration' into develop
Browse files Browse the repository at this point in the history
Close #14
  • Loading branch information
mwillbanks committed Oct 7, 2015
2 parents 407c96d + 5424306 commit 877eef4
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 78 deletions.
152 changes: 108 additions & 44 deletions src/Listener/ServiceListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ class ServiceListener implements ServiceListenerInterface
*/
protected $appServiceConfig = [];

/**
* Service manager post-configuration.
*
* @var ServiceManager
*/
protected $configuredServiceManager;

/**
* @var \Zend\Stdlib\CallbackHandler[]
*/
Expand Down Expand Up @@ -113,18 +120,21 @@ public function setApplicationServiceManager($key, $moduleInterface, $method)
'config_key' => $key,
'module_class_interface' => $moduleInterface,
'module_class_method' => $method,
'configuration' => [ $this->defaultServiceConfig ],
'configuration' => [
'__DEFAULT__' => $this->defaultServiceConfig,
],
];
}

/**
* Retrieve configuration aggregated for the application service manager.
*
* @param array
* @inheritDoc
*/
public function getServiceManagerConfig()
public function getConfiguredServiceManager()
{
return $this->appServiceConfig;
if (! $this->configuredServiceManager) {
return $this->defaultServiceManager;
}
return $this->configuredServiceManager;
}

/**
Expand Down Expand Up @@ -189,11 +199,11 @@ public function onLoadModule(ModuleEvent $e)
}

if (! is_array($config)) {
// If we don't have an array by this point, nothing left to do.
// If we do not have an array by this point, nothing left to do.
continue;
}

// We're keeping track of which modules provided which configuration to which service managers.
// We are keeping track of which modules provided which configuration to which service managers.
// The actual merging takes place later. Doing it this way will enable us to provide more powerful
// debugging tools for showing which modules overrode what.
$fullname = $e->getModuleName() . '::' . $sm['module_class_method'] . '()';
Expand All @@ -216,48 +226,28 @@ public function onLoadModulesPost(ModuleEvent $e)
{
$configListener = $e->getConfigListener();
$config = $configListener->getMergedConfig(false);

$appServiceConfig = [];
$pluginManagers = [];
$pluginManagers = [];
$serviceManager = $this->configureServiceManager($config);

foreach ($this->serviceManagers as $key => $sm) {
if (isset($config[$sm['config_key']])
&& is_array($config[$sm['config_key']])
&& !empty($config[$sm['config_key']])
) {
$this->serviceManagers[$key]['configuration']['merged_config'] = $config[$sm['config_key']];
if ($key === self::IS_APP_MANAGER) {
// Already completed.
continue;
}

// Merge all of the things!
$smConfig = [];
foreach ($this->serviceManagers[$key]['configuration'] as $name => $configs) {
if (isset($configs['configuration_classes'])) {
foreach ($configs['configuration_classes'] as $class) {
$configs = ArrayUtils::merge($configs, $this->serviceConfigToArray($class));
}
}
$smConfig = ArrayUtils::merge($smConfig, $configs);
}
$serviceConfig = $this->mergeServiceConfiguration($key, $sm, $config);

// If this is for the application service manager, we're done.
if ($key === self::IS_APP_MANAGER) {
$appServiceConfig = $smConfig;
// Nothing to do? move to the next
if (empty($serviceConfig)) {
continue;
}

// Create the plugin manager instance.
//
// Use the build method, so that we can pass the configuration, but
// also so we can prevent caching it in the SM instance itself.
$instance = $this->defaultServiceManager->build($sm['service_manager'], $smConfig);

if (! $instance instanceof ServiceManager) {
throw new Exception\RuntimeException(sprintf(
'Instance returned for %s is not a valid service or plugin manager; received instance of %s',
$sm['service_manager'],
(is_object($instance) ? get_class($instance) : gettype($instance))
));
}
$instance = $serviceManager->build($sm['service_manager'], $serviceConfig);
$this->validateServiceManager($instance, $sm['service_manager']);

// Map the configuration key (and class name, if it differs) to the instance.
$pluginManagers[$key] = $instance;
Expand All @@ -266,14 +256,13 @@ public function onLoadModulesPost(ModuleEvent $e)
}
}

// Register plugin managers as services in the application configuration.
if (! isset($appServiceConfig['services'])) {
$appServiceConfig['services'] = [];
// Register plugin managers as services in the application service manager
if (! empty($pluginManagers)) {
$serviceManager = $serviceManager->withConfig(['services' => $pluginManagers]);
}
$appServiceConfig['services'] = array_merge($appServiceConfig['services'], $pluginManagers);

// Set the application service manager configuration
$this->appServiceConfig = (new ServiceConfig($appServiceConfig))->toArray();
// Set the configured application service manager instance
$this->configuredServiceManager = $serviceManager;
}

/**
Expand Down Expand Up @@ -302,4 +291,79 @@ protected function serviceConfigToArray($config)

return $config->toArray();
}

/**
* Configure the service manager.
*
* If we've indicated we want to set the application service manager config
* metadata, then we will see if any was provided in the merged
* configuration, and merge it with any default service configuration we
* have in the instance to return a new service manager instance.
*
* @param array $config
* @return ServiceManager
*/
private function configureServiceManager(array $config)
{
if (! isset($this->serviceManagers[self::IS_APP_MANAGER])) {
return $this->defaultServiceManager->withConfig(['services' => [
'config' => $config,
]]);
}

$services = $this->defaultServiceManager;
$metadata = $this->serviceManagers[self::IS_APP_MANAGER];

$serviceConfig = $this->mergeServiceConfiguration(self::IS_APP_MANAGER, $metadata, $config);
$serviceConfig['services']['config'] = $config;

return $services->withConfig($serviceConfig);
}

/**
* Merge all configuration for a given service manager to a single array.
*
* @param string $key Named service manager
* @param array $metadata Service manager metadata
* @param array $config Merged configuration
* @return array Service manager-specific configuration
*/
private function mergeServiceConfiguration($key, array $metadata, array $config)
{
if (isset($config[$metadata['config_key']])
&& is_array($config[$metadata['config_key']])
&& !empty($config[$metadata['config_key']])
) {
$this->serviceManagers[$key]['configuration']['merged_config'] = $config[$metadata['config_key']];
}

// Merge all of the things!
$serviceConfig = [];
foreach ($this->serviceManagers[$key]['configuration'] as $name => $configs) {
if (isset($configs['configuration_classes'])) {
foreach ($configs['configuration_classes'] as $class) {
$configs = ArrayUtils::merge($configs, $this->serviceConfigToArray($class));
}
}
$serviceConfig = ArrayUtils::merge($serviceConfig, $configs);
}

return $serviceConfig;
}

/**
* Ensure the returned service manager is actually a service manager.
*
* @throws Exception\RuntimeException for invalid service managers.
*/
private function validateServiceManager($instance, $name)
{
if (! $instance instanceof ServiceManager) {
throw new Exception\RuntimeException(sprintf(
'Instance returned for %s is not a valid service or plugin manager; received instance of %s',
$name,
(is_object($instance) ? get_class($instance) : gettype($instance))
));
}
}
}
9 changes: 3 additions & 6 deletions src/Listener/ServiceListenerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,11 @@ public function addServiceManager($serviceManager, $key, $moduleInterface, $meth
public function setApplicationServiceManager($key, $moduleInterface, $method);

/**
* Retrieve the aggregated configuration for the application service manager.
* Retrieve the application service manager instance post-configuration.
*
* The array returned must be valid for passing to the service manager's
* constructor or withConfig() method.
*
* @return array
* @return ServiceManager
*/
public function getServiceManagerConfig();
public function getConfiguredServiceManager();

/**
* @param array $configuration
Expand Down
64 changes: 36 additions & 28 deletions test/Listener/ServiceListenerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
use Zend\ModuleManager\ModuleEvent;
use Zend\ServiceManager\Config as ServiceConfig;
use Zend\ServiceManager\ServiceManager;
use Zend\Stdlib\ArrayUtils;
use ZendTest\ModuleManager\EventManagerIntrospectionTrait;

/**
Expand Down Expand Up @@ -83,7 +82,7 @@ public function testPassingInvalidModuleDoesNothing()
$this->event->setModule($module);
$this->listener->onLoadModule($this->event);

$this->assertEquals([], $this->listener->getServiceManagerConfig());
$this->assertSame($this->services, $this->listener->getConfiguredServiceManager());
}

public function testInvalidReturnFromModuleDoesNothing()
Expand All @@ -92,14 +91,16 @@ public function testInvalidReturnFromModuleDoesNothing()
$this->event->setModule($module);
$this->listener->onLoadModule($this->event);

$this->assertEquals([], $this->listener->getServiceManagerConfig());
$this->assertSame($this->services, $this->listener->getConfiguredServiceManager());
}

public function getServiceConfig()
{
// @codingStandardsIgnoreStart
return [
'invokables' => [__CLASS__ => __CLASS__],
'invokables' => [
__CLASS__ => __CLASS__
],
'factories' => [
'foo' => function ($sm) { },
],
Expand All @@ -119,9 +120,16 @@ public function getServiceConfig()

public function assertServiceManagerConfiguration()
{
$expected = ArrayUtils::merge($this->defaultServiceConfig, $this->getServiceConfig());
$this->listener->onLoadModulesPost($this->event);
$this->assertEquals($expected, $this->listener->getServiceManagerConfig());
$services = $this->listener->getConfiguredServiceManager();
$this->assertNotSame($this->services, $services);
$this->assertInstanceOf(ServiceManager::class, $services);

$this->assertTrue($services->has(__CLASS__));
$this->assertTrue($services->has('foo'));
$this->assertTrue($services->has('bar'));
$this->assertFalse($services->has('resolved-by-abstract'));
$this->assertTrue($services->has('resolved-by-abstract', true));
}

public function testModuleReturningArrayConfiguresServiceManager()
Expand All @@ -130,6 +138,7 @@ public function testModuleReturningArrayConfiguresServiceManager()
$module = new TestAsset\ServiceProviderModule($config);
$this->event->setModule($module);
$this->listener->onLoadModule($this->event);
$services = $this->listener->getConfiguredServiceManager();
$this->assertServiceManagerConfiguration();
}

Expand All @@ -145,7 +154,10 @@ public function testModuleReturningTraversableConfiguresServiceManager()

public function testModuleServiceConfigOverridesGlobalConfig()
{
$defaultConfig = ['aliases' => ['foo' => 'bar']];
$defaultConfig = ['aliases' => ['foo' => 'bar'], 'services' => [
'bar' => new stdClass(),
'baz' => new stdClass(),
]];
$this->listener = new ServiceListener($this->services, $defaultConfig);
$this->listener->setApplicationServiceManager(
'service_manager',
Expand All @@ -158,13 +170,13 @@ public function testModuleServiceConfigOverridesGlobalConfig()
$this->event->setModuleName(__NAMESPACE__ . '\TestAsset\ServiceProvider');
$this->listener->onLoadModule($this->event);
$this->listener->onLoadModulesPost($this->event);
$expected = ArrayUtils::merge($defaultConfig, $config);
$expected = (new ServiceConfig($expected))->toArray();
$this->assertEquals(
$expected,
$this->listener->getServiceManagerConfig(),
'Default configuration was not overridden'
);

$services = $this->listener->getConfiguredServiceManager();
$this->assertNotSame($this->services, $services);
$this->assertTrue($services->has('config'));
$this->assertTrue($services->has('foo'));
$this->assertNotSame($services->get('foo'), $services->get('bar'));
$this->assertSame($services->get('foo'), $services->get('baz'));
}

public function testModuleReturningServiceConfigConfiguresServiceManager()
Expand Down Expand Up @@ -251,13 +263,11 @@ public function testCreatesPluginManagerBasedOnModuleImplementingSpecifiedProvid
$listener->onLoadModulesPost($this->event);
$this->assertEquals($pluginConfig, $received);

$serviceConfig = $listener->getServiceManagerConfig();
$this->assertArrayHasKey('services', $serviceConfig);
$this->assertArrayHasKey('CustomPluginManager', $serviceConfig['services']);
$this->assertInstanceOf(
TestAsset\CustomPluginManager::class,
$serviceConfig['services']['CustomPluginManager']
);
$configuredServices = $listener->getConfiguredServiceManager();
$this->assertNotSame($services, $configuredServices);
$this->assertTrue($configuredServices->has('CustomPluginManager'));
$plugins = $configuredServices->get('CustomPluginManager');
$this->assertInstanceOf(TestAsset\CustomPluginManager::class, $plugins);
}

public function testCreatesPluginManagerBasedOnModuleDuckTypingSpecifiedProviderInterface()
Expand Down Expand Up @@ -285,13 +295,11 @@ public function testCreatesPluginManagerBasedOnModuleDuckTypingSpecifiedProvider
$listener->onLoadModulesPost($this->event);
$this->assertEquals($pluginConfig, $received);

$serviceConfig = $listener->getServiceManagerConfig();
$this->assertArrayHasKey('services', $serviceConfig);
$this->assertArrayHasKey('CustomPluginManager', $serviceConfig['services']);
$this->assertInstanceOf(
TestAsset\CustomPluginManager::class,
$serviceConfig['services']['CustomPluginManager']
);
$configuredServices = $listener->getConfiguredServiceManager();
$this->assertNotSame($services, $configuredServices);
$this->assertTrue($configuredServices->has('CustomPluginManager'));
$plugins = $configuredServices->get('CustomPluginManager');
$this->assertInstanceOf(TestAsset\CustomPluginManager::class, $plugins);
}

public function testAttachesListenersAtExpectedPriorities()
Expand Down
3 changes: 3 additions & 0 deletions test/Listener/TestAsset/SampleAbstractFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@
namespace ZendTest\ModuleManager\Listener\TestAsset;

use Interop\Container\ContainerInterface;
use stdClass;
use Zend\ServiceManager\Factory\AbstractFactoryInterface;

class SampleAbstractFactory implements AbstractFactoryInterface
{
public function canCreateServiceWithName(ContainerInterface $container, $name)
{
return true;
}

public function __invoke(ContainerInterface $container, $name, array $options = [])
{
return new stdClass;
}
}

0 comments on commit 877eef4

Please sign in to comment.