Skip to content
This repository was archived by the owner on Feb 6, 2020. It is now read-only.

Commit c40bb38

Browse files
author
fhein
committed
Added optimized algorithm for batch addition of new aliases via
configure(). The algorithm suitable for single alias addition showed up to be utterly slow (because of the immense cost of array_intersect). Removed double parethesis.
1 parent 49bfc47 commit c40bb38

File tree

1 file changed

+124
-84
lines changed

1 file changed

+124
-84
lines changed

src/ServiceManager.php

Lines changed: 124 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
2222
use Zend\ServiceManager\Exception\ServiceNotFoundException;
2323

24-
use function \array_keys;
2524
use function \array_merge;
2625
use function \array_merge_recursive;
2726
use function \class_exists;
@@ -222,7 +221,7 @@ public function get($name)
222221
$object = $this->doCreate($resolvedName);
223222

224223
// Cache the object for later, if it is supposed to be shared.
225-
if (($sharedService)) {
224+
if ($sharedService) {
226225
$this->services[$resolvedName] = $object;
227226
}
228227

@@ -334,8 +333,8 @@ public function getAllowOverride()
334333
public function configure(array $config)
335334
{
336335
// This is a bulk update/initial configuration
337-
// So we check all definitions
338-
$this->validateServiceNameConfig($config);
336+
// So we check all definitions upfront
337+
$this->validateServiceNames($config);
339338

340339
if (isset($config['services'])) {
341340
$this->services = $config['services'] + $this->services;
@@ -368,16 +367,15 @@ public function configure(array $config)
368367
$this->shared = $config['shared'] + $this->shared;
369368
}
370369

371-
$aliases = [];
372370
if (isset($config['aliases'])) {
371+
// @todo: Shouldn't that be $config['aliases'] + $this->aliases (or array_merge?)
372+
// Will have to examine.
373373
$aliases = $config['aliases'];
374374
} elseif (! $this->configured && ! empty($this->aliases)) {
375375
$aliases = $this->aliases;
376376
}
377377
if (! empty($aliases)) {
378-
foreach ($aliases as $alias => $target) {
379-
$this->mapAliasToTarget($alias, $target);
380-
}
378+
$this->mapAliasesToTargets($aliases);
381379
}
382380

383381
if (isset($config['shared_by_default'])) {
@@ -418,8 +416,11 @@ public function configure(array $config)
418416
*/
419417
public function setAlias($alias, $target)
420418
{
421-
$this->validateServiceName($alias);
422-
$this->mapAliasToTarget($alias, $target);
419+
if (! isset($this->services[$alias]) || $this->allowOverride) {
420+
$this->mapAliasToTarget($alias, $target);
421+
return;
422+
}
423+
throw new ContainerModificationsNotAllowedException($alias);
423424
}
424425

425426
/**
@@ -443,8 +444,11 @@ public function setInvokableClass($name, $class = null)
443444
*/
444445
public function setFactory($name, $factory)
445446
{
446-
$this->validateServiceName($name);
447-
$this->factories[$name] = $factory;
447+
if (! isset($this->services[$name]) || $this->allowOverride) {
448+
$this->factories[$name] = $factory;
449+
return;
450+
}
451+
throw new ContainerModificationsNotAllowedException($name);
448452
}
449453

450454
/**
@@ -499,8 +503,11 @@ public function addInitializer($initializer)
499503
*/
500504
public function setService($name, $service)
501505
{
502-
$this->validateServiceName($name);
503-
$this->services[$name] = $service;
506+
if (! isset($this->services[$name]) || $this->allowOverride) {
507+
$this->services[$name] = $service;
508+
return;
509+
}
510+
throw new ContainerModificationsNotAllowedException($name);
504511
}
505512

506513
/**
@@ -511,8 +518,11 @@ public function setService($name, $service)
511518
*/
512519
public function setShared($name, $flag)
513520
{
514-
$this->validateServiceName($name);
515-
$this->shared[$name] = (bool) $flag;
521+
if (! isset($this->services[$name]) || $this->allowOverride) {
522+
$this->shared[$name] = (bool) $flag;
523+
return;
524+
}
525+
throw new ContainerModificationsNotAllowedException($name);
516526
}
517527

518528
/**
@@ -537,8 +547,8 @@ private function resolveInitializer($initializer)
537547

538548
if (is_string($initializer)) {
539549
throw new InvalidArgumentException(sprintf(
540-
'An invalid initializer was registered; resolved to class or function "%s" '
541-
. 'which does not exist; please provide a valid function name or class '
550+
'An invalid initializer was registered; resolved to class or function "%s" '
551+
. 'which does not exist; please provide a valid function name or class '
542552
. 'name resolving to an implementation of %s',
543553
$initializer,
544554
Initializer\InitializerInterface::class
@@ -547,8 +557,8 @@ private function resolveInitializer($initializer)
547557

548558
// Otherwise, we have an invalid type.
549559
throw new InvalidArgumentException(sprintf(
550-
'An invalid initializer was registered. Expected a callable, or an instance of '
551-
. '(or string class name resolving to) "%s", '
560+
'An invalid initializer was registered. Expected a callable, or an instance of '
561+
. '(or string class name resolving to) "%s", '
552562
. 'but "%s" was received',
553563
Initializer\InitializerInterface::class,
554564
(is_object($initializer) ? get_class($initializer) : gettype($initializer))
@@ -793,61 +803,6 @@ private function createFactoriesForInvokables(array $invokables)
793803
return $factories;
794804
}
795805

796-
/**
797-
* Determine if a service instance for a given name already exists,
798-
* and if it exists, determine if is it allowed to get overriden.
799-
*
800-
* Validation in the context of this class means, that for
801-
* a given service name we do not have a service instance
802-
* in the cache OR override is explicitly allowed.
803-
*
804-
* @param string $service
805-
* @throws ContainerModificationsNotAllowedException if the
806-
* provided service name is invalid.
807-
*/
808-
private function validateServiceName($service)
809-
{
810-
// Important: Next three lines must kept equal to the three
811-
// lines of validateServiceNameArray (see below) which are marked as code
812-
// duplicate!
813-
if (! isset($this->services[$service]) || $this->allowOverride) {
814-
return;
815-
}
816-
throw new ContainerModificationsNotAllowedException($service);
817-
}
818-
819-
/**
820-
* Determine if a service instance for any of the provided array's
821-
* keys already exists, and if it exists, determine if is it allowed
822-
* to get overriden.
823-
*
824-
* Validation in the context of this class means, that for
825-
* a given service name we do not have a service instance
826-
* in the cache OR override is explicitly allowed.
827-
*
828-
* @param string[] $services
829-
* @param string $type Type of service being checked.
830-
* @throws ContainerModificationsNotAllowedException if any
831-
* array keys is invalid.
832-
*/
833-
private function validateServiceNameArray(array $services)
834-
{
835-
foreach (array_keys($services) as $service) {
836-
// This is a code duplication from validateServiceName (see above).
837-
// validateServiceName is almost a one liner, so we reproduce it
838-
// here for the sake of performance of aggregated service
839-
// manager configurations (we save the overhead the function
840-
// call would produce)
841-
//
842-
// Important: Next three lines MUST kept equal to the first
843-
// three lines of validateServiceName!
844-
if (! isset($this->services[$service]) ?: $this->allowOverride) {
845-
return;
846-
}
847-
throw new ContainerModificationsNotAllowedException($service);
848-
}
849-
}
850-
851806
/**
852807
* Determine if a service for any name provided by a service
853808
* manager configuration(services, aliases, factories, ...)
@@ -862,22 +817,73 @@ private function validateServiceNameArray(array $services)
862817
* @throws ContainerModificationsNotAllowedException if any
863818
* service key is invalid.
864819
*/
865-
private function validateServiceNameConfig(array $config)
820+
private function validateServiceNames(array $config)
866821
{
867822
if ($this->allowOverride || ! $this->configured) {
868823
return;
869824
}
870825

871-
$sections = ['services', 'aliases', 'invokables', 'factories', 'delegators', 'shared'];
826+
if (isset($config['services'])) {
827+
foreach ($config['services'] as $service => $_) {
828+
if (! isset($this->services[$service]) || $this->allowOverride) {
829+
continue;
830+
}
831+
throw new ContainerModificationsNotAllowedException($service);
832+
}
833+
}
834+
835+
if (isset($config['aliases'])) {
836+
foreach ($config['aliases'] as $service => $_) {
837+
if (! isset($this->services[$service]) || $this->allowOverride) {
838+
continue;
839+
}
840+
throw new ContainerModificationsNotAllowedException($service);
841+
}
842+
}
872843

873-
foreach ($sections as $section) {
874-
if (isset($config[$section])) {
875-
$this->validateServiceNameArray($config[$section]);
844+
if (isset($config['invokables'])) {
845+
foreach ($config['invokables'] as $service => $_) {
846+
if (! isset($this->services[$service]) || $this->allowOverride) {
847+
continue;
848+
}
849+
throw new ContainerModificationsNotAllowedException($service);
850+
}
851+
}
852+
853+
if (isset($config['factories'])) {
854+
foreach ($config['factories'] as $service => $_) {
855+
if (! isset($this->services[$service]) || $this->allowOverride) {
856+
continue;
857+
}
858+
throw new ContainerModificationsNotAllowedException($service);
859+
}
860+
}
861+
862+
if (isset($config['delegators'])) {
863+
foreach ($config['delegators'] as $service => $_) {
864+
if (! isset($this->services[$service]) || $this->allowOverride) {
865+
continue;
866+
}
867+
throw new ContainerModificationsNotAllowedException($service);
868+
}
869+
}
870+
871+
if (isset($config['shared'])) {
872+
foreach ($config['shared'] as $service => $_) {
873+
if (! isset($this->services[$service]) || $this->allowOverride) {
874+
continue;
875+
}
876+
throw new ContainerModificationsNotAllowedException($service);
876877
}
877878
}
878879

879880
if (isset($config['lazy_services']['class_map'])) {
880-
$this->validateServiceNameArray($config['lazy_services']['class_map']);
881+
foreach ($config['lazy_services']['class_map'] as $service => $_) {
882+
if (! isset($this->services[$service]) || $this->allowOverride) {
883+
continue;
884+
}
885+
throw new ContainerModificationsNotAllowedException($service);
886+
}
881887
}
882888
}
883889

@@ -909,6 +915,40 @@ private function mapAliasToTarget($alias, $target)
909915
}
910916
}
911917

918+
/**
919+
* Assuming that all provided alias keys are valid resolve them.
920+
*
921+
* This as an adaptation of Tarjan's strongly connected components
922+
* algorithm. We detect cycles as well reduce the graph so that
923+
* each alias key gets associated with the resolved service.
924+
*
925+
* @param string[][] array of alias definitions
926+
*/
927+
private function mapAliasesToTargets($aliases)
928+
{
929+
$tagged = [];
930+
foreach ($aliases as $alias => $target) {
931+
if (! isset($tagged[$alias])) {
932+
$tCursor = $aliases[$alias];
933+
$aCursor = $alias;
934+
$stack = [];
935+
while (isset($aliases[$tCursor])) {
936+
$stack[] = $aCursor;
937+
$aCursor = $tCursor;
938+
if (isset($tagged[$aCursor])) {
939+
throw CyclicAliasException::fromCyclicAlias($alias, $aliases);
940+
}
941+
$tagged[$aCursor] = true;
942+
$tCursor = $aliases[$tCursor];
943+
}
944+
foreach ($stack as $alias) {
945+
$aliases[$alias] = $tCursor;
946+
}
947+
}
948+
}
949+
$this->aliases = $aliases;
950+
}
951+
912952
/**
913953
* Instantiate abstract factories in order to avoid checks during service construction.
914954
*
@@ -938,8 +978,8 @@ private function resolveAbstractFactoryInstance($abstractFactory)
938978
// If we still have a string, we have a class name that does not resolve
939979
if (is_string($abstractFactory)) {
940980
throw new InvalidArgumentException(sprintf(
941-
'An invalid abstract factory was registered; resolved to class "%s" '
942-
. 'which does not exist; please provide a valid class name resolving '
981+
'An invalid abstract factory was registered; resolved to class "%s" '
982+
. 'which does not exist; please provide a valid class name resolving '
943983
. 'to an implementation of %s',
944984
$abstractFactory,
945985
AbstractFactoryInterface::class
@@ -948,7 +988,7 @@ private function resolveAbstractFactoryInstance($abstractFactory)
948988

949989
// Otherwise, we have an invalid type.
950990
throw new InvalidArgumentException(sprintf(
951-
'An invalid abstract factory was registered. Expected an instance of "%s", '
991+
'An invalid abstract factory was registered. Expected an instance of "%s", '
952992
. 'but "%s" was received',
953993
AbstractFactoryInterface::class,
954994
(is_object($abstractFactory) ? get_class($abstractFactory) : gettype($abstractFactory))

0 commit comments

Comments
 (0)