PK [,QK8%
LICENSE.mdnu ٘ Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
- Neither the name of Laminas Foundation nor the names of its contributors may
be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
PK [,QF[ [ COPYRIGHT.mdnu ٘ Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. (https://getlaminas.org/)
PK [,Q-Op .travis.ymlnu ٘ language: php
cache:
directories:
- $HOME/.composer/cache
env:
global:
- COMPOSER_ARGS="--no-interaction"
- COVERAGE_DEPS="php-coveralls/php-coveralls"
matrix:
fast_finish: true
include:
- php: 7.4
env:
- DEPS=lowest
- php: 7.4
env:
- DEPS=latest
- CS_CHECK=true
- BENCHMARKS=true
- TEST_COVERAGE=true
- php: nightly
env:
- DEPS=lowest
- php: nightly
env:
- DEPS=latest
allow_failures:
- php: nightly
before_install:
- if [[ $TEST_COVERAGE != 'true' ]]; then phpenv config-rm xdebug.ini || return 0 ; fi
install:
- travis_retry composer install $COMPOSER_ARGS
- if [[ $DEPS == 'latest' ]]; then travis_retry composer update $COMPOSER_ARGS ; fi
- if [[ $DEPS == 'lowest' ]]; then travis_retry composer update --prefer-lowest --prefer-stable $COMPOSER_ARGS ; fi
- if [[ $TEST_COVERAGE == 'true' ]]; then travis_retry composer require --dev $COMPOSER_ARGS $COVERAGE_DEPS ; fi
- stty cols 120 && composer show
script:
- if [[ $TEST_COVERAGE == 'true' ]]; then composer test-coverage ; else composer test ; fi
- if [[ $BENCHMARKS == 'true' ]]; then vendor/bin/phpbench run --revs=2 --iterations=2 --report=aggregate ; fi
- if [[ $CS_CHECK == 'true' ]]; then composer cs-check ; fi
after_script:
- if [[ $TEST_COVERAGE == 'true' ]]; then vendor/bin/php-coveralls -v ; fi
notifications:
email: false
PK [,Qx= = .coveralls.ymlnu ٘ coverage_clover: clover.xml
json_path: coveralls-upload.json
PK [,QF. phpcs.xmlnu ٘
bin
src
test
benchmarks
test/log/
test/TestAsset/factories/SimpleDependencyObject.php
PK [,Q.p p
phpbench.jsonnu ٘ {
"bootstrap": "vendor/autoload.php",
"path": "benchmarks",
"retry_threshold": 5
}
PK [,QtIq q README.mdnu ٘ # laminas-servicemanager
Master:
[![Build Status](https://travis-ci.com/laminas/laminas-servicemanager.svg?branch=master)](https://travis-ci.com/laminas/laminas-servicemanager)
[![Coverage Status](https://coveralls.io/repos/github/laminas/laminas-servicemanager/badge.svg?branch=master)](https://coveralls.io/github/laminas/laminas-servicemanager?branch=master)
Develop:
[![Build Status](https://travis-ci.com/laminas/laminas-servicemanager.svg?branch=develop)](https://travis-ci.com/laminas/laminas-servicemanager)
[![Coverage Status](https://coveralls.io/repos/github/laminas/laminas-servicemanager/badge.svg?branch=develop)](https://coveralls.io/github/laminas/laminas-servicemanager?branch=develop)
The Service Locator design pattern is implemented by the `Laminas\ServiceManager`
component. The Service Locator is a service/object locator, tasked with
retrieving other objects.
- File issues at https://github.com/laminas/laminas-servicemanager/issues
- [Online documentation](https://docs.laminas.dev/laminas-servicemanager)
- [Documentation source files](docs/book/)
## Benchmarks
We provide scripts for benchmarking laminas-servicemanager using the
[PHPBench](https://github.com/phpbench/phpbench) framework; these can be
found in the `benchmarks/` directory.
To execute the benchmarks you can run the following command:
```bash
$ vendor/bin/phpbench run --report=aggregate
```
PK [,Q@ݫ
.gitignorenu ٘ /clover.xml
/composer.lock
/coveralls-upload.json
/docs/html/
/laminas-mkdoc-theme.tgz
/laminas-mkdoc-theme/
/phpunit.xml
/vendor/
PK [,QfI src/AbstractFactoryInterface.phpnu ٘ '. $cursor;
}
$cycle .= ' -> ' . $alias . "\n";
return new self(sprintf(
"A cycle was detected within the aliases definitions:\n%s",
$cycle
));
}
/**
* @param string[] $aliases map of referenced services, indexed by alias name (string)
* @return self
*/
public static function fromAliasesMap(array $aliases)
{
$detectedCycles = array_filter(array_map(
function ($alias) use ($aliases) {
return self::getCycleFor($aliases, $alias);
},
array_keys($aliases)
));
if (! $detectedCycles) {
return new self(sprintf(
"A cycle was detected within the following aliases map:\n\n%s",
self::printReferencesMap($aliases)
));
}
return new self(sprintf(
"Cycles were detected within the provided aliases:\n\n%s\n\n"
. "The cycle was detected in the following alias map:\n\n%s",
self::printCycles(self::deDuplicateDetectedCycles($detectedCycles)),
self::printReferencesMap($aliases)
));
}
/**
* Retrieves the cycle detected for the given $alias, or `null` if no cycle was detected
*
* @param string[] $aliases
* @param string $alias
* @return array|null
*/
private static function getCycleFor(array $aliases, $alias)
{
$cycleCandidate = [];
$targetName = $alias;
while (isset($aliases[$targetName])) {
if (isset($cycleCandidate[$targetName])) {
return $cycleCandidate;
}
$cycleCandidate[$targetName] = true;
$targetName = $aliases[$targetName];
}
return null;
}
/**
* @param string[] $aliases
* @return string
*/
private static function printReferencesMap(array $aliases)
{
$map = [];
foreach ($aliases as $alias => $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);
}
}
PK [,Q#\>5 5 $ src/Exception/ExceptionInterface.phpnu ٘ 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)
);
}
}
PK [,Ql l ! src/DelegatorFactoryInterface.phpnu ٘ 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;
}
/**
* Override setService for additional plugin validation.
*
* {@inheritDoc}
*/
public function setService($name, $service)
{
$this->validate($service);
parent::setService($name, $service);
}
/**
* {@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;
}
}
PK [,Qo=S S - src/AbstractFactory/ConfigAbstractFactory.phpnu ٘ has('config')) {
return false;
}
$config = $container->get('config');
if (! isset($config[self::class])) {
return false;
}
$dependencies = $config[self::class];
return is_array($dependencies) && array_key_exists($requestedName, $dependencies);
}
/**
* {@inheritDoc}
*/
public function __invoke(\Psr\Container\ContainerInterface $container, $requestedName, array $options = null)
{
if (! $container->has('config')) {
throw new ServiceNotCreatedException('Cannot find a config array in the container');
}
$config = $container->get('config');
if (! (is_array($config) || $config instanceof ArrayObject)) {
throw new ServiceNotCreatedException('Config must be an array or an instance of ArrayObject');
}
if (! isset($config[self::class])) {
throw new ServiceNotCreatedException('Cannot find a `' . self::class . '` key in the config array');
}
$dependencies = $config[self::class];
if (! is_array($dependencies)
|| ! array_key_exists($requestedName, $dependencies)
|| ! is_array($dependencies[$requestedName])
) {
throw new ServiceNotCreatedException('Service dependencies config must exist and be an array');
}
$serviceDependencies = $dependencies[$requestedName];
if ($serviceDependencies !== array_values(array_map('strval', $serviceDependencies))) {
$problem = json_encode(array_map('gettype', $serviceDependencies));
throw new ServiceNotCreatedException(
'Service dependencies config must be an array of strings, ' . $problem . ' given'
);
}
$arguments = array_map([$container, 'get'], $serviceDependencies);
return new $requestedName(...$arguments);
}
}
PK [,QZQ! ! 6 src/AbstractFactory/ReflectionBasedAbstractFactory.phpnu ٘
* 'service_manager' => [
* 'abstract_factories' => [
* ReflectionBasedAbstractFactory::class,
* ],
* ],
*
*
* Or as a factory, mapping a class name to it:
*
*
* '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(string $requestedName) : bool
{
$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 = $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();
}
}
PK [,QJm m src/ServiceLocatorInterface.phpnu ٘ 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();
}
PK [,Q gJ src/PluginManagerInterface.phpnu ٘ [
* 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)
{
// We start by checking if we have cached the requested service;
// this is the fastest method.
if (isset($this->services[$name])) {
return $this->services[$name];
}
// Determine if the service should be shared.
$sharedService = isset($this->shared[$name]) ? $this->shared[$name] : $this->sharedByDefault;
// We achieve better performance if we can let all alias
// considerations out.
if (! $this->aliases) {
$object = $this->doCreate($name);
// Cache the object for later, if it is supposed to be shared.
if ($sharedService) {
$this->services[$name] = $object;
}
return $object;
}
// We now deal with requests which may be aliases.
$resolvedName = isset($this->aliases[$name]) ? $this->aliases[$name] : $name;
// The following is only true if the requested service is a shared alias.
$sharedAlias = $sharedService && isset($this->services[$resolvedName]);
// If the alias is configured as a shared service, we are done.
if ($sharedAlias) {
$this->services[$name] = $this->services[$resolvedName];
return $this->services[$resolvedName];
}
// At this point, we have to create the object.
// We use the resolved name for that.
$object = $this->doCreate($resolvedName);
// Cache the object for later, if it is supposed to be shared.
if ($sharedService) {
$this->services[$resolvedName] = $object;
}
// Also cache under the alias name; this allows sharing based on the
// service name used.
if ($sharedAlias) {
$this->services[$name] = $object;
}
return $object;
}
/**
* {@inheritDoc}
*/
public function build($name, array $options = null)
{
// We never cache when using "build".
$name = $this->aliases[$name] ?? $name;
return $this->doCreate($name, $options);
}
/**
* {@inheritDoc}
*/
public function has($name)
{
// Check services and factories first to speedup the most common requests.
if (isset($this->services[$name]) || isset($this->factories[$name])) {
return true;
}
// Check abstract factories next.
foreach ($this->abstractFactories as $abstractFactory) {
if ($abstractFactory->canCreate($this->creationContext, $name)) {
return true;
}
}
// If $name is not an alias, we are done.
if (! isset($this->aliases[$name])) {
return false;
}
// Check aliases.
$resolvedName = $this->aliases[$name];
if (isset($this->services[$resolvedName]) || isset($this->factories[$resolvedName])) {
return true;
}
// Check abstract factories on the $resolvedName as well.
foreach ($this->abstractFactories as $abstractFactory) {
if ($abstractFactory->canCreate($this->creationContext, $resolvedName)) {
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 is a bulk update/initial configuration,
// so we check all definitions up front.
$this->validateServiceNames($config);
if (isset($config['services'])) {
$this->services = $config['services'] + $this->services;
}
if (isset($config['invokables']) && ! empty($config['invokables'])) {
$this->createAliasesAndFactoriesForInvokables($config['invokables']);
}
if (isset($config['factories'])) {
$this->factories = $config['factories'] + $this->factories;
}
if (isset($config['delegators'])) {
$this->mergeDelegators($config['delegators']);
}
if (isset($config['shared'])) {
$this->shared = $config['shared'] + $this->shared;
}
if (! empty($config['aliases'])) {
$this->aliases = $config['aliases'] + $this->aliases;
$this->mapAliasesToTargets();
} elseif (! $this->configured && ! empty($this->aliases)) {
$this->mapAliasesToTargets();
}
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 = ArrayUtils::merge($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'])) {
$abstractFactories = $config['abstract_factories'];
// $key not needed, but foreach is faster than foreach + array_values.
foreach ($abstractFactories as $key => $abstractFactory) {
$this->resolveAbstractFactoryInstance($abstractFactory);
}
}
if (isset($config['initializers'])) {
$this->resolveInitializers($config['initializers']);
}
$this->configured = true;
return $this;
}
/**
* Add an alias.
*
* @param string $alias
* @param string $target
* @throws ContainerModificationsNotAllowedException if $alias already
* exists as a service and overrides are disallowed.
*/
public function setAlias($alias, $target)
{
if (isset($this->services[$alias]) && ! $this->allowOverride) {
throw ContainerModificationsNotAllowedException::fromExistingService($alias);
}
$this->mapAliasToTarget($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.
* @throws ContainerModificationsNotAllowedException if $name already
* exists as a service and overrides are disallowed.
*/
public function setInvokableClass($name, $class = null)
{
if (isset($this->services[$name]) && ! $this->allowOverride) {
throw ContainerModificationsNotAllowedException::fromExistingService($name);
}
$this->createAliasesAndFactoriesForInvokables([$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.
* @throws ContainerModificationsNotAllowedException if $name already
* exists as a service and overrides are disallowed.
*/
public function setFactory($name, $factory)
{
if (isset($this->services[$name]) && ! $this->allowOverride) {
throw ContainerModificationsNotAllowedException::fromExistingService($name);
}
$this->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 Abstract factory
* instance or class name.
*/
public function addAbstractFactory($factory)
{
$this->resolveAbstractFactoryInstance($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
* @throws ContainerModificationsNotAllowedException if $name already
* exists as a service and overrides are disallowed.
*/
public function setService($name, $service)
{
if (isset($this->services[$name]) && ! $this->allowOverride) {
throw ContainerModificationsNotAllowedException::fromExistingService($name);
}
$this->services[$name] = $service;
}
/**
* Add a service sharing rule.
*
* @param string $name Service name
* @param boolean $flag Whether or not the service should be shared.
* @throws ContainerModificationsNotAllowedException if $name already
* exists as a service and overrides are disallowed.
*/
public function setShared($name, $flag)
{
if (isset($this->services[$name]) && ! $this->allowOverride) {
throw ContainerModificationsNotAllowedException::fromExistingService($name);
}
$this->shared[$name] = (bool) $flag;
}
/**
* Instantiate initializers for to avoid checks during service construction.
*
* @param string[]|Initializer\InitializerInterface[]|callable[] $initializers
*/
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;
return;
}
throw InvalidArgumentException::fromInvalidInitializer($initializer);
}
}
/**
* Get a factory for the given service name
*
* @param string $name
* @return callable
* @throws ServiceNotFoundException
*/
private function getFactory($name)
{
$factory = $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;
}
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 ExceptionInterface 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 (ExceptionInterface $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;
}
/**
* Merge delegators avoiding multiple same delegators for the same service.
* It works with strings and class instances.
* It's not possible to de-duple anonymous functions
*
* @param string[][]|Factory\DelegatorFactoryInterface[][] $config
* @return string[][]|Factory\DelegatorFactoryInterface[][]
*/
private function mergeDelegators(array $config)
{
foreach ($config as $key => $delegators) {
if (! array_key_exists($key, $this->delegators)) {
$this->delegators[$key] = $delegators;
continue;
}
foreach ($delegators as $delegator) {
if (! in_array($delegator, $this->delegators[$key], true)) {
$this->delegators[$key][] = $delegator;
}
}
}
return $this->delegators;
}
/**
* Create aliases and factories 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
*/
private function createAliasesAndFactoriesForInvokables(array $invokables)
{
foreach ($invokables as $name => $class) {
$this->factories[$class] = Factory\InvokableFactory::class;
if ($name !== $class) {
$this->aliases[$name] = $class;
}
}
}
/**
* Determine if a service for any name provided by a service
* manager configuration(services, aliases, factories, ...)
* already exists, and if it exists, determine if is it allowed
* to get overriden.
*
* Validation in the context of this class means, that for
* a given service name we do not have a service instance
* in the cache OR override is explicitly allowed.
*
* @param array $config
* @throws ContainerModificationsNotAllowedException if any
* service key is invalid.
*/
private function validateServiceNames(array $config)
{
if ($this->allowOverride || ! $this->configured) {
return;
}
if (isset($config['services'])) {
foreach ($config['services'] as $service => $_) {
if (isset($this->services[$service]) && ! $this->allowOverride) {
throw ContainerModificationsNotAllowedException::fromExistingService($service);
}
}
}
if (isset($config['aliases'])) {
foreach ($config['aliases'] as $service => $_) {
if (isset($this->services[$service]) && ! $this->allowOverride) {
throw ContainerModificationsNotAllowedException::fromExistingService($service);
}
}
}
if (isset($config['invokables'])) {
foreach ($config['invokables'] as $service => $_) {
if (isset($this->services[$service]) && ! $this->allowOverride) {
throw ContainerModificationsNotAllowedException::fromExistingService($service);
}
}
}
if (isset($config['factories'])) {
foreach ($config['factories'] as $service => $_) {
if (isset($this->services[$service]) && ! $this->allowOverride) {
throw ContainerModificationsNotAllowedException::fromExistingService($service);
}
}
}
if (isset($config['delegators'])) {
foreach ($config['delegators'] as $service => $_) {
if (isset($this->services[$service]) && ! $this->allowOverride) {
throw ContainerModificationsNotAllowedException::fromExistingService($service);
}
}
}
if (isset($config['shared'])) {
foreach ($config['shared'] as $service => $_) {
if (isset($this->services[$service]) && ! $this->allowOverride) {
throw ContainerModificationsNotAllowedException::fromExistingService($service);
}
}
}
if (isset($config['lazy_services']['class_map'])) {
foreach ($config['lazy_services']['class_map'] as $service => $_) {
if (isset($this->services[$service]) && ! $this->allowOverride) {
throw ContainerModificationsNotAllowedException::fromExistingService($service);
}
}
}
}
/**
* Assuming that the alias name is valid (see above) resolve/add it.
*
* This is done differently from bulk mapping aliases for performance reasons, as the
* algorithms for mapping a single item efficiently are different from those of mapping
* many.
*
* @see mapAliasesToTargets() below
*
* @param string $alias
* @param string $target
*/
private function mapAliasToTarget($alias, $target)
{
// $target is either an alias or something else
// if it is an alias, resolve it
$this->aliases[$alias] = $this->aliases[$target] ?? $target;
// a self-referencing alias indicates a cycle
if ($alias === $this->aliases[$alias]) {
throw CyclicAliasException::fromCyclicAlias($alias, $this->aliases);
}
// finally we have to check if existing incomplete alias definitions
// exist which can get resolved by the new alias
if (in_array($alias, $this->aliases)) {
$r = array_intersect($this->aliases, [ $alias ]);
// found some, resolve them
foreach ($r as $name => $service) {
$this->aliases[$name] = $target;
}
}
}
/**
* Assuming that all provided alias keys are valid resolve them.
*
* This function maps $this->aliases in place.
*
* This algorithm is an adaptated version of Tarjans Strongly
* Connected Components. Instead of returning the strongly
* connected components (i.e. cycles in our case), we throw.
* If nodes are not strongly connected (i.e. resolvable in
* our case), they get resolved.
*
* This algorithm is fast for mass updates through configure().
* It is not appropriate if just a single alias is added.
*
* @see mapAliasToTarget above
*
*/
private function mapAliasesToTargets()
{
$tagged = [];
foreach ($this->aliases as $alias => $target) {
if (isset($tagged[$alias])) {
continue;
}
$tCursor = $this->aliases[$alias];
$aCursor = $alias;
if ($aCursor === $tCursor) {
throw CyclicAliasException::fromCyclicAlias($alias, $this->aliases);
}
if (! isset($this->aliases[$tCursor])) {
continue;
}
$stack = [];
while (isset($this->aliases[$tCursor])) {
$stack[] = $aCursor;
if ($aCursor === $this->aliases[$tCursor]) {
throw CyclicAliasException::fromCyclicAlias($alias, $this->aliases);
}
$aCursor = $tCursor;
$tCursor = $this->aliases[$tCursor];
}
$tagged[$aCursor] = true;
foreach ($stack as $alias) {
if ($alias === $tCursor) {
throw CyclicAliasException::fromCyclicAlias($alias, $this->aliases);
}
$this->aliases[$alias] = $tCursor;
$tagged[$alias] = true;
}
}
}
/**
* Instantiate abstract factories in order to avoid checks during service construction.
*
* @param string|Factory\AbstractFactoryInterface $abstractFactories
* @return void
*/
private function resolveAbstractFactoryInstance($abstractFactory)
{
if (is_string($abstractFactory) && class_exists($abstractFactory)) {
// Cached string factory name
if (! isset($this->cachedAbstractFactories[$abstractFactory])) {
$this->cachedAbstractFactories[$abstractFactory] = new $abstractFactory();
}
$abstractFactory = $this->cachedAbstractFactories[$abstractFactory];
}
if (! $abstractFactory instanceof Factory\AbstractFactoryInterface) {
throw InvalidArgumentException::fromInvalidAbstractFactory($abstractFactory);
}
$abstractFactoryObjHash = spl_object_hash($abstractFactory);
$this->abstractFactories[$abstractFactoryObjHash] = $abstractFactory;
}
}
PK [,Q]
src/FactoryInterface.phpnu ٘ getClassName($className);
return sprintf(
self::FACTORY_TEMPLATE,
str_replace('\\' . $class, '', $className),
$this->createImportStatements($className),
$class,
$class,
$class,
$this->createArgumentString($className)
);
}
/**
* @param $className
* @return string
*/
private function getClassName($className)
{
$class = substr($className, strrpos($className, '\\') + 1);
return $class;
}
/**
* @param string $className
* @return array
*/
private function getConstructorParameters($className)
{
$reflectionClass = new ReflectionClass($className);
if (! $reflectionClass || ! $reflectionClass->getConstructor()) {
return [];
}
$constructorParameters = $reflectionClass->getConstructor()->getParameters();
if (empty($constructorParameters)) {
return [];
}
$constructorParameters = array_filter(
$constructorParameters,
function (ReflectionParameter $argument) {
if ($argument->isOptional()) {
return false;
}
if (null === $argument->getClass()) {
throw new InvalidArgumentException(sprintf(
'Cannot identify type for constructor argument "%s"; '
. 'no type hint, or non-class/interface type hint',
$argument->getName()
));
}
return true;
}
);
if (empty($constructorParameters)) {
return [];
}
return array_map(function (ReflectionParameter $parameter) {
return $parameter->getClass()->getName();
}, $constructorParameters);
}
/**
* @param string $className
* @return string
*/
private function createArgumentString($className)
{
$arguments = array_map(function ($dependency) {
return sprintf('$container->get(\\%s::class)', $dependency);
}, $this->getConstructorParameters($className));
switch (count($arguments)) {
case 0:
return '';
case 1:
return array_shift($arguments);
default:
$argumentPad = str_repeat(' ', 12);
$closePad = str_repeat(' ', 8);
return sprintf(
"\n%s%s\n%s",
$argumentPad,
implode(",\n" . $argumentPad, $arguments),
$closePad
);
}
}
private function createImportStatements(string $className): string
{
$imports = array_merge(self::IMPORT_ALWAYS, [$className]);
sort($imports);
return implode("\n", array_map(function ($import) {
return sprintf('use %s;', $import);
}, $imports));
}
}
PK [,Qݽ"W W src/Tool/ConfigDumper.phpnu ٘ 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);
}
}
PK [,Q&z} } src/Tool/ConfigDumperCommand.phpnu ٘ Usage:
%s [-h|--help|help] [-i|--ignore-unresolved]
Arguments:
-h|--help|help This usage message
-i|--ignore-unresolved Ignore classes with unresolved direct dependencies.
Path to a config file for which to generate
configuration. If the file does not exist, it will
be created. If it does exist, it must return an
array, and the file will be updated with new
configuration.
Name of the class to reflect and for which to
generate dependency configuration.
Reads the provided configuration file (creating it if it does not exist),
and injects it with ConfigAbstractFactory dependency configuration for
the provided class name, writing the changes back to the file.
EOH;
/**
* @var ConsoleHelper
*/
private $helper;
/**
* @var string
*/
private $scriptName;
/**
* @param string $scriptName
*/
public function __construct($scriptName = self::DEFAULT_SCRIPT_NAME, ConsoleHelper $helper = null)
{
$this->scriptName = $scriptName;
$this->helper = $helper ?: new ConsoleHelper();
}
/**
* @param array $args Argument list, minus script name
* @return int Exit status
*/
public function __invoke(array $args)
{
$arguments = $this->parseArgs($args);
switch ($arguments->command) {
case self::COMMAND_HELP:
$this->help();
return 0;
case self::COMMAND_ERROR:
$this->helper->writeErrorMessage($arguments->message);
$this->help(STDERR);
return 1;
case self::COMMAND_DUMP:
// fall-through
default:
break;
}
$dumper = new ConfigDumper();
try {
$config = $dumper->createDependencyConfig(
$arguments->config,
$arguments->class,
$arguments->ignoreUnresolved
);
} catch (Exception\InvalidArgumentException $e) {
$this->helper->writeErrorMessage(sprintf(
'Unable to create config for "%s": %s',
$arguments->class,
$e->getMessage()
));
$this->help(STDERR);
return 1;
}
file_put_contents($arguments->configFile, $dumper->dumpConfigFile($config));
$this->helper->writeLine(sprintf(
'[DONE] Changes written to %s',
$arguments->configFile
));
return 0;
}
/**
* @param array $args
* @return \stdClass
*/
private function parseArgs(array $args)
{
if (! $args) {
return $this->createHelpArgument();
}
$arg1 = array_shift($args);
if (in_array($arg1, ['-h', '--help', 'help'], true)) {
return $this->createHelpArgument();
}
$ignoreUnresolved = false;
if (in_array($arg1, ['-i', '--ignore-unresolved'], true)) {
$ignoreUnresolved = true;
$arg1 = array_shift($args);
}
if (! $args) {
return $this->createErrorArgument('Missing class name');
}
$configFile = $arg1;
switch (file_exists($configFile)) {
case true:
$config = require $configFile;
if (! is_array($config)) {
return $this->createErrorArgument(sprintf(
'Configuration at path "%s" does not return an array.',
$configFile
));
}
break;
case false:
// fall-through
default:
if (! is_writable(dirname($configFile))) {
return $this->createErrorArgument(sprintf(
'Cannot create configuration at path "%s"; not writable.',
$configFile
));
}
$config = [];
break;
}
$class = array_shift($args);
if (! class_exists($class)) {
return $this->createErrorArgument(sprintf(
'Class "%s" does not exist or could not be autoloaded.',
$class
));
}
return $this->createArguments(self::COMMAND_DUMP, $configFile, $config, $class, $ignoreUnresolved);
}
/**
* @param resource $resource Defaults to STDOUT
* @return void
*/
private function help($resource = STDOUT)
{
$this->helper->writeLine(sprintf(
self::HELP_TEMPLATE,
$this->scriptName
), true, $resource);
}
/**
* @param string $command
* @param string $configFile File from which config originates, and to
* which it will be written.
* @param array $config Parsed configuration.
* @param string $class Name of class to reflect.
* @param bool $ignoreUnresolved If to ignore classes with unresolved direct dependencies.
* @return \stdClass
*/
private function createArguments($command, $configFile, $config, $class, $ignoreUnresolved)
{
return (object) [
'command' => $command,
'configFile' => $configFile,
'config' => $config,
'class' => $class,
'ignoreUnresolved' => $ignoreUnresolved,
];
}
/**
* @param string $message
* @return \stdClass
*/
private function createErrorArgument($message)
{
return (object) [
'command' => self::COMMAND_ERROR,
'message' => $message,
];
}
/**
* @return \stdClass
*/
private function createHelpArgument()
{
return (object) [
'command' => self::COMMAND_HELP,
];
}
}
PK [,Q-> > " src/Tool/FactoryCreatorCommand.phpnu ٘ Usage:
%s [-h|--help|help]
Arguments:
-h|--help|help This usage message
Name of the class to reflect and for which to generate
a factory.
Generates to STDOUT a factory for creating the specified class; this may then
be added to your application, and configured as a factory for the class.
EOH;
/**
* @var ConsoleHelper
*/
private $helper;
/**
* @var string
*/
private $scriptName;
/**
* @param string $scriptName
* @param ConsoleHelper $helper
*/
public function __construct($scriptName = self::DEFAULT_SCRIPT_NAME, ConsoleHelper $helper = null)
{
$this->scriptName = $scriptName;
$this->helper = $helper ?: new ConsoleHelper();
}
/**
* @param array $args Argument list, minus script name
* @return int Exit status
*/
public function __invoke(array $args)
{
$arguments = $this->parseArgs($args);
switch ($arguments->command) {
case self::COMMAND_HELP:
$this->help();
return 0;
case self::COMMAND_ERROR:
$this->helper->writeErrorMessage($arguments->message);
$this->help(STDERR);
return 1;
case self::COMMAND_DUMP:
// fall-through
default:
break;
}
$generator = new FactoryCreator();
try {
$factory = $generator->createFactory($arguments->class);
} catch (Exception\InvalidArgumentException $e) {
$this->helper->writeErrorMessage(sprintf(
'Unable to create factory for "%s": %s',
$arguments->class,
$e->getMessage()
));
$this->help(STDERR);
return 1;
}
$this->helper->write($factory, false);
return 0;
}
/**
* @param array $args
* @return \stdClass
*/
private function parseArgs(array $args)
{
if (! $args) {
return $this->createArguments(self::COMMAND_HELP);
}
$arg1 = array_shift($args);
if (in_array($arg1, ['-h', '--help', 'help'], true)) {
return $this->createArguments(self::COMMAND_HELP);
}
$class = $arg1;
if (! class_exists($class)) {
return $this->createArguments(self::COMMAND_ERROR, null, sprintf(
'Class "%s" does not exist or could not be autoloaded.',
$class
));
}
return $this->createArguments(self::COMMAND_DUMP, $class);
}
/**
* @param resource $resource Defaults to STDOUT
* @return void
*/
private function help($resource = STDOUT)
{
$this->helper->writeLine(sprintf(
self::HELP_TEMPLATE,
$this->scriptName
), true, $resource);
}
/**
* @param string $command
* @param string|null $class Name of class to reflect.
* @param string|null $error Error message, if any.
* @return \stdClass
*/
private function createArguments($command, $class = null, $error = null)
{
return (object) [
'command' => $command,
'class' => $class,
'message' => $error,
];
}
}
PK [,Q۟{ { ( src/Initializer/InitializerInterface.phpnu ٘ 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;
}
}
PK [,Qrf f
mkdocs.ymlnu ٘ docs_dir: docs/book
site_dir: docs/html
nav:
- Home: index.md
- 'Quick Start': quick-start.md
- Reference:
- 'Configuring the service manager': configuring-the-service-manager.md
- Delegators: delegators.md
- 'Lazy services': lazy-services.md
- 'Plugin managers': plugin-managers.md
- 'Configuration-based Abstract Factory': config-abstract-factory.md
- 'Reflection-based Abstract Factory': reflection-abstract-factory.md
- 'Console Tools': console-tools.md
- Cookbook:
- 'Factories vs Abstract Factories': cookbook/factories-vs-abstract-factories.md
- 'Migration Guide': migration.md
site_name: laminas-servicemanager
site_description: 'laminas-servicemanager: factory-driven dependency injection container'
repo_url: 'https://github.com/laminas/laminas-servicemanager'
extra:
project: Components
PK [,Q0J .github/workflows/docs-build.ymlnu ٘ name: docs-build
on:
release:
types: [published]
repository_dispatch:
types: docs-build
jobs:
build-deploy:
runs-on: ubuntu-latest
steps:
- name: Build Docs
uses: laminas/documentation-theme/github-actions/docs@master
env:
"DOCS_DEPLOY_KEY": ${{ secrets.DOCS_DEPLOY_KEY }}
"GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }}
PK [,Qu|e)
1 .github/workflows/release-on-milestone-closed.ymlnu ٘ # Alternate workflow example.
# This one is identical to the one in release-on-milestone.yml, with one change:
# the Release step uses the ORGANIZATION_ADMIN_TOKEN instead, to allow it to
# trigger a release workflow event. This is useful if you have other actions
# that intercept that event.
name: "Automatic Releases"
on:
milestone:
types:
- "closed"
jobs:
release:
name: "GIT tag, release & create merge-up PR"
runs-on: ubuntu-latest
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
- name: "Release"
uses: "laminas/automatic-releases@v1"
with:
command-name: "laminas:automatic-releases:release"
env:
"GITHUB_TOKEN": ${{ secrets.ORGANIZATION_ADMIN_TOKEN }}
"SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }}
"GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }}
"GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }}
- name: "Create Merge-Up Pull Request"
uses: "laminas/automatic-releases@v1"
with:
command-name: "laminas:automatic-releases:create-merge-up-pull-request"
env:
"GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }}
"SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }}
"GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }}
"GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }}
- name: "Create and/or Switch to new Release Branch"
uses: "laminas/automatic-releases@v1"
with:
command-name: "laminas:automatic-releases:switch-default-branch-to-next-minor"
env:
"GITHUB_TOKEN": ${{ secrets.ORGANIZATION_ADMIN_TOKEN }}
"SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }}
"GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }}
"GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }}
- name: "Bump Changelog Version On Originating Release Branch"
uses: "laminas/automatic-releases@v1"
with:
command-name: "laminas:automatic-releases:bump-changelog"
env:
"GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }}
"SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }}
"GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }}
"GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }}
- name: "Create new milestones"
uses: "laminas/automatic-releases@v1"
with:
command-name: "laminas:automatic-releases:create-milestones"
env:
"GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }}
"SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }}
"GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }}
"GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }}
PK [,QX=_
" benchmarks/SetNewServicesBench.phpnu ٘ [
'factory1' => BenchAsset\FactoryFoo::class,
],
'invokables' => [
'invokable1' => BenchAsset\Foo::class,
],
'services' => [
'service1' => new \stdClass(),
],
'aliases' => [
'factoryAlias1' => 'factory1',
'recursiveFactoryAlias1' => 'factoryAlias1',
'recursiveFactoryAlias2' => 'recursiveFactoryAlias1',
],
'abstract_factories' => [
BenchAsset\AbstractFactoryFoo::class
],
];
for ($i = 0; $i <= self::NUM_SERVICES; $i++) {
$config['factories']["factory_$i"] = BenchAsset\FactoryFoo::class;
$config['aliases']["alias_$i"] = "service_$i";
}
$this->sm = new ServiceManager($config);
}
public function benchSetService()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->setService('service2', new \stdClass());
}
public function benchSetFactory()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->setFactory('factory2', BenchAsset\FactoryFoo::class);
}
public function benchSetAlias()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->setAlias('factoryAlias2', 'factory1');
}
public function benchSetAliasOverrided()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->setAlias('recursiveFactoryAlias1', 'factory1');
}
}
PK [,Q-pL L * benchmarks/FetchNewServiceManagerBench.phpnu ٘ [],
'invokables' => [],
'services' => [],
'aliases' => [],
'abstract_factories' => [
BenchAsset\AbstractFactoryFoo::class,
],
];
$service = new \stdClass();
for ($i = 0; $i <= self::NUM_SERVICES; $i++) {
$config['factories']["factory_$i"] = BenchAsset\FactoryFoo::class;
$config['invokables']["invokable_$i"] = BenchAsset\Foo::class;
$config['services']["service_$i"] = $service;
$config['aliases']["alias_$i"] = "service_$i";
}
$this->config = $config;
}
public function benchFetchServiceManagerCreation()
{
new ServiceManager($this->config);
}
}
PK [,Q J benchmarks/FetchNewServiceUsingReflectionAbstractFactoryAsFactoryBench.phpnu ٘ sm = new ServiceManager([
'services' => [
'config' => [],
],
'factories' => [
BenchAsset\Dependency::class => ReflectionBasedAbstractFactory::class,
BenchAsset\ServiceWithDependency::class => ReflectionBasedAbstractFactory::class,
BenchAsset\ServiceDependingOnConfig::class => ReflectionBasedAbstractFactory::class,
],
]);
}
public function benchFetchServiceWithNoDependencies()
{
$sm = clone $this->sm;
$sm->get(BenchAsset\Dependency::class);
}
public function benchBuildServiceWithNoDependencies()
{
$sm = clone $this->sm;
$sm->build(BenchAsset\Dependency::class);
}
public function benchFetchServiceDependingOnConfig()
{
$sm = clone $this->sm;
$sm->get(BenchAsset\ServiceDependingOnConfig::class);
}
public function benchBuildServiceDependingOnConfig()
{
$sm = clone $this->sm;
$sm->build(BenchAsset\ServiceDependingOnConfig::class);
}
public function benchFetchServiceWithDependency()
{
$sm = clone $this->sm;
$sm->get(BenchAsset\ServiceWithDependency::class);
}
public function benchBuildServiceWithDependency()
{
$sm = clone $this->sm;
$sm->build(BenchAsset\ServiceWithDependency::class);
}
}
PK [,Qs $ benchmarks/FetchNewServicesBench.phpnu ٘ sm = new ServiceManager([
'factories' => [
'factory1' => BenchAsset\FactoryFoo::class,
],
'invokables' => [
'invokable1' => BenchAsset\Foo::class,
],
'services' => [
'service1' => new \stdClass(),
'config' => [],
],
'aliases' => [
'factoryAlias1' => 'factory1',
'recursiveFactoryAlias1' => 'factoryAlias1',
'recursiveFactoryAlias2' => 'recursiveFactoryAlias1',
],
'abstract_factories' => [
BenchAsset\AbstractFactoryFoo::class,
],
]);
}
public function benchFetchFactory1()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->get('factory1');
}
public function benchBuildFactory1()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->build('factory1');
}
public function benchFetchInvokable1()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->get('invokable1');
}
public function benchBuildInvokable1()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->build('invokable1');
}
public function benchFetchService1()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->get('service1');
}
public function benchFetchFactoryAlias1()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->build('factoryAlias1');
}
public function benchBuildFactoryAlias1()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->build('factoryAlias1');
}
public function benchFetchRecursiveFactoryAlias1()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->build('recursiveFactoryAlias1');
}
public function benchBuildRecursiveFactoryAlias1()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->build('recursiveFactoryAlias1');
}
public function benchFetchRecursiveFactoryAlias2()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->build('recursiveFactoryAlias2');
}
public function benchBuildRecursiveFactoryAlias2()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->build('recursiveFactoryAlias2');
}
public function benchFetchAbstractFactoryFoo()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->get('foo');
}
public function benchBuildAbstractFactoryFoo()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->build('foo');
}
}
PK [,Q benchmarks/BenchAsset/Foo.phpnu ٘ options = $options;
}
}
PK [,Qc | | $ benchmarks/BenchAsset/Dependency.phpnu ٘ dependency = $dependency;
}
}
PK [,Q1n n , benchmarks/BenchAsset/AbstractFactoryFoo.phpnu ٘ config = $config;
}
}
PK [,Qxlj $ benchmarks/BenchAsset/FactoryFoo.phpnu ٘ options = $options;
}
}
PK [,Q*]= = benchmarks/HasBench.phpnu ٘ sm = new ServiceManager([
'factories' => [
'factory1' => BenchAsset\FactoryFoo::class,
],
'invokables' => [
'invokable1' => BenchAsset\Foo::class,
],
'services' => [
'service1' => new \stdClass(),
],
'aliases' => [
'alias1' => 'service1',
'recursiveAlias1' => 'alias1',
'recursiveAlias2' => 'recursiveAlias1',
],
'abstract_factories' => [
BenchAsset\AbstractFactoryFoo::class
]
]);
}
public function benchHasFactory1()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->has('factory1');
}
public function benchHasInvokable1()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->has('invokable1');
}
public function benchHasService1()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->has('service1');
}
public function benchHasAlias1()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->has('alias1');
}
public function benchHasRecursiveAlias1()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->has('recursiveAlias1');
}
public function benchHasRecursiveAlias2()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->has('recursiveAlias2');
}
public function benchHasAbstractFactory()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->has('foo');
}
public function benchHasNot()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->has('42');
}
}
PK [,Q}u ' benchmarks/FetchCachedServicesBench.phpnu ٘ sm = new ServiceManager([
'factories' => [
'factory1' => BenchAsset\FactoryFoo::class,
],
'invokables' => [
'invokable1' => BenchAsset\Foo::class,
],
'services' => [
'service1' => new \stdClass(),
],
'aliases' => [
'alias1' => 'service1',
'recursiveAlias1' => 'alias1',
'recursiveAlias2' => 'recursiveAlias1',
],
'abstract_factories' => [
BenchAsset\AbstractFactoryFoo::class
]
]);
// forcing initialization of all the services
$this->sm->get('factory1');
$this->sm->get('invokable1');
$this->sm->get('service1');
$this->sm->get('alias1');
$this->sm->get('recursiveAlias1');
$this->sm->get('recursiveAlias2');
}
public function benchFetchFactory1()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->get('factory1');
}
public function benchFetchInvokable1()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->get('invokable1');
}
public function benchFetchService1()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->get('service1');
}
public function benchFetchAlias1()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->get('alias1');
}
public function benchFetchRecursiveAlias1()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->get('recursiveAlias1');
}
public function benchFetchRecursiveAlias2()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->get('recursiveAlias2');
}
public function benchFetchAbstractFactoryService()
{
// @todo @link https://github.com/phpbench/phpbench/issues/304
$sm = clone $this->sm;
$sm->get('foo');
}
}
PK [,QFq
q
F benchmarks/FetchNewServiceUsingConfigAbstractFactoryAsFactoryBench.phpnu ٘ sm = new ServiceManager([
'services' => [
'config' => [
ConfigAbstractFactory::class => [
BenchAsset\Dependency::class => [],
BenchAsset\ServiceWithDependency::class => [
BenchAsset\Dependency::class,
],
BenchAsset\ServiceDependingOnConfig::class => [
'config',
],
],
],
],
'factories' => [
BenchAsset\Dependency::class => ConfigAbstractFactory::class,
BenchAsset\ServiceWithDependency::class => ConfigAbstractFactory::class,
BenchAsset\ServiceDependingOnConfig::class => ConfigAbstractFactory::class,
],
]);
}
public function benchFetchServiceWithNoDependencies()
{
$sm = clone $this->sm;
$sm->get(BenchAsset\Dependency::class);
}
public function benchBuildServiceWithNoDependencies()
{
$sm = clone $this->sm;
$sm->build(BenchAsset\Dependency::class);
}
public function benchFetchServiceDependingOnConfig()
{
$sm = clone $this->sm;
$sm->get(BenchAsset\ServiceDependingOnConfig::class);
}
public function benchBuildServiceDependingOnConfig()
{
$sm = clone $this->sm;
$sm->build(BenchAsset\ServiceDependingOnConfig::class);
}
public function benchFetchServiceWithDependency()
{
$sm = clone $this->sm;
$sm->get(BenchAsset\ServiceWithDependency::class);
}
public function benchBuildServiceWithDependency()
{
$sm = clone $this->sm;
$sm->build(BenchAsset\ServiceWithDependency::class);
}
}
PK [,Q࿚ ; benchmarks/FetchNewServiceViaConfigAbstractFactoryBench.phpnu ٘ sm = new ServiceManager([
'services' => [
'config' => [
ConfigAbstractFactory::class => [
BenchAsset\Dependency::class => [],
BenchAsset\ServiceWithDependency::class => [
BenchAsset\Dependency::class,
],
BenchAsset\ServiceDependingOnConfig::class => [
'config',
],
],
],
],
'abstract_factories' => [
ConfigAbstractFactory::class,
],
]);
}
public function benchFetchServiceWithNoDependencies()
{
$sm = clone $this->sm;
$sm->get(BenchAsset\Dependency::class);
}
public function benchBuildServiceWithNoDependencies()
{
$sm = clone $this->sm;
$sm->build(BenchAsset\Dependency::class);
}
public function benchFetchServiceDependingOnConfig()
{
$sm = clone $this->sm;
$sm->get(BenchAsset\ServiceDependingOnConfig::class);
}
public function benchBuildServiceDependingOnConfig()
{
$sm = clone $this->sm;
$sm->build(BenchAsset\ServiceDependingOnConfig::class);
}
public function benchFetchServiceWithDependency()
{
$sm = clone $this->sm;
$sm->get(BenchAsset\ServiceWithDependency::class);
}
public function benchBuildServiceWithDependency()
{
$sm = clone $this->sm;
$sm->build(BenchAsset\ServiceWithDependency::class);
}
}
PK [,Q| ? benchmarks/FetchNewServiceViaReflectionAbstractFactoryBench.phpnu ٘ sm = new ServiceManager([
'services' => [
'config' => [],
],
'abstract_factories' => [
ReflectionBasedAbstractFactory::class,
],
]);
}
public function benchFetchServiceWithNoDependencies()
{
$sm = clone $this->sm;
$sm->get(BenchAsset\Dependency::class);
}
public function benchBuildServiceWithNoDependencies()
{
$sm = clone $this->sm;
$sm->build(BenchAsset\Dependency::class);
}
public function benchFetchServiceDependingOnConfig()
{
$sm = clone $this->sm;
$sm->get(BenchAsset\ServiceDependingOnConfig::class);
}
public function benchBuildServiceDependingOnConfig()
{
$sm = clone $this->sm;
$sm->build(BenchAsset\ServiceDependingOnConfig::class);
}
public function benchFetchServiceWithDependency()
{
$sm = clone $this->sm;
$sm->get(BenchAsset\ServiceWithDependency::class);
}
public function benchBuildServiceWithDependency()
{
$sm = clone $this->sm;
$sm->build(BenchAsset\ServiceWithDependency::class);
}
}
PK [,Q~1> > .gitattributesnu ٘ /.coveralls.yml export-ignore
/.gitattributes export-ignore
/.github/ export-ignore
/.gitignore export-ignore
/.travis.yml export-ignore
/benchmarks/ export-ignore
/docs/ export-ignore
/mkdocs.yml export-ignore
/phpbench.json export-ignore
/phpcs.xml export-ignore
/phpunit.xml.dist export-ignore
/test/ export-ignore
PK [,Q*
% % + test/Exception/CyclicAliasExceptionTest.phpnu ٘ getMessage());
}
/**
* Test data provider for testFromCyclicAlias
*
* @return string[][]|string[][][]
*/
public function cyclicAliasProvider()
{
return [
[
'a',
[
'a' => 'a',
],
"A cycle was detected within the aliases definitions:\n"
. "a -> a\n",
],
[
'a',
[
'a' => 'b',
'b' => 'a'
],
"A cycle was detected within the aliases definitions:\n"
. "a -> b -> a\n",
],
[
'b',
[
'a' => 'b',
'b' => 'a'
],
"A cycle was detected within the aliases definitions:\n"
. "b -> a -> b\n",
],
[
'b',
[
'a' => 'b',
'b' => 'a',
],
"A cycle was detected within the aliases definitions:\n"
. "b -> a -> b\n",
],
[
'a',
[
'a' => 'b',
'b' => 'c',
'c' => 'a',
],
"A cycle was detected within the aliases definitions:\n"
. "a -> b -> c -> a\n",
],
[
'b',
[
'a' => 'b',
'b' => 'c',
'c' => 'a',
],
"A cycle was detected within the aliases definitions:\n"
. "b -> c -> a -> b\n",
],
[
'c',
[
'a' => 'b',
'b' => 'c',
'c' => 'a',
],
"A cycle was detected within the aliases definitions:\n"
. "c -> a -> b -> c\n",
],
];
}
/**
* @dataProvider aliasesProvider
*
* @param string[] $aliases
* @param string $expectedMessage
*/
public function testFromAliasesMap(array $aliases, $expectedMessage)
{
$exception = CyclicAliasException::fromAliasesMap($aliases);
self::assertInstanceOf(CyclicAliasException::class, $exception);
self::assertSame($expectedMessage, $exception->getMessage());
}
/**
* @return string[][]|string[][][]
*/
public function aliasesProvider()
{
return [
'empty set' => [
[],
'A cycle was detected within the following aliases map:
[
]'
],
'acyclic set' => [
[
'b' => 'a',
'd' => 'c',
],
'A cycle was detected within the following aliases map:
[
"b" => "a"
"d" => "c"
]'
],
'acyclic self-referencing set' => [
[
'b' => 'a',
'c' => 'b',
'd' => 'c',
],
'A cycle was detected within the following aliases map:
[
"b" => "a"
"c" => "b"
"d" => "c"
]'
],
'cyclic set' => [
[
'b' => 'a',
'a' => 'b',
],
'Cycles were detected within the provided aliases:
[
"b" => "a" => "b"
]
The cycle was detected in the following alias map:
[
"b" => "a"
"a" => "b"
]'
],
'cyclic set (indirect)' => [
[
'b' => 'a',
'c' => 'b',
'a' => 'c',
],
'Cycles were detected within the provided aliases:
[
"b" => "a" => "c" => "b"
]
The cycle was detected in the following alias map:
[
"b" => "a"
"c" => "b"
"a" => "c"
]'
],
'cyclic set + acyclic set' => [
[
'b' => 'a',
'a' => 'b',
'd' => 'c',
],
'Cycles were detected within the provided aliases:
[
"b" => "a" => "b"
]
The cycle was detected in the following alias map:
[
"b" => "a"
"a" => "b"
"d" => "c"
]'
],
'cyclic set + reference to cyclic set' => [
[
'b' => 'a',
'a' => 'b',
'c' => 'a',
],
'Cycles were detected within the provided aliases:
[
"b" => "a" => "b"
"c" => "a" => "b" => "c"
]
The cycle was detected in the following alias map:
[
"b" => "a"
"a" => "b"
"c" => "a"
]'
],
];
}
}
PK [,Q:y7 7 " test/AbstractPluginManagerTest.phpnu ٘ creationContext = new ServiceManager();
return new TestAsset\LenientPluginManager($this->creationContext, $config);
}
public function testInjectCreationContextInFactories()
{
$invokableFactory = $this->getMockBuilder(FactoryInterface::class)
->getMock();
$config = [
'factories' => [
InvokableObject::class => $invokableFactory,
],
];
$container = $this->getMockBuilder(ContainerInterface::class)
->getMock();
$pluginManager = new SimplePluginManager($container, $config);
$invokableFactory->expects($this->once())
->method('__invoke')
->with($container, InvokableObject::class)
->will($this->returnValue(new InvokableObject()));
$object = $pluginManager->get(InvokableObject::class);
self::assertInstanceOf(InvokableObject::class, $object);
}
public function testValidateInstance()
{
$config = [
'factories' => [
InvokableObject::class => new InvokableFactory(),
stdClass::class => new InvokableFactory(),
],
];
$container = $this->getMockBuilder(ContainerInterface::class)
->getMock();
$pluginManager = new SimplePluginManager($container, $config);
// Assert no exception is triggered because the plugin manager validate ObjectWithOptions
$pluginManager->get(InvokableObject::class);
// Assert it throws an exception for anything else
$this->expectException(InvalidServiceException::class);
$pluginManager->get(stdClass::class);
}
public function testCachesInstanceByDefaultIfNoOptionsArePassed()
{
$config = [
'factories' => [
InvokableObject::class => new InvokableFactory(),
],
];
$container = $this->getMockBuilder(ContainerInterface::class)
->getMock();
$pluginManager = new SimplePluginManager($container, $config);
$first = $pluginManager->get(InvokableObject::class);
$second = $pluginManager->get(InvokableObject::class);
self::assertInstanceOf(InvokableObject::class, $first);
self::assertInstanceOf(InvokableObject::class, $second);
self::assertSame($first, $second);
}
public function shareByDefaultSettings()
{
return [
'true' => [true],
'false' => [false],
];
}
/**
* @dataProvider shareByDefaultSettings
*/
public function testReturnsDiscreteInstancesIfOptionsAreProvidedRegardlessOfShareByDefaultSetting($shareByDefault)
{
$config = [
'factories' => [
InvokableObject::class => new InvokableFactory(),
],
'share_by_default' => $shareByDefault,
];
$options = ['foo' => 'bar'];
$container = $this->getMockBuilder(ContainerInterface::class)
->getMock();
$pluginManager = new SimplePluginManager($container, $config);
$first = $pluginManager->get(InvokableObject::class, $options);
$second = $pluginManager->get(InvokableObject::class, $options);
self::assertInstanceOf(InvokableObject::class, $first);
self::assertInstanceOf(InvokableObject::class, $second);
self::assertNotSame($first, $second);
}
/**
* Separate test from ServiceManager, as all factories go through the
* creation context; we need to configure the parent container, as
* the delegator factory will be receiving that.
*/
public function testCanWrapCreationInDelegators()
{
$config = [
'option' => 'OPTIONED',
];
$serviceManager = new ServiceManager([
'services' => [
'config' => $config,
],
]);
$pluginManager = new TestAsset\LenientPluginManager($serviceManager, [
'factories' => [
stdClass::class => InvokableFactory::class,
],
'delegators' => [
stdClass::class => [
TestAsset\PreDelegator::class,
function ($container, $name, $callback) {
$instance = $callback();
$instance->foo = 'bar';
return $instance;
},
],
],
]);
$instance = $pluginManager->get(stdClass::class);
self::assertTrue(isset($instance->option), 'Delegator-injected option was not found');
self::assertEquals(
$config['option'],
$instance->option,
'Delegator-injected option does not match configuration'
);
self::assertEquals('bar', $instance->foo);
}
/**
* Overrides the method in the CommonServiceLocatorBehaviorsTrait, due to behavior differences.
*
* @covers \Laminas\ServiceManager\AbstractPluginManager::get
*/
public function testGetRaisesExceptionWhenNoFactoryIsResolved()
{
$pluginManager = $this->createContainer();
$this->expectException(ServiceNotFoundException::class);
$this->expectExceptionMessage(get_class($pluginManager));
$pluginManager->get('Some\Unknown\Service');
}
/**
* @group migration
*/
public function testCallingSetServiceLocatorSetsCreationContextWithDeprecationNotice()
{
set_error_handler(function ($errno, $errstr) {
self::assertEquals(E_USER_DEPRECATED, $errno);
}, E_USER_DEPRECATED);
$pluginManager = new TestAsset\LenientPluginManager();
restore_error_handler();
self::assertAttributeSame($pluginManager, 'creationContext', $pluginManager);
$serviceManager = new ServiceManager();
set_error_handler(function ($errno, $errstr) {
self::assertEquals(E_USER_DEPRECATED, $errno);
}, E_USER_DEPRECATED);
$pluginManager->setServiceLocator($serviceManager);
restore_error_handler();
self::assertAttributeSame($serviceManager, 'creationContext', $pluginManager);
}
/**
* @group migration
*/
public function testPassingNoInitialConstructorArgumentSetsPluginManagerAsCreationContextWithDeprecationNotice()
{
set_error_handler(function ($errno, $errstr) {
self::assertEquals(E_USER_DEPRECATED, $errno);
}, E_USER_DEPRECATED);
$pluginManager = new TestAsset\LenientPluginManager();
restore_error_handler();
self::assertAttributeSame($pluginManager, 'creationContext', $pluginManager);
}
/**
* @group migration
*/
public function testCanPassConfigInterfaceAsFirstConstructorArgumentWithDeprecationNotice()
{
$config = $this->prophesize(ConfigInterface::class);
$config->toArray()->willReturn([]);
set_error_handler(function ($errno, $errstr) {
self::assertEquals(E_USER_DEPRECATED, $errno);
}, E_USER_DEPRECATED);
$pluginManager = new TestAsset\LenientPluginManager($config->reveal());
restore_error_handler();
self::assertAttributeSame($pluginManager, 'creationContext', $pluginManager);
}
public function invalidConstructorArguments()
{
return [
'true' => [true],
'false' => [false],
'zero' => [0],
'int' => [1],
'zero-float' => [0.0],
'float' => [1.1],
'string' => ['invalid'],
'array' => [['invokables' => []]],
'object' => [(object) ['invokables' => []]],
];
}
/**
* @group migration
* @dataProvider invalidConstructorArguments
*/
public function testPassingNonContainerNonConfigNonNullFirstConstructorArgumentRaisesException($arg)
{
$this->expectException(InvalidArgumentException::class);
new TestAsset\LenientPluginManager($arg);
}
/**
* @group migration
*/
public function testPassingConfigInstanceAsFirstConstructorArgumentSkipsSecondArgumentWithDeprecationNotice()
{
$config = $this->prophesize(ConfigInterface::class);
$config->toArray()->willReturn(['services' => [__CLASS__ => $this]]);
set_error_handler(function ($errno, $errstr) {
self::assertEquals(E_USER_DEPRECATED, $errno);
}, E_USER_DEPRECATED);
$pluginManager = new TestAsset\LenientPluginManager($config->reveal(), ['services' => [__CLASS__ => []]]);
restore_error_handler();
self::assertSame($this, $pluginManager->get(__CLASS__));
}
/**
* @group migration
* @group autoinvokable
*/
public function testAutoInvokableServicesAreNotKnownBeforeRetrieval()
{
$pluginManager = new TestAsset\SimplePluginManager(new ServiceManager());
self::assertFalse($pluginManager->has(TestAsset\InvokableObject::class));
}
/**
* @group migration
* @group autoinvokable
*/
public function testSupportsRetrievingAutoInvokableServicesByDefault()
{
$pluginManager = new TestAsset\SimplePluginManager(new ServiceManager());
$invokable = $pluginManager->get(TestAsset\InvokableObject::class);
self::assertInstanceOf(TestAsset\InvokableObject::class, $invokable);
}
/**
* @group migration
* @group autoinvokable
*/
public function testPluginManagersMayOptOutOfSupportingAutoInvokableServices()
{
$pluginManager = new TestAsset\NonAutoInvokablePluginManager(new ServiceManager());
$this->expectException(ServiceNotFoundException::class);
$this->expectExceptionMessage(TestAsset\NonAutoInvokablePluginManager::class);
$pluginManager->get(TestAsset\InvokableObject::class);
}
/**
* @group migration
*/
public function testValidateWillFallBackToValidatePluginWhenDefinedAndEmitDeprecationNotice()
{
$assertionCalled = false;
$instance = (object) [];
$assertion = function ($plugin) use ($instance, &$assertionCalled) {
self::assertSame($instance, $plugin);
$assertionCalled = true;
};
$pluginManager = new TestAsset\V2ValidationPluginManager(new ServiceManager());
$pluginManager->assertion = $assertion;
$errorHandlerCalled = false;
set_error_handler(function ($errno, $errmsg) use (&$errorHandlerCalled) {
self::assertEquals(E_USER_DEPRECATED, $errno);
self::assertContains('3.0', $errmsg);
$errorHandlerCalled = true;
}, E_USER_DEPRECATED);
$pluginManager->validate($instance);
restore_error_handler();
self::assertTrue($assertionCalled, 'Assertion was not called by validatePlugin!');
self::assertTrue($errorHandlerCalled, 'Error handler was not triggered by validatePlugin!');
}
public function testSetServiceShouldRaiseExceptionForInvalidPlugin()
{
$pluginManager = new TestAsset\SimplePluginManager(new ServiceManager());
$this->expectException(InvalidServiceException::class);
$pluginManager->setService(stdClass::class, new stdClass());
}
public function testPassingServiceInstanceViaConfigureShouldRaiseExceptionForInvalidPlugin()
{
$pluginManager = new TestAsset\SimplePluginManager(new ServiceManager());
$this->expectException(InvalidServiceException::class);
$pluginManager->configure(['services' => [
stdClass::class => new stdClass(),
]]);
}
/**
* @group 79
* @group 78
*/
public function testAbstractFactoryGetsCreationContext()
{
$serviceManager = new ServiceManager();
$pluginManager = new TestAsset\SimplePluginManager($serviceManager);
$abstractFactory = $this->prophesize(AbstractFactoryInterface::class);
$abstractFactory->canCreate($serviceManager, 'foo')
->willReturn(true);
$abstractFactory->__invoke($serviceManager, 'foo', null)
->willReturn(new InvokableObject());
$pluginManager->addAbstractFactory($abstractFactory->reveal());
self::assertInstanceOf(InvokableObject::class, $pluginManager->get('foo'));
}
public function testAliasPropertyResolves()
{
$pluginManager = new V2v3PluginManager(new ServiceManager());
self::assertInstanceOf(InvokableObject::class, $pluginManager->get('foo'));
}
}
PK [,Q % test/Proxy/LazyServiceFactoryTest.phpnu ٘ proxyFactory = $this->getMockBuilder(LazyLoadingValueHolderFactory::class)
->getMock();
$servicesMap = [
'fooService' => 'FooClass',
];
$this->factory = new LazyServiceFactory($this->proxyFactory, $servicesMap);
}
public function testImplementsDelegatorFactoryInterface()
{
self::assertInstanceOf(DelegatorFactoryInterface::class, $this->factory);
}
public function testThrowExceptionWhenServiceNotExists()
{
$callback = $this->getMockBuilder('stdClass')
->setMethods(['callback'])
->getMock();
$callback->expects($this->never())
->method('callback');
$container = $this->createContainerMock();
$this->proxyFactory->expects($this->never())
->method('createProxy')
;
$this->expectException(ServiceNotFoundException::class);
$this->expectExceptionMessage('The requested service "not_exists" was not found in the provided services map');
$this->factory->__invoke($container, 'not_exists', [$callback, 'callback']);
}
public function testCreates()
{
$callback = $this->getMockBuilder('stdClass')
->setMethods(['callback'])
->getMock();
$callback->expects($this->once())
->method('callback')
->willReturn('fooValue')
;
$container = $this->createContainerMock();
$expectedService = $this->getMockBuilder(VirtualProxyInterface::class)
->getMock();
$this->proxyFactory->expects($this->once())
->method('createProxy')
->willReturnCallback(
function ($className, $initializer) use ($expectedService) {
self::assertEquals('FooClass', $className, 'class name not match');
$wrappedInstance = null;
$result = $initializer(
$wrappedInstance,
$this->getMockBuilder(LazyLoadingInterface::class)->getMock()
);
self::assertEquals('fooValue', $wrappedInstance, 'expected callback return value');
self::assertTrue($result, 'initializer should return true');
return $expectedService;
}
)
;
$result = $this->factory->__invoke($container, 'fooService', [$callback, 'callback']);
self::assertSame($expectedService, $result, 'service created not match the expected');
}
/**
* @return ContainerInterface|MockObject
*/
private function createContainerMock()
{
/** @var ContainerInterface|MockObject $container */
$container = $this->getMockBuilder(ContainerInterface::class)
->getMock();
return $container;
}
}
PK [,Q' Bź ( test/TestAsset/SimpleAbstractFactory.phpnu ٘ get(\LaminasTest\ServiceManager\TestAsset\InvokableObject::class));
}
}
PK [,QN N 4 test/TestAsset/factories/ComplexDependencyObject.phpnu ٘ get(\LaminasTest\ServiceManager\TestAsset\SimpleDependencyObject::class),
$container->get(\LaminasTest\ServiceManager\TestAsset\SecondComplexDependencyObject::class)
);
}
}
PK [,Q{ { ' test/TestAsset/SimpleServiceManager.phpnu ٘ InvokableFactory::class
];
}
PK [,Q# + test/TestAsset/CallTimesAbstractFactory.phpnu ٘ dependency = $dependency;
}
}
PK [,QBR test/TestAsset/Foo.phpnu ٘ options = $options;
}
}
PK [,QX , test/TestAsset/ExceptionWithStringAsCode.phpnu ٘ [
InvokableObject::class => InvokableObject::class,
],
'delegators' => [
'foo' => [
InvokableObject::class,
],
],
];
}
PK [,QkoLY Y % test/TestAsset/config/test.config.phpnu ٘ foo = 'bar';
}
}
PK [,Quh test/TestAsset/PreDelegator.phpnu ٘ has('config')) {
return $callback();
}
$config = $container->get('config');
$instance = $callback();
foreach ($config as $key => $value) {
$instance->{$key} = $value;
}
return $instance;
}
}
PK [,Q= test/TestAsset/SampleFactory.phpnu ٘ InvokableObject::class,
// Legacy Zend Framework aliases
\ZendTest\ServiceManager\TestAsset\InvokableObject::class => InvokableObject::class,
// v2 normalized FQCNs
'zendtestservicemanagertestassetinvokableobject' => InvokableObject::class,
];
protected $factories = [
InvokableObject::class => InvokableFactory::class,
// Legacy (v2) due to alias resolution
'laminastestservicemanagertestassetinvokableobject' => InvokableFactory::class,
];
protected $instanceOf = InvokableObject::class;
protected $shareByDefault = false;
protected $sharedByDefault = false;
public function validate($plugin)
{
if ($plugin instanceof $this->instanceOf) {
return;
}
throw new InvalidServiceException(sprintf(
"'%s' is not an instance of '%s'",
get_class($plugin),
$this->instanceOf
));
}
/**
* {@inheritDoc}
*/
public function validatePlugin($plugin)
{
try {
$this->validate($plugin);
} catch (InvalidServiceException $e) {
throw new \RuntimeException($e->getMessage(), $e->getCode(), $e);
}
}
}
PK [,Qe] " test/TestAsset/InvokableObject.phpnu ٘ options = $options;
}
/**
* @return array
*/
public function getOptions()
{
return $this->options;
}
}
PK [,QD D ) test/TestAsset/FailingAbstractFactory.phpnu ٘ assertion)) {
throw new RuntimeException(sprintf(
'%s requires a callable $assertion property; not currently set',
__CLASS__
));
}
($this->assertion)($plugin);
}
}
PK [,Q ) test/TestAsset/SimpleDependencyObject.phpnu ٘ creationContext = new ServiceManager($config);
return $this->creationContext;
}
public function testServiceManagerIsAPsr11Container()
{
$container = $this->createContainer();
self::assertInstanceOf(ContainerInterface::class, $container);
}
public function testConfigurationCanBeMerged()
{
$serviceManager = new SimpleServiceManager([
'factories' => [
DateTime::class => InvokableFactory::class
]
]);
self::assertTrue($serviceManager->has(DateTime::class));
// stdClass service is inlined in SimpleServiceManager
self::assertTrue($serviceManager->has(stdClass::class));
}
public function testConfigurationTakesPrecedenceWhenMerged()
{
$factory = $this->getMockBuilder(FactoryInterface::class)
->getMock();
$factory->expects($this->once())->method('__invoke');
$serviceManager = new SimpleServiceManager([
'factories' => [
stdClass::class => $factory
]
]);
$serviceManager->get(stdClass::class);
}
/**
* @covers \Laminas\ServiceManager\ServiceManager::doCreate
* @covers \Laminas\ServiceManager\ServiceManager::createDelegatorFromName
*/
public function testCanWrapCreationInDelegators()
{
$config = [
'option' => 'OPTIONED',
];
$serviceManager = new ServiceManager([
'services' => [
'config' => $config,
],
'factories' => [
stdClass::class => InvokableFactory::class,
],
'delegators' => [
stdClass::class => [
TestAsset\PreDelegator::class,
function ($container, $name, $callback) {
$instance = $callback();
$instance->foo = 'bar';
return $instance;
},
],
],
]);
$instance = $serviceManager->get(stdClass::class);
self::assertTrue(isset($instance->option), 'Delegator-injected option was not found');
self::assertEquals(
$config['option'],
$instance->option,
'Delegator-injected option does not match configuration'
);
self::assertEquals('bar', $instance->foo);
}
public function shareProvider()
{
$sharedByDefault = true;
$serviceShared = true;
$serviceDefined = true;
$shouldReturnSameInstance = true;
// @codingStandardsIgnoreStart
return [
// Description => [$sharedByDefault, $serviceShared, $serviceDefined, $expectedInstance]
'SharedByDefault: T, ServiceIsExplicitlyShared: T, ServiceIsDefined: T' => [ $sharedByDefault, $serviceShared, $serviceDefined, $shouldReturnSameInstance],
'SharedByDefault: T, ServiceIsExplicitlyShared: T, ServiceIsDefined: F' => [ $sharedByDefault, $serviceShared, !$serviceDefined, $shouldReturnSameInstance],
'SharedByDefault: T, ServiceIsExplicitlyShared: F, ServiceIsDefined: T' => [ $sharedByDefault, !$serviceShared, $serviceDefined, !$shouldReturnSameInstance],
'SharedByDefault: T, ServiceIsExplicitlyShared: F, ServiceIsDefined: F' => [ $sharedByDefault, !$serviceShared, !$serviceDefined, $shouldReturnSameInstance],
'SharedByDefault: F, ServiceIsExplicitlyShared: T, ServiceIsDefined: T' => [!$sharedByDefault, $serviceShared, $serviceDefined, $shouldReturnSameInstance],
'SharedByDefault: F, ServiceIsExplicitlyShared: T, ServiceIsDefined: F' => [!$sharedByDefault, $serviceShared, !$serviceDefined, !$shouldReturnSameInstance],
'SharedByDefault: F, ServiceIsExplicitlyShared: F, ServiceIsDefined: T' => [!$sharedByDefault, !$serviceShared, $serviceDefined, !$shouldReturnSameInstance],
'SharedByDefault: F, ServiceIsExplicitlyShared: F, ServiceIsDefined: F' => [!$sharedByDefault, !$serviceShared, !$serviceDefined, !$shouldReturnSameInstance],
];
// @codingStandardsIgnoreEnd
}
/**
* @dataProvider shareProvider
*/
public function testShareability($sharedByDefault, $serviceShared, $serviceDefined, $shouldBeSameInstance)
{
$config = [
'shared_by_default' => $sharedByDefault,
'factories' => [
stdClass::class => InvokableFactory::class,
]
];
if ($serviceDefined) {
$config['shared'] = [
stdClass::class => $serviceShared
];
}
$serviceManager = new ServiceManager($config);
$a = $serviceManager->get(stdClass::class);
$b = $serviceManager->get(stdClass::class);
self::assertEquals($shouldBeSameInstance, $a === $b);
}
public function testMapsOneToOneInvokablesAsInvokableFactoriesInternally()
{
$config = [
'invokables' => [
InvokableObject::class => InvokableObject::class,
],
];
$serviceManager = new ServiceManager($config);
self::assertAttributeSame([
InvokableObject::class => InvokableFactory::class,
], 'factories', $serviceManager, 'Invokable object factory not found');
}
public function testMapsNonSymmetricInvokablesAsAliasPlusInvokableFactory()
{
$config = [
'invokables' => [
'Invokable' => InvokableObject::class,
],
];
$serviceManager = new ServiceManager($config);
self::assertAttributeSame([
'Invokable' => InvokableObject::class,
], 'aliases', $serviceManager, 'Alias not found for non-symmetric invokable');
self::assertAttributeSame([
InvokableObject::class => InvokableFactory::class,
], 'factories', $serviceManager, 'Factory not found for non-symmetric invokable target');
}
/**
* @depends testMapsNonSymmetricInvokablesAsAliasPlusInvokableFactory
*/
public function testSharedServicesReferencingInvokableAliasShouldBeHonored()
{
$config = [
'invokables' => [
'Invokable' => InvokableObject::class,
],
'shared' => [
'Invokable' => false,
],
];
$serviceManager = new ServiceManager($config);
$instance1 = $serviceManager->get('Invokable');
$instance2 = $serviceManager->get('Invokable');
self::assertNotSame($instance1, $instance2);
}
public function testSharedServicesReferencingAliasShouldBeHonored()
{
$config = [
'aliases' => [
'Invokable' => InvokableObject::class,
],
'factories' => [
InvokableObject::class => InvokableFactory::class,
],
'shared' => [
'Invokable' => false,
],
];
$serviceManager = new ServiceManager($config);
$instance1 = $serviceManager->get('Invokable');
$instance2 = $serviceManager->get('Invokable');
self::assertNotSame($instance1, $instance2);
}
public function testAliasToAnExplicitServiceShouldWork()
{
$config = [
'aliases' => [
'Invokable' => InvokableObject::class,
],
'services' => [
InvokableObject::class => new InvokableObject(),
],
];
$serviceManager = new ServiceManager($config);
$service = $serviceManager->get(InvokableObject::class);
$alias = $serviceManager->get('Invokable');
self::assertSame($service, $alias);
}
/**
* @depends testAliasToAnExplicitServiceShouldWork
*/
public function testSetAliasShouldWorkWithRecursiveAlias()
{
$config = [
'aliases' => [
'Alias' => 'TailInvokable',
],
'services' => [
InvokableObject::class => new InvokableObject(),
],
];
$serviceManager = new ServiceManager($config);
$serviceManager->setAlias('HeadAlias', 'Alias');
$serviceManager->setAlias('TailInvokable', InvokableObject::class);
$service = $serviceManager->get(InvokableObject::class);
$alias = $serviceManager->get('Alias');
$headAlias = $serviceManager->get('HeadAlias');
self::assertSame($service, $alias);
self::assertSame($service, $headAlias);
}
public function testAbstractFactoryShouldBeCheckedForResolvedAliasesInsteadOfAliasName()
{
$abstractFactory = $this->createMock(AbstractFactoryInterface::class);
$serviceManager = new SimpleServiceManager([
'aliases' => [
'Alias' => 'ServiceName',
],
'abstract_factories' => [
$abstractFactory,
],
]);
$abstractFactory
->method('canCreate')
->withConsecutive(
[ $this->anything(), $this->equalTo('Alias') ],
[ $this->anything(), $this->equalTo('ServiceName')]
)
->willReturnCallback(function ($context, $name) {
return $name === 'Alias';
});
$this->assertTrue($serviceManager->has('Alias'));
}
public static function sampleFactory()
{
return new stdClass();
}
public function testFactoryMayBeStaticMethodDescribedByCallableString()
{
$config = [
'factories' => [
stdClass::class => 'LaminasTest\ServiceManager\ServiceManagerTest::sampleFactory',
]
];
$serviceManager = new SimpleServiceManager($config);
$this->assertEquals(stdClass::class, get_class($serviceManager->get(stdClass::class)));
}
public function testResolvedAliasFromAbstractFactory()
{
$abstractFactory = $this->createMock(AbstractFactoryInterface::class);
$serviceManager = new SimpleServiceManager([
'aliases' => [
'Alias' => 'ServiceName',
],
'abstract_factories' => [
$abstractFactory,
],
]);
$abstractFactory
->expects(self::any())
->method('canCreate')
->withConsecutive(
[self::anything(), 'Alias'],
[self::anything(), 'ServiceName']
)
->will(self::returnCallback(function ($context, $name) {
return $name === 'ServiceName';
}));
self::assertTrue($serviceManager->has('Alias'));
}
public function testResolvedAliasNoMatchingAbstractFactoryReturnsFalse()
{
$abstractFactory = $this->createMock(AbstractFactoryInterface::class);
$serviceManager = new SimpleServiceManager([
'aliases' => [
'Alias' => 'ServiceName',
],
'abstract_factories' => [
$abstractFactory,
],
]);
$abstractFactory
->expects(self::any())
->method('canCreate')
->withConsecutive(
[self::anything(), 'Alias'],
[self::anything(), 'ServiceName']
)
->willReturn(false);
self::assertFalse($serviceManager->has('Alias'));
}
/**
* @group #3
* @see https://github.com/laminas/laminas-servicemanager/issues/3
*/
public function testConfiguringADelegatorMultipleTimesDoesNotLeadToDuplicateDelegatorCalls()
{
$delegatorFactory = function (
ContainerInterface $container,
$name,
callable $callback
) {
/** @var InvokableObject $instance */
$instance = $callback();
$options = $instance->getOptions();
$inc = $options['inc'] ?? 0;
return new InvokableObject(['inc' => ++$inc]);
};
$config = [
'factories' => [
'Foo' => function () {
return new InvokableObject();
},
],
'delegators' => [
'Foo' => [
$delegatorFactory,
LazyServiceFactory::class,
],
],
'lazy_services' => [
'class_map' => [
'Foo' => InvokableObject::class,
],
],
];
$serviceManager = new ServiceManager($config);
$serviceManager->configure($config);
/** @var InvokableObject $instance */
$instance = $serviceManager->get('Foo');
self::assertInstanceOf(InvokableObject::class, $instance);
self::assertSame(1, $instance->getOptions()['inc']);
}
}
PK [,Q]' ' 2 test/AbstractFactory/ConfigAbstractFactoryTest.phpnu ٘ setService(
'config',
[
ConfigAbstractFactory::class => 'Blancmange',
]
);
self::assertFalse($abstractFactory->canCreate($serviceManager, InvokableObject::class));
$serviceManager->setAllowOverride(true);
$serviceManager->setService(
'config',
[
ConfigAbstractFactory::class => [
InvokableObject::class => 42,
]
]
);
self::assertTrue($abstractFactory->canCreate($serviceManager, InvokableObject::class));
$serviceManager->setAllowOverride(true);
$serviceManager->setService(
'config',
[
ConfigAbstractFactory::class => [
InvokableObject::class => [
'Jabba',
'Gandalf',
'Blofeld',
42
],
]
]
);
self::assertTrue($abstractFactory->canCreate($serviceManager, InvokableObject::class));
}
public function testCanCreate()
{
$abstractFactory = new ConfigAbstractFactory();
$serviceManager = new ServiceManager();
$serviceManager->setService(
'config',
[
ConfigAbstractFactory::class => [
InvokableObject::class => [],
]
]
);
self::assertTrue($abstractFactory->canCreate($serviceManager, InvokableObject::class));
self::assertFalse($abstractFactory->canCreate($serviceManager, ServiceManager::class));
}
public function testCanCreateReturnsTrueWhenConfigIsAnArrayObject()
{
$abstractFactory = new ConfigAbstractFactory();
$serviceManager = new ServiceManager();
$serviceManager->setService(
'config',
new ArrayObject([
ConfigAbstractFactory::class => [
InvokableObject::class => [],
]
])
);
self::assertTrue($abstractFactory->canCreate($serviceManager, InvokableObject::class));
self::assertFalse($abstractFactory->canCreate($serviceManager, ServiceManager::class));
}
public function testFactoryCanCreateInstancesWhenConfigIsAnArrayObject()
{
$abstractFactory = new ConfigAbstractFactory();
$serviceManager = new ServiceManager();
$serviceManager->setService(
'config',
new ArrayObject([
ConfigAbstractFactory::class => [
InvokableObject::class => [],
]
])
);
self::assertInstanceOf(InvokableObject::class, $abstractFactory($serviceManager, InvokableObject::class));
}
public function testInvokeWithInvokableClass()
{
$abstractFactory = new ConfigAbstractFactory();
$serviceManager = new ServiceManager();
$serviceManager->setService(
'config',
[
ConfigAbstractFactory::class => [
InvokableObject::class => [],
]
]
);
self::assertInstanceOf(InvokableObject::class, $abstractFactory($serviceManager, InvokableObject::class));
}
public function testInvokeWithSimpleArguments()
{
$abstractFactory = new ConfigAbstractFactory();
$serviceManager = new ServiceManager();
$serviceManager->setService(
'config',
[
ConfigAbstractFactory::class => [
InvokableObject::class => [],
SimpleDependencyObject::class => [
InvokableObject::class,
],
]
]
);
$serviceManager->addAbstractFactory($abstractFactory);
self::assertInstanceOf(
SimpleDependencyObject::class,
$abstractFactory($serviceManager, SimpleDependencyObject::class)
);
}
public function testInvokeWithComplexArguments()
{
$abstractFactory = new ConfigAbstractFactory();
$serviceManager = new ServiceManager();
$serviceManager->setService(
'config',
[
ConfigAbstractFactory::class => [
InvokableObject::class => [],
SimpleDependencyObject::class => [
InvokableObject::class,
],
ComplexDependencyObject::class => [
SimpleDependencyObject::class,
SecondComplexDependencyObject::class,
],
SecondComplexDependencyObject::class => [
InvokableObject::class,
],
]
]
);
$serviceManager->addAbstractFactory($abstractFactory);
self::assertInstanceOf(
ComplexDependencyObject::class,
$abstractFactory($serviceManager, ComplexDependencyObject::class)
);
}
public function testExceptsWhenConfigNotSet()
{
$abstractFactory = new ConfigAbstractFactory();
$serviceManager = new ServiceManager();
$this->expectException(ServiceNotCreatedException::class);
$this->expectExceptionMessage('Cannot find a config array in the container');
$abstractFactory($serviceManager, 'Dirk_Gently');
}
public function testExceptsWhenConfigKeyNotSet()
{
$abstractFactory = new ConfigAbstractFactory();
$serviceManager = new ServiceManager();
$serviceManager->setService('config', []);
$this->expectException(ServiceNotCreatedException::class);
$this->expectExceptionMessage('Cannot find a `' . ConfigAbstractFactory::class . '` key in the config array');
$abstractFactory($serviceManager, 'Dirk_Gently');
}
public function testExceptsWhenConfigIsNotArray()
{
$abstractFactory = new ConfigAbstractFactory();
$serviceManager = new ServiceManager();
$serviceManager->setService('config', 'Holistic');
$this->expectException(ServiceNotCreatedException::class);
$this->expectExceptionMessage('Config must be an array');
$abstractFactory($serviceManager, 'Dirk_Gently');
}
public function testExceptsWhenServiceConfigIsNotArray()
{
$abstractFactory = new ConfigAbstractFactory();
$serviceManager = new ServiceManager();
$serviceManager->setService(
'config',
[
ConfigAbstractFactory::class => 'Detective_Agency'
]
);
self::expectException(ServiceNotCreatedException::class);
self::expectExceptionMessage('Service dependencies config must exist and be an array');
$abstractFactory($serviceManager, 'Dirk_Gently');
}
public function testExceptsWhenServiceConfigDoesNotExist()
{
$abstractFactory = new ConfigAbstractFactory();
$serviceManager = new ServiceManager();
$serviceManager->setService(
'config',
[
ConfigAbstractFactory::class => [],
]
);
self::expectException(ServiceNotCreatedException::class);
self::expectExceptionMessage('Service dependencies config must exist and be an array');
$abstractFactory($serviceManager, 'Dirk_Gently');
}
public function testExceptsWhenServiceConfigForRequestedNameIsNotArray()
{
$abstractFactory = new ConfigAbstractFactory();
$serviceManager = new ServiceManager();
$serviceManager->setService(
'config',
[
ConfigAbstractFactory::class => [
'DirkGently' => 'Holistic',
],
]
);
self::expectException(ServiceNotCreatedException::class);
self::expectExceptionMessage('Service dependencies config must exist and be an array');
$abstractFactory($serviceManager, 'Dirk_Gently');
}
public function testExceptsWhenServiceConfigForRequestedNameIsNotArrayOfStrings()
{
$abstractFactory = new ConfigAbstractFactory();
$serviceManager = new ServiceManager();
$serviceManager->setService(
'config',
[
ConfigAbstractFactory::class => [
'DirkGently' => [
'Holistic',
'Detective',
'Agency',
42
],
],
]
);
self::expectException(ServiceNotCreatedException::class);
self::expectExceptionMessage(
'Service dependencies config must be an array of strings, ["string","string","string","integer"] given'
);
$abstractFactory($serviceManager, 'DirkGently');
}
}
PK [,QFDi > test/AbstractFactory/TestAsset/ClassWithPrivateConstructor.phpnu ٘ sample = $sample;
}
}
PK [,Qßo 9 test/AbstractFactory/TestAsset/ValidatorPluginManager.phpnu ٘ config = $config;
}
}
PK [,Q)AT T <