diff --git a/composer.json b/composer.json
index 3b32231e5..ec3e9df39 100644
--- a/composer.json
+++ b/composer.json
@@ -56,7 +56,8 @@
"application",
"sources/application",
"sources/Composer",
- "sources/Controller"
+ "sources/Controller",
+ "sources/Core"
],
"exclude-from-classmap": [
"core/dbobjectsearch.class.php",
diff --git a/datamodels/2.x/itop-welcome-itil/datamodel.itop-welcome-itil.xml b/datamodels/2.x/itop-welcome-itil/datamodel.itop-welcome-itil.xml
index 70534c9ad..2e3e204d8 100644
--- a/datamodels/2.x/itop-welcome-itil/datamodel.itop-welcome-itil.xml
+++ b/datamodels/2.x/itop-welcome-itil/datamodel.itop-welcome-itil.xml
@@ -186,5 +186,10 @@
+ * array(
+ * 'namespaces' => array(
+ * 'Laminas' => '/path/to/Laminas/library',
+ * 'Doctrine' => '/path/to/Doctrine/library',
+ * ),
+ * 'prefixes' => array(
+ * 'Phly_' => '/path/to/Phly/library',
+ * ),
+ * 'fallback_autoloader' => true,
+ * )
+ *
+ *
+ * @param array|\Traversable $options
+ * @throws Exception\InvalidArgumentException
+ * @return StandardAutoloader
+ */
+ public function setOptions($options)
+ {
+ if (! is_array($options) && ! ($options instanceof \Traversable)) {
+ require_once __DIR__ . '/Exception/InvalidArgumentException.php';
+ throw new Exception\InvalidArgumentException('Options must be either an array or Traversable');
+ }
+
+ foreach ($options as $type => $pairs) {
+ switch ($type) {
+ case self::AUTOREGISTER_LAMINAS:
+ if ($pairs) {
+ $this->registerNamespace('Laminas', dirname(__DIR__));
+ }
+ break;
+ case self::LOAD_NS:
+ if (is_array($pairs) || $pairs instanceof \Traversable) {
+ $this->registerNamespaces($pairs);
+ }
+ break;
+ case self::LOAD_PREFIX:
+ if (is_array($pairs) || $pairs instanceof \Traversable) {
+ $this->registerPrefixes($pairs);
+ }
+ break;
+ case self::ACT_AS_FALLBACK:
+ $this->setFallbackAutoloader($pairs);
+ break;
+ default:
+ // ignore
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Set flag indicating fallback autoloader status
+ *
+ * @param bool $flag
+ * @return StandardAutoloader
+ */
+ public function setFallbackAutoloader($flag)
+ {
+ $this->fallbackAutoloaderFlag = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Is this autoloader acting as a fallback autoloader?
+ *
+ * @return bool
+ */
+ public function isFallbackAutoloader()
+ {
+ return $this->fallbackAutoloaderFlag;
+ }
+
+ /**
+ * Register a namespace/directory pair
+ *
+ * @param string $namespace
+ * @param string $directory
+ * @return StandardAutoloader
+ */
+ public function registerNamespace($namespace, $directory)
+ {
+ $namespace = rtrim($namespace, self::NS_SEPARATOR) . self::NS_SEPARATOR;
+ $this->namespaces[$namespace] = $this->normalizeDirectory($directory);
+ return $this;
+ }
+
+ /**
+ * Register many namespace/directory pairs at once
+ *
+ * @param array $namespaces
+ * @throws Exception\InvalidArgumentException
+ * @return StandardAutoloader
+ */
+ public function registerNamespaces($namespaces)
+ {
+ if (! is_array($namespaces) && ! $namespaces instanceof \Traversable) {
+ require_once __DIR__ . '/Exception/InvalidArgumentException.php';
+ throw new Exception\InvalidArgumentException('Namespace pairs must be either an array or Traversable');
+ }
+
+ foreach ($namespaces as $namespace => $directory) {
+ $this->registerNamespace($namespace, $directory);
+ }
+ return $this;
+ }
+
+ /**
+ * Register a prefix/directory pair
+ *
+ * @param string $prefix
+ * @param string $directory
+ * @return StandardAutoloader
+ */
+ public function registerPrefix($prefix, $directory)
+ {
+ $prefix = rtrim($prefix, self::PREFIX_SEPARATOR). self::PREFIX_SEPARATOR;
+ $this->prefixes[$prefix] = $this->normalizeDirectory($directory);
+ return $this;
+ }
+
+ /**
+ * Register many namespace/directory pairs at once
+ *
+ * @param array $prefixes
+ * @throws Exception\InvalidArgumentException
+ * @return StandardAutoloader
+ */
+ public function registerPrefixes($prefixes)
+ {
+ if (! is_array($prefixes) && ! $prefixes instanceof \Traversable) {
+ require_once __DIR__ . '/Exception/InvalidArgumentException.php';
+ throw new Exception\InvalidArgumentException('Prefix pairs must be either an array or Traversable');
+ }
+
+ foreach ($prefixes as $prefix => $directory) {
+ $this->registerPrefix($prefix, $directory);
+ }
+ return $this;
+ }
+
+ /**
+ * Defined by Autoloadable; autoload a class
+ *
+ * @param string $class
+ * @return false|string
+ */
+ public function autoload($class)
+ {
+ $isFallback = $this->isFallbackAutoloader();
+ if (false !== strpos($class, self::NS_SEPARATOR)) {
+ if ($this->loadClass($class, self::LOAD_NS)) {
+ return $class;
+ } elseif ($isFallback) {
+ return $this->loadClass($class, self::ACT_AS_FALLBACK);
+ }
+ return false;
+ }
+ if (false !== strpos($class, self::PREFIX_SEPARATOR)) {
+ if ($this->loadClass($class, self::LOAD_PREFIX)) {
+ return $class;
+ } elseif ($isFallback) {
+ return $this->loadClass($class, self::ACT_AS_FALLBACK);
+ }
+ return false;
+ }
+ if ($isFallback) {
+ return $this->loadClass($class, self::ACT_AS_FALLBACK);
+ }
+ return false;
+ }
+
+ /**
+ * Register the autoloader with spl_autoload
+ *
+ * @return void
+ */
+ public function register()
+ {
+ spl_autoload_register([$this, 'autoload']);
+ }
+
+ /**
+ * Transform the class name to a filename
+ *
+ * @param string $class
+ * @param string $directory
+ * @return string
+ */
+ protected function transformClassNameToFilename($class, $directory)
+ {
+ // $class may contain a namespace portion, in which case we need
+ // to preserve any underscores in that portion.
+ $matches = [];
+ preg_match('/(?P
+ * 'service_manager' => [
+ * 'factories' => [
+ * MyClassWithDependencies::class => ReflectionBasedAbstractFactory::class,
+ * ],
+ * ],
+ *
+ *
+ * The latter approach is more explicit, and also more performant.
+ *
+ * The factory has the following constraints/features:
+ *
+ * - A parameter named `$config` typehinted as an array will receive the
+ * application "config" service (i.e., the merged configuration).
+ * - Parameters type-hinted against array, but not named `$config` will
+ * be injected with an empty array.
+ * - Scalar parameters will result in an exception being thrown, unless
+ * a default value is present; if the default is present, that will be used.
+ * - If a service cannot be found for a given typehint, the factory will
+ * raise an exception detailing this.
+ * - Some services provided by Laminas components do not have
+ * entries based on their class name (for historical reasons); the
+ * factory allows defining a map of these class/interface names to the
+ * corresponding service name to allow them to resolve.
+ *
+ * `$options` passed to the factory are ignored in all cases, as we cannot
+ * make assumptions about which argument(s) they might replace.
+ *
+ * Based on the LazyControllerAbstractFactory from laminas-mvc.
+ */
+class ReflectionBasedAbstractFactory implements AbstractFactoryInterface
+{
+ /**
+ * Maps known classes/interfaces to the service that provides them; only
+ * required for those services with no entry based on the class/interface
+ * name.
+ *
+ * Extend the class if you wish to add to the list.
+ *
+ * Example:
+ *
+ *
+ * [
+ * \Laminas\Filter\FilterPluginManager::class => 'FilterManager',
+ * \Laminas\Validator\ValidatorPluginManager::class => 'ValidatorManager',
+ * ]
+ *
+ *
+ * @var string[]
+ */
+ protected $aliases = [];
+
+ /**
+ * Constructor.
+ *
+ * Allows overriding the internal list of aliases. These should be of the
+ * form `class name => well-known service name`; see the documentation for
+ * the `$aliases` property for details on what is accepted.
+ *
+ * @param string[] $aliases
+ */
+ public function __construct(array $aliases = [])
+ {
+ if (! empty($aliases)) {
+ $this->aliases = $aliases;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return DispatchableInterface
+ */
+ public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
+ {
+ $reflectionClass = new ReflectionClass($requestedName);
+
+ if (null === ($constructor = $reflectionClass->getConstructor())) {
+ return new $requestedName();
+ }
+
+ $reflectionParameters = $constructor->getParameters();
+
+ if (empty($reflectionParameters)) {
+ return new $requestedName();
+ }
+
+ $resolver = $container->has('config')
+ ? $this->resolveParameterWithConfigService($container, $requestedName)
+ : $this->resolveParameterWithoutConfigService($container, $requestedName);
+
+ $parameters = array_map($resolver, $reflectionParameters);
+
+ return new $requestedName(...$parameters);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function canCreate(ContainerInterface $container, $requestedName)
+ {
+ return class_exists($requestedName) && $this->canCallConstructor($requestedName);
+ }
+
+ private function canCallConstructor($requestedName)
+ {
+ $constructor = (new ReflectionClass($requestedName))->getConstructor();
+
+ return $constructor === null || $constructor->isPublic();
+ }
+
+ /**
+ * Resolve a parameter to a value.
+ *
+ * Returns a callback for resolving a parameter to a value, but without
+ * allowing mapping array `$config` arguments to the `config` service.
+ *
+ * @param ContainerInterface $container
+ * @param string $requestedName
+ * @return callable
+ */
+ private function resolveParameterWithoutConfigService(ContainerInterface $container, $requestedName)
+ {
+ /**
+ * @param ReflectionParameter $parameter
+ * @return mixed
+ * @throws ServiceNotFoundException If type-hinted parameter cannot be
+ * resolved to a service in the container.
+ */
+ return function (ReflectionParameter $parameter) use ($container, $requestedName) {
+ return $this->resolveParameter($parameter, $container, $requestedName);
+ };
+ }
+
+ /**
+ * Returns a callback for resolving a parameter to a value, including mapping 'config' arguments.
+ *
+ * Unlike resolveParameter(), this version will detect `$config` array
+ * arguments and have them return the 'config' service.
+ *
+ * @param ContainerInterface $container
+ * @param string $requestedName
+ * @return callable
+ */
+ private function resolveParameterWithConfigService(ContainerInterface $container, $requestedName)
+ {
+ /**
+ * @param ReflectionParameter $parameter
+ * @return mixed
+ * @throws ServiceNotFoundException If type-hinted parameter cannot be
+ * resolved to a service in the container.
+ */
+ return function (ReflectionParameter $parameter) use ($container, $requestedName) {
+ if ($parameter->isArray() && $parameter->getName() === 'config') {
+ return $container->get('config');
+ }
+ return $this->resolveParameter($parameter, $container, $requestedName);
+ };
+ }
+
+ /**
+ * Logic common to all parameter resolution.
+ *
+ * @param ReflectionParameter $parameter
+ * @param ContainerInterface $container
+ * @param string $requestedName
+ * @return mixed
+ * @throws ServiceNotFoundException If type-hinted parameter cannot be
+ * resolved to a service in the container.
+ */
+ private function resolveParameter(ReflectionParameter $parameter, ContainerInterface $container, $requestedName)
+ {
+ if ($parameter->isArray()) {
+ return [];
+ }
+
+ if (! $parameter->getClass()) {
+ if (! $parameter->isDefaultValueAvailable()) {
+ throw new ServiceNotFoundException(sprintf(
+ 'Unable to create service "%s"; unable to resolve parameter "%s" '
+ . 'to a class, interface, or array type',
+ $requestedName,
+ $parameter->getName()
+ ));
+ }
+
+ return $parameter->getDefaultValue();
+ }
+
+ $type = $parameter->getClass()->getName();
+ $type = isset($this->aliases[$type]) ? $this->aliases[$type] : $type;
+
+ if ($container->has($type)) {
+ return $container->get($type);
+ }
+
+ if (! $parameter->isOptional()) {
+ throw new ServiceNotFoundException(sprintf(
+ 'Unable to create service "%s"; unable to resolve parameter "%s" using type hint "%s"',
+ $requestedName,
+ $parameter->getName(),
+ $type
+ ));
+ }
+
+ // Type not available in container, but the value is optional and has a
+ // default defined.
+ return $parameter->getDefaultValue();
+ }
+}
diff --git a/lib/laminas/laminas-servicemanager/src/AbstractFactoryInterface.php b/lib/laminas/laminas-servicemanager/src/AbstractFactoryInterface.php
new file mode 100644
index 000000000..c378e7202
--- /dev/null
+++ b/lib/laminas/laminas-servicemanager/src/AbstractFactoryInterface.php
@@ -0,0 +1,59 @@
+toArray();
+ }
+
+ parent::__construct($config);
+
+ if (! $configInstanceOrParentLocator instanceof ContainerInterface) {
+ trigger_error(sprintf(
+ '%s now expects a %s instance representing the parent container; please update your code',
+ __METHOD__,
+ ContainerInterface::class
+ ), E_USER_DEPRECATED);
+ }
+
+ $this->creationContext = $configInstanceOrParentLocator instanceof ContainerInterface
+ ? $configInstanceOrParentLocator
+ : $this;
+ }
+
+ /**
+ * Override configure() to validate service instances.
+ *
+ * If an instance passed in the `services` configuration is invalid for the
+ * plugin manager, this method will raise an InvalidServiceException.
+ *
+ * {@inheritDoc}
+ * @throws InvalidServiceException
+ */
+ public function configure(array $config)
+ {
+ if (isset($config['services'])) {
+ foreach ($config['services'] as $service) {
+ $this->validate($service);
+ }
+ }
+
+ parent::configure($config);
+
+ return $this;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @param string $name Service name of plugin to retrieve.
+ * @param null|array $options Options to use when creating the instance.
+ * @return mixed
+ * @throws Exception\ServiceNotFoundException if the manager does not have
+ * a service definition for the instance, and the service is not
+ * auto-invokable.
+ * @throws InvalidServiceException if the plugin created is invalid for the
+ * plugin context.
+ */
+ public function get($name, array $options = null)
+ {
+ if (! $this->has($name)) {
+ if (! $this->autoAddInvokableClass || ! class_exists($name)) {
+ throw new Exception\ServiceNotFoundException(sprintf(
+ 'A plugin by the name "%s" was not found in the plugin manager %s',
+ $name,
+ get_class($this)
+ ));
+ }
+
+ $this->setFactory($name, Factory\InvokableFactory::class);
+ }
+
+ $instance = empty($options) ? parent::get($name) : $this->build($name, $options);
+ $this->validate($instance);
+ return $instance;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function validate($instance)
+ {
+ if (method_exists($this, 'validatePlugin')) {
+ trigger_error(sprintf(
+ '%s::validatePlugin() has been deprecated as of 3.0; please define validate() instead',
+ get_class($this)
+ ), E_USER_DEPRECATED);
+ $this->validatePlugin($instance);
+ return;
+ }
+
+ if (empty($this->instanceOf) || $instance instanceof $this->instanceOf) {
+ return;
+ }
+
+ throw new InvalidServiceException(sprintf(
+ 'Plugin manager "%s" expected an instance of type "%s", but "%s" was received',
+ __CLASS__,
+ $this->instanceOf,
+ is_object($instance) ? get_class($instance) : gettype($instance)
+ ));
+ }
+
+ /**
+ * Implemented for backwards compatibility only.
+ *
+ * Returns the creation context.
+ *
+ * @deprecated since 3.0.0. The creation context should be passed during
+ * instantiation instead.
+ * @param ContainerInterface $container
+ * @return void
+ */
+ public function setServiceLocator(ContainerInterface $container)
+ {
+ trigger_error(sprintf(
+ 'Usage of %s is deprecated since v3.0.0; please pass the container to the constructor instead',
+ __METHOD__
+ ), E_USER_DEPRECATED);
+ $this->creationContext = $container;
+ }
+}
diff --git a/lib/laminas/laminas-servicemanager/src/Config.php b/lib/laminas/laminas-servicemanager/src/Config.php
new file mode 100644
index 000000000..8893afa3d
--- /dev/null
+++ b/lib/laminas/laminas-servicemanager/src/Config.php
@@ -0,0 +1,120 @@
+ true,
+ 'aliases' => true,
+ 'delegators' => true,
+ 'factories' => true,
+ 'initializers' => true,
+ 'invokables' => true,
+ 'lazy_services' => true,
+ 'services' => true,
+ 'shared' => true,
+ ];
+
+ /**
+ * @var array
+ */
+ protected $config = [
+ 'abstract_factories' => [],
+ 'aliases' => [],
+ 'delegators' => [],
+ 'factories' => [],
+ 'initializers' => [],
+ 'invokables' => [],
+ 'lazy_services' => [],
+ 'services' => [],
+ 'shared' => [],
+ ];
+
+ /**
+ * @param array $config
+ */
+ public function __construct(array $config = [])
+ {
+ // Only merge keys we're interested in
+ foreach (array_keys($config) as $key) {
+ if (! isset($this->allowedKeys[$key])) {
+ unset($config[$key]);
+ }
+ }
+ $this->config = $this->merge($this->config, $config);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function configureServiceManager(ServiceManager $serviceManager)
+ {
+ return $serviceManager->configure($this->config);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function toArray()
+ {
+ return $this->config;
+ }
+
+ /**
+ * Copy paste from https://github.com/laminas/laminas-stdlib/commit/26fcc32a358aa08de35625736095cb2fdaced090
+ * to keep compatibility with previous version
+ *
+ * @link https://github.com/zendframework/zend-servicemanager/pull/68
+ */
+ private function merge(array $a, array $b)
+ {
+ foreach ($b as $key => $value) {
+ if ($value instanceof MergeReplaceKeyInterface) {
+ $a[$key] = $value->getData();
+ } elseif (isset($a[$key]) || array_key_exists($key, $a)) {
+ if ($value instanceof MergeRemoveKey) {
+ unset($a[$key]);
+ } elseif (is_int($key)) {
+ $a[] = $value;
+ } elseif (is_array($value) && is_array($a[$key])) {
+ $a[$key] = $this->merge($a[$key], $value);
+ } else {
+ $a[$key] = $value;
+ }
+ } else {
+ if (! $value instanceof MergeRemoveKey) {
+ $a[$key] = $value;
+ }
+ }
+ }
+ return $a;
+ }
+}
diff --git a/lib/laminas/laminas-servicemanager/src/ConfigInterface.php b/lib/laminas/laminas-servicemanager/src/ConfigInterface.php
new file mode 100644
index 000000000..37598d22c
--- /dev/null
+++ b/lib/laminas/laminas-servicemanager/src/ConfigInterface.php
@@ -0,0 +1,47 @@
+ $reference) {
+ $map[] = '"' . $alias . '" => "' . $reference . '"';
+ }
+
+ return "[\n" . implode("\n", $map) . "\n]";
+ }
+
+ /**
+ * @param string[][] $detectedCycles
+ *
+ * @return string
+ */
+ private static function printCycles(array $detectedCycles)
+ {
+ return "[\n" . implode("\n", array_map([__CLASS__, 'printCycle'], $detectedCycles)) . "\n]";
+ }
+
+ /**
+ * @param string[] $detectedCycle
+ *
+ * @return string
+ */
+ private static function printCycle(array $detectedCycle)
+ {
+ $fullCycle = array_keys($detectedCycle);
+ $fullCycle[] = reset($fullCycle);
+
+ return implode(
+ ' => ',
+ array_map(
+ function ($cycle) {
+ return '"' . $cycle . '"';
+ },
+ $fullCycle
+ )
+ );
+ }
+
+ /**
+ * @param bool[][] $detectedCycles
+ *
+ * @return bool[][] de-duplicated
+ */
+ private static function deDuplicateDetectedCycles(array $detectedCycles)
+ {
+ $detectedCyclesByHash = [];
+
+ foreach ($detectedCycles as $detectedCycle) {
+ $cycleAliases = array_keys($detectedCycle);
+
+ sort($cycleAliases);
+
+ $hash = serialize(array_values($cycleAliases));
+
+ $detectedCyclesByHash[$hash] = isset($detectedCyclesByHash[$hash])
+ ? $detectedCyclesByHash[$hash]
+ : $detectedCycle;
+ }
+
+ return array_values($detectedCyclesByHash);
+ }
+}
diff --git a/lib/laminas/laminas-servicemanager/src/Exception/ExceptionInterface.php b/lib/laminas/laminas-servicemanager/src/Exception/ExceptionInterface.php
new file mode 100644
index 000000000..f6ad4855c
--- /dev/null
+++ b/lib/laminas/laminas-servicemanager/src/Exception/ExceptionInterface.php
@@ -0,0 +1,18 @@
+proxyFactory = $proxyFactory;
+ $this->servicesMap = $servicesMap;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return \ProxyManager\Proxy\VirtualProxyInterface
+ */
+ public function __invoke(ContainerInterface $container, $name, callable $callback, array $options = null)
+ {
+ if (isset($this->servicesMap[$name])) {
+ $initializer = function (&$wrappedInstance, LazyLoadingInterface $proxy) use ($callback) {
+ $proxy->setProxyInitializer(null);
+ $wrappedInstance = $callback();
+
+ return true;
+ };
+
+ return $this->proxyFactory->createProxy($this->servicesMap[$name], $initializer);
+ }
+
+ throw new Exception\ServiceNotFoundException(
+ sprintf('The requested service "%s" was not found in the provided services map', $name)
+ );
+ }
+}
diff --git a/lib/laminas/laminas-servicemanager/src/PsrContainerDecorator.php b/lib/laminas/laminas-servicemanager/src/PsrContainerDecorator.php
new file mode 100644
index 000000000..cac04f5f8
--- /dev/null
+++ b/lib/laminas/laminas-servicemanager/src/PsrContainerDecorator.php
@@ -0,0 +1,52 @@
+container = $container;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($id)
+ {
+ return $this->container->get($id);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has($id)
+ {
+ return $this->container->has($id);
+ }
+
+ /**
+ * @return PsrContainerInterface
+ */
+ public function getContainer()
+ {
+ return $this->container;
+ }
+}
diff --git a/lib/laminas/laminas-servicemanager/src/ServiceLocatorInterface.php b/lib/laminas/laminas-servicemanager/src/ServiceLocatorInterface.php
new file mode 100644
index 000000000..d6ed8e847
--- /dev/null
+++ b/lib/laminas/laminas-servicemanager/src/ServiceLocatorInterface.php
@@ -0,0 +1,35 @@
+ [
+ * MyService::class => true, // will be shared, even if "sharedByDefault" is false
+ * MyOtherService::class => false // won't be shared, even if "sharedByDefault" is true
+ * ]
+ *
+ * @var boolean[]
+ */
+ protected $shared = [];
+
+ /**
+ * Should the services be shared by default?
+ *
+ * @var bool
+ */
+ protected $sharedByDefault = true;
+
+ /**
+ * Service manager was already configured?
+ *
+ * @var bool
+ */
+ protected $configured = false;
+
+ /**
+ * Cached abstract factories from string.
+ *
+ * @var array
+ */
+ private $cachedAbstractFactories = [];
+
+ /**
+ * Constructor.
+ *
+ * See {@see \Laminas\ServiceManager\ServiceManager::configure()} for details
+ * on what $config accepts.
+ *
+ * @param array $config
+ */
+ public function __construct(array $config = [])
+ {
+ $this->creationContext = $this;
+ $this->configure($config);
+ }
+
+ /**
+ * Implemented for backwards compatibility with previous plugin managers only.
+ *
+ * Returns the creation context.
+ *
+ * @deprecated since 3.0.0. Factories using 3.0 should use the container
+ * instance passed to the factory instead.
+ * @return ContainerInterface
+ */
+ public function getServiceLocator()
+ {
+ trigger_error(sprintf(
+ 'Usage of %s is deprecated since v3.0.0; please use the container passed to the factory instead',
+ __METHOD__
+ ), E_USER_DEPRECATED);
+ return $this->creationContext;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function get($name)
+ {
+ $requestedName = $name;
+
+ // We start by checking if we have cached the requested service (this
+ // is the fastest method).
+ if (isset($this->services[$requestedName])) {
+ return $this->services[$requestedName];
+ }
+
+ $name = isset($this->resolvedAliases[$name]) ? $this->resolvedAliases[$name] : $name;
+
+ // Next, if the alias should be shared, and we have cached the resolved
+ // service, use it.
+ if ($requestedName !== $name
+ && (! isset($this->shared[$requestedName]) || $this->shared[$requestedName])
+ && isset($this->services[$name])
+ ) {
+ $this->services[$requestedName] = $this->services[$name];
+ return $this->services[$name];
+ }
+
+ // At this point, we need to create the instance; we use the resolved
+ // name for that.
+ $object = $this->doCreate($name);
+
+ // Cache it for later, if it is supposed to be shared.
+ if (($this->sharedByDefault && ! isset($this->shared[$name]))
+ || (isset($this->shared[$name]) && $this->shared[$name])
+ ) {
+ $this->services[$name] = $object;
+ }
+
+ // Also do so for aliases; this allows sharing based on service name used.
+ if ($requestedName !== $name
+ && (($this->sharedByDefault && ! isset($this->shared[$requestedName]))
+ || (isset($this->shared[$requestedName]) && $this->shared[$requestedName]))
+ ) {
+ $this->services[$requestedName] = $object;
+ }
+
+ return $object;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function build($name, array $options = null)
+ {
+ // We never cache when using "build"
+ $name = isset($this->resolvedAliases[$name]) ? $this->resolvedAliases[$name] : $name;
+ return $this->doCreate($name, $options);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function has($name)
+ {
+ $name = isset($this->resolvedAliases[$name]) ? $this->resolvedAliases[$name] : $name;
+ $found = isset($this->services[$name]) || isset($this->factories[$name]);
+
+ if ($found) {
+ return $found;
+ }
+
+ // Check abstract factories
+ foreach ($this->abstractFactories as $abstractFactory) {
+ if ($abstractFactory->canCreate($this->creationContext, $name)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Indicate whether or not the instance is immutable.
+ *
+ * @param bool $flag
+ */
+ public function setAllowOverride($flag)
+ {
+ $this->allowOverride = (bool) $flag;
+ }
+
+ /**
+ * Retrieve the flag indicating immutability status.
+ *
+ * @return bool
+ */
+ public function getAllowOverride()
+ {
+ return $this->allowOverride;
+ }
+
+ /**
+ * Configure the service manager
+ *
+ * Valid top keys are:
+ *
+ * - services: service name => service instance pairs
+ * - invokables: service name => class name pairs for classes that do not
+ * have required constructor arguments; internally, maps the class to an
+ * InvokableFactory instance, and creates an alias if the service name
+ * and class name do not match.
+ * - factories: service name => factory pairs; factories may be any
+ * callable, string name resolving to an invokable class, or string name
+ * resolving to a FactoryInterface instance.
+ * - abstract_factories: an array of abstract factories; these may be
+ * instances of AbstractFactoryInterface, or string names resolving to
+ * classes that implement that interface.
+ * - delegators: service name => list of delegator factories for the given
+ * service; each item in the list may be a callable, a string name
+ * resolving to an invokable class, or a string name resolving to a class
+ * implementing DelegatorFactoryInterface.
+ * - shared: service name => flag pairs; the flag is a boolean indicating
+ * whether or not the service is shared.
+ * - aliases: alias => service name pairs.
+ * - lazy_services: lazy service configuration; can contain the keys:
+ * - class_map: service name => class name pairs.
+ * - proxies_namespace: string namespace to use for generated proxy
+ * classes.
+ * - proxies_target_dir: directory in which to write generated proxy
+ * classes; uses system temporary by default.
+ * - write_proxy_files: boolean indicating whether generated proxy
+ * classes should be written; defaults to boolean false.
+ * - shared_by_default: boolean, indicating if services in this instance
+ * should be shared by default.
+ *
+ * @param array $config
+ * @return self
+ * @throws ContainerModificationsNotAllowedException if the allow
+ * override flag has been toggled off, and a service instance
+ * exists for a given service.
+ */
+ public function configure(array $config)
+ {
+ $this->validateOverrides($config);
+
+ if (isset($config['services'])) {
+ $this->services = $config['services'] + $this->services;
+ }
+
+ if (isset($config['invokables']) && ! empty($config['invokables'])) {
+ $aliases = $this->createAliasesForInvokables($config['invokables']);
+ $factories = $this->createFactoriesForInvokables($config['invokables']);
+
+ if (! empty($aliases)) {
+ $config['aliases'] = (isset($config['aliases']))
+ ? array_merge($config['aliases'], $aliases)
+ : $aliases;
+ }
+
+ $config['factories'] = (isset($config['factories']))
+ ? array_merge($config['factories'], $factories)
+ : $factories;
+ }
+
+ if (isset($config['factories'])) {
+ $this->factories = $config['factories'] + $this->factories;
+ }
+
+ if (isset($config['delegators'])) {
+ $this->delegators = array_merge_recursive($this->delegators, $config['delegators']);
+ }
+
+ if (isset($config['shared'])) {
+ $this->shared = $config['shared'] + $this->shared;
+ }
+
+ if (isset($config['aliases'])) {
+ $this->configureAliases($config['aliases']);
+ } elseif (! $this->configured && ! empty($this->aliases)) {
+ $this->resolveAliases($this->aliases);
+ }
+
+ if (isset($config['shared_by_default'])) {
+ $this->sharedByDefault = $config['shared_by_default'];
+ }
+
+ // If lazy service configuration was provided, reset the lazy services
+ // delegator factory.
+ if (isset($config['lazy_services']) && ! empty($config['lazy_services'])) {
+ $this->lazyServices = array_merge_recursive($this->lazyServices, $config['lazy_services']);
+ $this->lazyServicesDelegator = null;
+ }
+
+ // For abstract factories and initializers, we always directly
+ // instantiate them to avoid checks during service construction.
+ if (isset($config['abstract_factories'])) {
+ $this->resolveAbstractFactories($config['abstract_factories']);
+ }
+
+ if (isset($config['initializers'])) {
+ $this->resolveInitializers($config['initializers']);
+ }
+
+ $this->configured = true;
+
+ return $this;
+ }
+
+ /**
+ * @param string[] $aliases
+ *
+ * @return void
+ */
+ private function configureAliases(array $aliases)
+ {
+ if (! $this->configured) {
+ $this->aliases = $aliases + $this->aliases;
+
+ $this->resolveAliases($this->aliases);
+
+ return;
+ }
+
+ // Performance optimization. If there are no collisions, then we don't need to recompute loops
+ $intersecting = $this->aliases && \array_intersect_key($this->aliases, $aliases);
+ $this->aliases = $this->aliases ? \array_merge($this->aliases, $aliases) : $aliases;
+
+ if ($intersecting) {
+ $this->resolveAliases($this->aliases);
+
+ return;
+ }
+
+ $this->resolveAliases($aliases);
+ $this->resolveNewAliasesWithPreviouslyResolvedAliases($aliases);
+ }
+
+ /**
+ * Add an alias.
+ *
+ * @param string $alias
+ * @param string $target
+ */
+ public function setAlias($alias, $target)
+ {
+ $this->configure(['aliases' => [$alias => $target]]);
+ }
+
+ /**
+ * Add an invokable class mapping.
+ *
+ * @param string $name Service name
+ * @param null|string $class Class to which to map; if omitted, $name is
+ * assumed.
+ */
+ public function setInvokableClass($name, $class = null)
+ {
+ $this->configure(['invokables' => [$name => $class ?: $name]]);
+ }
+
+ /**
+ * Specify a factory for a given service name.
+ *
+ * @param string $name Service name
+ * @param string|callable|Factory\FactoryInterface $factory Factory to which
+ * to map.
+ */
+ public function setFactory($name, $factory)
+ {
+ $this->configure(['factories' => [$name => $factory]]);
+ }
+
+ /**
+ * Create a lazy service mapping to a class.
+ *
+ * @param string $name Service name to map
+ * @param null|string $class Class to which to map; if not provided, $name
+ * will be used for the mapping.
+ */
+ public function mapLazyService($name, $class = null)
+ {
+ $this->configure(['lazy_services' => ['class_map' => [$name => $class ?: $name]]]);
+ }
+
+ /**
+ * Add an abstract factory for resolving services.
+ *
+ * @param string|Factory\AbstractFactoryInterface $factory Service name
+ */
+ public function addAbstractFactory($factory)
+ {
+ $this->configure(['abstract_factories' => [$factory]]);
+ }
+
+ /**
+ * Add a delegator for a given service.
+ *
+ * @param string $name Service name
+ * @param string|callable|Factory\DelegatorFactoryInterface $factory Delegator
+ * factory to assign.
+ */
+ public function addDelegator($name, $factory)
+ {
+ $this->configure(['delegators' => [$name => [$factory]]]);
+ }
+
+ /**
+ * Add an initializer.
+ *
+ * @param string|callable|Initializer\InitializerInterface $initializer
+ */
+ public function addInitializer($initializer)
+ {
+ $this->configure(['initializers' => [$initializer]]);
+ }
+
+ /**
+ * Map a service.
+ *
+ * @param string $name Service name
+ * @param array|object $service
+ */
+ public function setService($name, $service)
+ {
+ $this->configure(['services' => [$name => $service]]);
+ }
+
+ /**
+ * Add a service sharing rule.
+ *
+ * @param string $name Service name
+ * @param boolean $flag Whether or not the service should be shared.
+ */
+ public function setShared($name, $flag)
+ {
+ $this->configure(['shared' => [$name => (bool) $flag]]);
+ }
+
+ /**
+ * Instantiate abstract factories for to avoid checks during service construction.
+ *
+ * @param string[]|Factory\AbstractFactoryInterface[] $abstractFactories
+ *
+ * @return void
+ */
+ private function resolveAbstractFactories(array $abstractFactories)
+ {
+ foreach ($abstractFactories as $abstractFactory) {
+ if (is_string($abstractFactory) && class_exists($abstractFactory)) {
+ //Cached string
+ if (! isset($this->cachedAbstractFactories[$abstractFactory])) {
+ $this->cachedAbstractFactories[$abstractFactory] = new $abstractFactory();
+ }
+
+ $abstractFactory = $this->cachedAbstractFactories[$abstractFactory];
+ }
+
+ if ($abstractFactory instanceof Factory\AbstractFactoryInterface) {
+ $abstractFactoryObjHash = spl_object_hash($abstractFactory);
+ $this->abstractFactories[$abstractFactoryObjHash] = $abstractFactory;
+ continue;
+ }
+
+ // Error condition; let's find out why.
+
+ // If we still have a string, we have a class name that does not resolve
+ if (is_string($abstractFactory)) {
+ throw new InvalidArgumentException(
+ sprintf(
+ 'An invalid abstract factory was registered; resolved to class "%s" ' .
+ 'which does not exist; please provide a valid class name resolving ' .
+ 'to an implementation of %s',
+ $abstractFactory,
+ AbstractFactoryInterface::class
+ )
+ );
+ }
+
+ // Otherwise, we have an invalid type.
+ throw new InvalidArgumentException(
+ sprintf(
+ 'An invalid abstract factory was registered. Expected an instance of "%s", ' .
+ 'but "%s" was received',
+ AbstractFactoryInterface::class,
+ (is_object($abstractFactory) ? get_class($abstractFactory) : gettype($abstractFactory))
+ )
+ );
+ }
+ }
+
+ /**
+ * Instantiate initializers for to avoid checks during service construction.
+ *
+ * @param string[]|Initializer\InitializerInterface[]|callable[] $initializers
+ *
+ * @return void
+ */
+ private function resolveInitializers(array $initializers)
+ {
+ foreach ($initializers as $initializer) {
+ if (is_string($initializer) && class_exists($initializer)) {
+ $initializer = new $initializer();
+ }
+
+ if (is_callable($initializer)) {
+ $this->initializers[] = $initializer;
+ continue;
+ }
+
+ // Error condition; let's find out why.
+
+ if (is_string($initializer)) {
+ throw new InvalidArgumentException(
+ sprintf(
+ 'An invalid initializer was registered; resolved to class or function "%s" ' .
+ 'which does not exist; please provide a valid function name or class ' .
+ 'name resolving to an implementation of %s',
+ $initializer,
+ Initializer\InitializerInterface::class
+ )
+ );
+ }
+
+ // Otherwise, we have an invalid type.
+ throw new InvalidArgumentException(
+ sprintf(
+ 'An invalid initializer was registered. Expected a callable, or an instance of ' .
+ '(or string class name resolving to) "%s", ' .
+ 'but "%s" was received',
+ Initializer\InitializerInterface::class,
+ (is_object($initializer) ? get_class($initializer) : gettype($initializer))
+ )
+ );
+ }
+ }
+
+ /**
+ * Resolve aliases to their canonical service names.
+ *
+ * @param string[] $aliases
+ *
+ * @return void
+ */
+ private function resolveAliases(array $aliases)
+ {
+ foreach ($aliases as $alias => $service) {
+ $visited = [];
+ $name = $alias;
+
+ while (isset($this->aliases[$name])) {
+ if (isset($visited[$name])) {
+ throw CyclicAliasException::fromAliasesMap($aliases);
+ }
+
+ $visited[$name] = true;
+ $name = $this->aliases[$name];
+ }
+
+ $this->resolvedAliases[$alias] = $name;
+ }
+ }
+
+ /**
+ * Rewrites the map of aliases by resolving the given $aliases with the existing resolved ones.
+ * This is mostly done for performance reasons.
+ *
+ * @param string[] $aliases
+ *
+ * @return void
+ */
+ private function resolveNewAliasesWithPreviouslyResolvedAliases(array $aliases)
+ {
+ foreach ($this->resolvedAliases as $name => $target) {
+ if (isset($aliases[$target])) {
+ $this->resolvedAliases[$name] = $this->resolvedAliases[$target];
+ }
+ }
+ }
+
+ /**
+ * Get a factory for the given service name
+ *
+ * @param string $name
+ * @return callable
+ * @throws ServiceNotFoundException
+ */
+ private function getFactory($name)
+ {
+ $factory = isset($this->factories[$name]) ? $this->factories[$name] : null;
+
+ $lazyLoaded = false;
+ if (is_string($factory) && class_exists($factory)) {
+ $factory = new $factory();
+ $lazyLoaded = true;
+ }
+
+ if (is_callable($factory)) {
+ if ($lazyLoaded) {
+ $this->factories[$name] = $factory;
+ }
+ // PHP 5.6 fails on 'class::method' callables unless we explode them:
+ if (PHP_MAJOR_VERSION < 7
+ && is_string($factory) && strpos($factory, '::') !== false
+ ) {
+ $factory = explode('::', $factory);
+ }
+ return $factory;
+ }
+
+ // Check abstract factories
+ foreach ($this->abstractFactories as $abstractFactory) {
+ if ($abstractFactory->canCreate($this->creationContext, $name)) {
+ return $abstractFactory;
+ }
+ }
+
+ throw new ServiceNotFoundException(sprintf(
+ 'Unable to resolve service "%s" to a factory; are you certain you provided it during configuration?',
+ $name
+ ));
+ }
+
+ /**
+ * @param string $name
+ * @param null|array $options
+ * @return object
+ */
+ private function createDelegatorFromName($name, array $options = null)
+ {
+ $creationCallback = function () use ($name, $options) {
+ // Code is inlined for performance reason, instead of abstracting the creation
+ $factory = $this->getFactory($name);
+ return $factory($this->creationContext, $name, $options);
+ };
+
+ foreach ($this->delegators[$name] as $index => $delegatorFactory) {
+ $delegatorFactory = $this->delegators[$name][$index];
+
+ if ($delegatorFactory === Proxy\LazyServiceFactory::class) {
+ $delegatorFactory = $this->createLazyServiceDelegatorFactory();
+ }
+
+ if (is_string($delegatorFactory) && class_exists($delegatorFactory)) {
+ $delegatorFactory = new $delegatorFactory();
+ }
+
+ if (! is_callable($delegatorFactory)) {
+ if (is_string($delegatorFactory)) {
+ throw new ServiceNotCreatedException(sprintf(
+ 'An invalid delegator factory was registered; resolved to class or function "%s" '
+ . 'which does not exist; please provide a valid function name or class name resolving '
+ . 'to an implementation of %s',
+ $delegatorFactory,
+ DelegatorFactoryInterface::class
+ ));
+ }
+
+ throw new ServiceNotCreatedException(sprintf(
+ 'A non-callable delegator, "%s", was provided; expected a callable or instance of "%s"',
+ is_object($delegatorFactory) ? get_class($delegatorFactory) : gettype($delegatorFactory),
+ DelegatorFactoryInterface::class
+ ));
+ }
+
+ $this->delegators[$name][$index] = $delegatorFactory;
+
+ $creationCallback = function () use ($delegatorFactory, $name, $creationCallback, $options) {
+ return $delegatorFactory($this->creationContext, $name, $creationCallback, $options);
+ };
+ }
+
+ return $creationCallback($this->creationContext, $name, $creationCallback, $options);
+ }
+
+ /**
+ * Create a new instance with an already resolved name
+ *
+ * This is a highly performance sensitive method, do not modify if you have not benchmarked it carefully
+ *
+ * @param string $resolvedName
+ * @param null|array $options
+ * @return mixed
+ * @throws ServiceNotFoundException if unable to resolve the service.
+ * @throws ServiceNotCreatedException if an exception is raised when
+ * creating a service.
+ * @throws ContainerException if any other error occurs
+ */
+ private function doCreate($resolvedName, array $options = null)
+ {
+ try {
+ if (! isset($this->delegators[$resolvedName])) {
+ // Let's create the service by fetching the factory
+ $factory = $this->getFactory($resolvedName);
+ $object = $factory($this->creationContext, $resolvedName, $options);
+ } else {
+ $object = $this->createDelegatorFromName($resolvedName, $options);
+ }
+ } catch (ContainerException $exception) {
+ throw $exception;
+ } catch (Exception $exception) {
+ throw new ServiceNotCreatedException(sprintf(
+ 'Service with name "%s" could not be created. Reason: %s',
+ $resolvedName,
+ $exception->getMessage()
+ ), (int) $exception->getCode(), $exception);
+ }
+
+ foreach ($this->initializers as $initializer) {
+ $initializer($this->creationContext, $object);
+ }
+
+ return $object;
+ }
+
+ /**
+ * Create the lazy services delegator factory.
+ *
+ * Creates the lazy services delegator factory based on the lazy_services
+ * configuration present.
+ *
+ * @return Proxy\LazyServiceFactory
+ * @throws ServiceNotCreatedException when the lazy service class_map
+ * configuration is missing
+ */
+ private function createLazyServiceDelegatorFactory()
+ {
+ if ($this->lazyServicesDelegator) {
+ return $this->lazyServicesDelegator;
+ }
+
+ if (! isset($this->lazyServices['class_map'])) {
+ throw new ServiceNotCreatedException('Missing "class_map" config key in "lazy_services"');
+ }
+
+ $factoryConfig = new ProxyConfiguration();
+
+ if (isset($this->lazyServices['proxies_namespace'])) {
+ $factoryConfig->setProxiesNamespace($this->lazyServices['proxies_namespace']);
+ }
+
+ if (isset($this->lazyServices['proxies_target_dir'])) {
+ $factoryConfig->setProxiesTargetDir($this->lazyServices['proxies_target_dir']);
+ }
+
+ if (! isset($this->lazyServices['write_proxy_files']) || ! $this->lazyServices['write_proxy_files']) {
+ $factoryConfig->setGeneratorStrategy(new EvaluatingGeneratorStrategy());
+ } else {
+ $factoryConfig->setGeneratorStrategy(new FileWriterGeneratorStrategy(
+ new FileLocator($factoryConfig->getProxiesTargetDir())
+ ));
+ }
+
+ spl_autoload_register($factoryConfig->getProxyAutoloader());
+
+ $this->lazyServicesDelegator = new Proxy\LazyServiceFactory(
+ new LazyLoadingValueHolderFactory($factoryConfig),
+ $this->lazyServices['class_map']
+ );
+
+ return $this->lazyServicesDelegator;
+ }
+
+ /**
+ * Create aliases for invokable classes.
+ *
+ * If an invokable service name does not match the class it maps to, this
+ * creates an alias to the class (which will later be mapped as an
+ * invokable factory).
+ *
+ * @param array $invokables
+ * @return array
+ */
+ private function createAliasesForInvokables(array $invokables)
+ {
+ $aliases = [];
+ foreach ($invokables as $name => $class) {
+ if ($name === $class) {
+ continue;
+ }
+ $aliases[$name] = $class;
+ }
+ return $aliases;
+ }
+
+ /**
+ * Create invokable factories for invokable classes.
+ *
+ * If an invokable service name does not match the class it maps to, this
+ * creates an invokable factory entry for the class name; otherwise, it
+ * creates an invokable factory for the entry name.
+ *
+ * @param array $invokables
+ * @return array
+ */
+ private function createFactoriesForInvokables(array $invokables)
+ {
+ $factories = [];
+ foreach ($invokables as $name => $class) {
+ if ($name === $class) {
+ $factories[$name] = Factory\InvokableFactory::class;
+ continue;
+ }
+
+ $factories[$class] = Factory\InvokableFactory::class;
+ }
+ return $factories;
+ }
+
+ /**
+ * Determine if one or more services already exist in the container.
+ *
+ * If the allow override flag is true or it's first time configured,
+ * this method does nothing.
+ *
+ * Otherwise, it checks against each of the following service types,
+ * if present, and validates that none are defining services that
+ * already exist; if they do, it raises an exception indicating
+ * modification is not allowed.
+ *
+ * @param array $config
+ * @throws ContainerModificationsNotAllowedException if any services
+ * provided already have instances available.
+ */
+ private function validateOverrides(array $config)
+ {
+ if ($this->allowOverride || ! $this->configured) {
+ return;
+ }
+
+ if (isset($config['services'])) {
+ $this->validateOverrideSet(array_keys($config['services']), 'service');
+ }
+
+ if (isset($config['aliases'])) {
+ $this->validateOverrideSet(array_keys($config['aliases']), 'alias');
+ }
+
+ if (isset($config['invokables'])) {
+ $this->validateOverrideSet(array_keys($config['invokables']), 'invokable class');
+ }
+
+ if (isset($config['factories'])) {
+ $this->validateOverrideSet(array_keys($config['factories']), 'factory');
+ }
+
+ if (isset($config['delegators'])) {
+ $this->validateOverrideSet(array_keys($config['delegators']), 'delegator');
+ }
+
+ if (isset($config['shared'])) {
+ $this->validateOverrideSet(array_keys($config['shared']), 'sharing rule');
+ }
+
+ if (isset($config['lazy_services']['class_map'])) {
+ $this->validateOverrideSet(array_keys($config['lazy_services']['class_map']), 'lazy service');
+ }
+ }
+
+ /**
+ * Determine if one or more services already exist for a given type.
+ *
+ * Loops through the provided service names, checking if any have current
+ * service instances; if not, it returns, but otherwise, it raises an
+ * exception indicating modification is not allowed.
+ *
+ * @param string[] $services
+ * @param string $type Type of service being checked.
+ * @throws ContainerModificationsNotAllowedException if any services
+ * provided already have instances available.
+ */
+ private function validateOverrideSet(array $services, $type)
+ {
+ $detected = [];
+ foreach ($services as $service) {
+ if (isset($this->services[$service])) {
+ $detected[] = $service;
+ }
+ }
+
+ if (empty($detected)) {
+ return;
+ }
+
+ throw new ContainerModificationsNotAllowedException(sprintf(
+ 'An updated/new %s is not allowed, as the container does not allow '
+ . 'changes for services with existing instances; the following '
+ . 'already exist in the container: %s',
+ $type,
+ implode(', ', $detected)
+ ));
+ }
+}
diff --git a/lib/laminas/laminas-servicemanager/src/Test/CommonPluginManagerTrait.php b/lib/laminas/laminas-servicemanager/src/Test/CommonPluginManagerTrait.php
new file mode 100644
index 000000000..a736cb05c
--- /dev/null
+++ b/lib/laminas/laminas-servicemanager/src/Test/CommonPluginManagerTrait.php
@@ -0,0 +1,115 @@
+getPluginManager();
+ $reflection = new ReflectionProperty($manager, 'instanceOf');
+ $reflection->setAccessible(true);
+ $this->assertEquals($this->getInstanceOf(), $reflection->getValue($manager), 'instanceOf does not match');
+ }
+
+ public function testShareByDefaultAndSharedByDefault()
+ {
+ $manager = $this->getPluginManager();
+ $reflection = new ReflectionClass($manager);
+ $shareByDefault = $sharedByDefault = true;
+
+ foreach ($reflection->getProperties() as $prop) {
+ if ($prop->getName() == 'shareByDefault') {
+ $prop->setAccessible(true);
+ $shareByDefault = $prop->getValue($manager);
+ }
+ if ($prop->getName() == 'sharedByDefault') {
+ $prop->setAccessible(true);
+ $sharedByDefault = $prop->getValue($manager);
+ }
+ }
+
+ $this->assertTrue(
+ $shareByDefault == $sharedByDefault,
+ 'Values of shareByDefault and sharedByDefault do not match'
+ );
+ }
+
+ public function testRegisteringInvalidElementRaisesException()
+ {
+ $this->expectException($this->getServiceNotFoundException());
+ $this->getPluginManager()->setService('test', $this);
+ }
+
+ public function testLoadingInvalidElementRaisesException()
+ {
+ $manager = $this->getPluginManager();
+ $manager->setInvokableClass('test', get_class($this));
+ $this->expectException($this->getServiceNotFoundException());
+ $manager->get('test');
+ }
+
+ /**
+ * @dataProvider aliasProvider
+ */
+ public function testPluginAliasesResolve($alias, $expected)
+ {
+ $this->assertInstanceOf($expected, $this->getPluginManager()->get($alias), "Alias '$alias' does not resolve'");
+ }
+
+ public function aliasProvider()
+ {
+ $manager = $this->getPluginManager();
+ $reflection = new ReflectionProperty($manager, 'aliases');
+ $reflection->setAccessible(true);
+ $data = [];
+ foreach ($reflection->getValue($manager) as $alias => $expected) {
+ $data[] = [$alias, $expected];
+ }
+ return $data;
+ }
+
+ protected function getServiceNotFoundException()
+ {
+ $manager = $this->getPluginManager();
+ if (method_exists($manager, 'configure')) {
+ return InvalidServiceException::class;
+ }
+ return $this->getV2InvalidPluginException();
+ }
+
+ /**
+ * Returns the plugin manager to test
+ * @return \Laminas\ServiceManager\AbstractPluginManager
+ */
+ abstract protected function getPluginManager();
+
+ /**
+ * Returns the FQCN of the exception thrown under v2 by `validatePlugin()`
+ * @return mixed
+ */
+ abstract protected function getV2InvalidPluginException();
+
+ /**
+ * Returns the value the instanceOf property has been set to
+ * @return string
+ */
+ abstract protected function getInstanceOf();
+}
diff --git a/lib/laminas/laminas-servicemanager/src/Tool/ConfigDumper.php b/lib/laminas/laminas-servicemanager/src/Tool/ConfigDumper.php
new file mode 100644
index 000000000..949b9930d
--- /dev/null
+++ b/lib/laminas/laminas-servicemanager/src/Tool/ConfigDumper.php
@@ -0,0 +1,259 @@
+container = $container;
+ }
+
+ /**
+ * @param array $config
+ * @param string $className
+ * @param bool $ignoreUnresolved
+ * @return array
+ * @throws InvalidArgumentException for invalid $className
+ */
+ public function createDependencyConfig(array $config, $className, $ignoreUnresolved = false)
+ {
+ $this->validateClassName($className);
+
+ $reflectionClass = new ReflectionClass($className);
+
+ // class is an interface; do nothing
+ if ($reflectionClass->isInterface()) {
+ return $config;
+ }
+
+ // class has no constructor, treat it as an invokable
+ if (! $reflectionClass->getConstructor()) {
+ return $this->createInvokable($config, $className);
+ }
+
+ $constructorArguments = $reflectionClass->getConstructor()->getParameters();
+ $constructorArguments = array_filter(
+ $constructorArguments,
+ function (ReflectionParameter $argument) {
+ return ! $argument->isOptional();
+ }
+ );
+
+ // has no required parameters, treat it as an invokable
+ if (empty($constructorArguments)) {
+ return $this->createInvokable($config, $className);
+ }
+
+ $classConfig = [];
+
+ foreach ($constructorArguments as $constructorArgument) {
+ $argumentType = $constructorArgument->getClass();
+ if (is_null($argumentType)) {
+ if ($ignoreUnresolved) {
+ // don't throw an exception, just return the previous config
+ return $config;
+ }
+ // don't throw an exception if the class is an already defined service
+ if ($this->container && $this->container->has($className)) {
+ return $config;
+ }
+ throw new InvalidArgumentException(sprintf(
+ 'Cannot create config for constructor argument "%s", '
+ . 'it has no type hint, or non-class/interface type hint',
+ $constructorArgument->getName()
+ ));
+ }
+ $argumentName = $argumentType->getName();
+ $config = $this->createDependencyConfig($config, $argumentName, $ignoreUnresolved);
+ $classConfig[] = $argumentName;
+ }
+
+ $config[ConfigAbstractFactory::class][$className] = $classConfig;
+
+ return $config;
+ }
+
+ /**
+ * @param $className
+ * @throws InvalidArgumentException if class name is not a string or does
+ * not exist.
+ */
+ private function validateClassName($className)
+ {
+ if (! is_string($className)) {
+ throw new InvalidArgumentException('Class name must be a string, ' . gettype($className) . ' given');
+ }
+
+ if (! class_exists($className) && ! interface_exists($className)) {
+ throw new InvalidArgumentException('Cannot find class or interface with name ' . $className);
+ }
+ }
+
+ /**
+ * @param array $config
+ * @param string $className
+ * @return array
+ */
+ private function createInvokable(array $config, $className)
+ {
+ $config[ConfigAbstractFactory::class][$className] = [];
+ return $config;
+ }
+
+ /**
+ * @param array $config
+ * @return array
+ * @throws InvalidArgumentException if ConfigAbstractFactory configuration
+ * value is not an array.
+ */
+ public function createFactoryMappingsFromConfig(array $config)
+ {
+ if (! array_key_exists(ConfigAbstractFactory::class, $config)) {
+ return $config;
+ }
+
+ if (! is_array($config[ConfigAbstractFactory::class])) {
+ throw new InvalidArgumentException(
+ 'Config key for ' . ConfigAbstractFactory::class . ' should be an array, ' . gettype(
+ $config[ConfigAbstractFactory::class]
+ ) . ' given'
+ );
+ }
+
+ foreach ($config[ConfigAbstractFactory::class] as $className => $dependency) {
+ $config = $this->createFactoryMappings($config, $className);
+ }
+ return $config;
+ }
+
+ /**
+ * @param array $config
+ * @param string $className
+ * @return array
+ */
+ public function createFactoryMappings(array $config, $className)
+ {
+ $this->validateClassName($className);
+
+ if (array_key_exists('service_manager', $config)
+ && array_key_exists('factories', $config['service_manager'])
+ && array_key_exists($className, $config['service_manager']['factories'])
+ ) {
+ return $config;
+ }
+
+ $config['service_manager']['factories'][$className] = ConfigAbstractFactory::class;
+ return $config;
+ }
+
+ /**
+ * @param array $config
+ * @return string
+ */
+ public function dumpConfigFile(array $config)
+ {
+ $prepared = $this->prepareConfig($config);
+ return sprintf(
+ self::CONFIG_TEMPLATE,
+ get_class($this),
+ date('Y-m-d H:i:s'),
+ $prepared
+ );
+ }
+
+ /**
+ * @param array|Traversable $config
+ * @param int $indentLevel
+ * @return string
+ */
+ private function prepareConfig($config, $indentLevel = 1)
+ {
+ $indent = str_repeat(' ', $indentLevel * 4);
+ $entries = [];
+ foreach ($config as $key => $value) {
+ $key = $this->createConfigKey($key);
+ $entries[] = sprintf(
+ '%s%s%s,',
+ $indent,
+ $key ? sprintf('%s => ', $key) : '',
+ $this->createConfigValue($value, $indentLevel)
+ );
+ }
+
+ $outerIndent = str_repeat(' ', ($indentLevel - 1) * 4);
+
+ return sprintf(
+ "[\n%s\n%s]",
+ implode("\n", $entries),
+ $outerIndent
+ );
+ }
+
+ /**
+ * @param string|int|null $key
+ * @return null|string
+ */
+ private function createConfigKey($key)
+ {
+ if (is_string($key) && class_exists($key)) {
+ return sprintf('\\%s::class', $key);
+ }
+
+ if (is_int($key)) {
+ return null;
+ }
+
+ return sprintf("'%s'", $key);
+ }
+
+ /**
+ * @param mixed $value
+ * @param int $indentLevel
+ * @return string
+ */
+ private function createConfigValue($value, $indentLevel)
+ {
+ if (is_array($value) || $value instanceof Traversable) {
+ return $this->prepareConfig($value, $indentLevel + 1);
+ }
+
+ if (is_string($value) && class_exists($value)) {
+ return sprintf('\\%s::class', $value);
+ }
+
+ return var_export($value, true);
+ }
+}
diff --git a/lib/laminas/laminas-servicemanager/src/Tool/ConfigDumperCommand.php b/lib/laminas/laminas-servicemanager/src/Tool/ConfigDumperCommand.php
new file mode 100644
index 000000000..d2b6589a1
--- /dev/null
+++ b/lib/laminas/laminas-servicemanager/src/Tool/ConfigDumperCommand.php
@@ -0,0 +1,229 @@
+Usage:
+
+ %s [-h|--help|help] [-i|--ignore-unresolved]
+ * $list = array('a', 'b', 'c', 'd');
+ * $list = array(
+ * 0 => 'foo',
+ * 1 => 'bar',
+ * 2 => array('foo' => 'baz'),
+ * );
+ *
+ *
+ * @param mixed $value
+ * @param bool $allowEmpty Is an empty list a valid list?
+ * @return bool
+ */
+ public static function isList($value, $allowEmpty = false)
+ {
+ if (! is_array($value)) {
+ return false;
+ }
+
+ if (! $value) {
+ return $allowEmpty;
+ }
+
+ return (array_values($value) === $value);
+ }
+
+ /**
+ * Test whether an array is a hash table.
+ *
+ * An array is a hash table if:
+ *
+ * 1. Contains one or more non-integer keys, or
+ * 2. Integer keys are non-continuous or misaligned (not starting with 0)
+ *
+ * For example:
+ *
+ * $hash = array(
+ * 'foo' => 15,
+ * 'bar' => false,
+ * );
+ * $hash = array(
+ * 1995 => 'Birth of PHP',
+ * 2009 => 'PHP 5.3.0',
+ * 2012 => 'PHP 5.4.0',
+ * );
+ * $hash = array(
+ * 'formElement,
+ * 'options' => array( 'debug' => true ),
+ * );
+ *
+ *
+ * @param mixed $value
+ * @param bool $allowEmpty Is an empty array() a valid hash table?
+ * @return bool
+ */
+ public static function isHashTable($value, $allowEmpty = false)
+ {
+ if (! is_array($value)) {
+ return false;
+ }
+
+ if (! $value) {
+ return $allowEmpty;
+ }
+
+ return (array_values($value) !== $value);
+ }
+
+ /**
+ * Checks if a value exists in an array.
+ *
+ * Due to "foo" == 0 === TRUE with in_array when strict = false, an option
+ * has been added to prevent this. When $strict = 0/false, the most secure
+ * non-strict check is implemented. if $strict = -1, the default in_array
+ * non-strict behaviour is used.
+ *
+ * @param mixed $needle
+ * @param array $haystack
+ * @param int|bool $strict
+ * @return bool
+ */
+ public static function inArray($needle, array $haystack, $strict = false)
+ {
+ if (! $strict) {
+ if (is_int($needle) || is_float($needle)) {
+ $needle = (string) $needle;
+ }
+ if (is_string($needle)) {
+ foreach ($haystack as &$h) {
+ if (is_int($h) || is_float($h)) {
+ $h = (string) $h;
+ }
+ }
+ }
+ }
+ return in_array($needle, $haystack, $strict);
+ }
+
+ /**
+ * Convert an iterator to an array.
+ *
+ * Converts an iterator to an array. The $recursive flag, on by default,
+ * hints whether or not you want to do so recursively.
+ *
+ * @param array|Traversable $iterator The array or Traversable object to convert
+ * @param bool $recursive Recursively check all nested structures
+ * @throws Exception\InvalidArgumentException if $iterator is not an array or a Traversable object
+ * @return array
+ */
+ public static function iteratorToArray($iterator, $recursive = true)
+ {
+ if (! is_array($iterator) && ! $iterator instanceof Traversable) {
+ throw new Exception\InvalidArgumentException(__METHOD__ . ' expects an array or Traversable object');
+ }
+
+ if (! $recursive) {
+ if (is_array($iterator)) {
+ return $iterator;
+ }
+
+ return iterator_to_array($iterator);
+ }
+
+ if (method_exists($iterator, 'toArray')) {
+ return $iterator->toArray();
+ }
+
+ $array = [];
+ foreach ($iterator as $key => $value) {
+ if (is_scalar($value)) {
+ $array[$key] = $value;
+ continue;
+ }
+
+ if ($value instanceof Traversable) {
+ $array[$key] = static::iteratorToArray($value, $recursive);
+ continue;
+ }
+
+ if (is_array($value)) {
+ $array[$key] = static::iteratorToArray($value, $recursive);
+ continue;
+ }
+
+ $array[$key] = $value;
+ }
+
+ return $array;
+ }
+
+ /**
+ * Merge two arrays together.
+ *
+ * If an integer key exists in both arrays and preserveNumericKeys is false, the value
+ * from the second array will be appended to the first array. If both values are arrays, they
+ * are merged together, else the value of the second array overwrites the one of the first array.
+ *
+ * @param array $a
+ * @param array $b
+ * @param bool $preserveNumericKeys
+ * @return array
+ */
+ public static function merge(array $a, array $b, $preserveNumericKeys = false)
+ {
+ foreach ($b as $key => $value) {
+ if ($value instanceof MergeReplaceKeyInterface) {
+ $a[$key] = $value->getData();
+ } elseif (isset($a[$key]) || array_key_exists($key, $a)) {
+ if ($value instanceof MergeRemoveKey) {
+ unset($a[$key]);
+ } elseif (! $preserveNumericKeys && is_int($key)) {
+ $a[] = $value;
+ } elseif (is_array($value) && is_array($a[$key])) {
+ $a[$key] = static::merge($a[$key], $value, $preserveNumericKeys);
+ } else {
+ $a[$key] = $value;
+ }
+ } else {
+ if (! $value instanceof MergeRemoveKey) {
+ $a[$key] = $value;
+ }
+ }
+ }
+
+ return $a;
+ }
+
+ /**
+ * @deprecated Since 3.2.0; use the native array_filter methods
+ *
+ * @param array $data
+ * @param callable $callback
+ * @param null|int $flag
+ * @return array
+ * @throws Exception\InvalidArgumentException
+ */
+ public static function filter(array $data, $callback, $flag = null)
+ {
+ if (! is_callable($callback)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Second parameter of %s must be callable',
+ __METHOD__
+ ));
+ }
+
+ return array_filter($data, $callback, $flag);
+ }
+}
diff --git a/lib/laminas/laminas-stdlib/src/ArrayUtils/MergeRemoveKey.php b/lib/laminas/laminas-stdlib/src/ArrayUtils/MergeRemoveKey.php
new file mode 100644
index 000000000..8c9d56e69
--- /dev/null
+++ b/lib/laminas/laminas-stdlib/src/ArrayUtils/MergeRemoveKey.php
@@ -0,0 +1,13 @@
+data = $data;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getData()
+ {
+ return $this->data;
+ }
+}
diff --git a/lib/laminas/laminas-stdlib/src/ArrayUtils/MergeReplaceKeyInterface.php b/lib/laminas/laminas-stdlib/src/ArrayUtils/MergeReplaceKeyInterface.php
new file mode 100644
index 000000000..54c244382
--- /dev/null
+++ b/lib/laminas/laminas-stdlib/src/ArrayUtils/MergeReplaceKeyInterface.php
@@ -0,0 +1,20 @@
+message`,
+ * `