PK Pڤ
LICENSE.mdnu ٘ Copyright (c) 2019, Laminas Foundation
All rights reserved.
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 PKW W COPYRIGHT.mdnu ٘ Copyright (c) 2019, Laminas Foundation.
All rights reserved. (https://getlaminas.org/)
PK P.p p
phpbench.jsonnu ٘ {
"bootstrap": "vendor/autoload.php",
"path": "benchmarks",
"retry_threshold": 5
}
PK P {EX X README.mdnu ٘ # laminas-servicemanager
Master:
[![Build Status](https://travis-ci.org/laminas/laminas-servicemanager.svg?branch=master)](https://travis-ci.org/laminas/laminas-servicemanager)
[![Coverage Status](https://coveralls.io/repos/laminas/laminas-servicemanager/badge.svg?branch=master)](https://coveralls.io/r/laminas/laminas-servicemanager?branch=master)
Develop:
[![Build Status](https://travis-ci.org/laminas/laminas-servicemanager.svg?branch=develop)](https://travis-ci.org/laminas/laminas-servicemanager)
[![Coverage Status](https://coveralls.io/repos/laminas/laminas-servicemanager/badge.svg?branch=develop)](https://coveralls.io/r/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](doc/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 Pb src/AbstractFactoryInterface.phpnu ٘ F ; src/Exception/ContainerModificationsNotAllowedException.phpnu ٘ $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 P8eG $ 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)
{
$initializer = function (&$wrappedInstance, LazyLoadingInterface $proxy) use ($callback) {
$proxy->setProxyInitializer(null);
$wrappedInstance = $callback();
return true;
};
if (isset($this->servicesMap[$name])) {
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 P|;V V ! 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;
}
/**
* {@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 Pa^
- src/AbstractFactory/ConfigAbstractFactory.phpnu ٘ has('config') || ! array_key_exists(self::class, $container->get('config'))) {
return false;
}
$config = $container->get('config');
$dependencies = $config[self::class];
return is_array($dependencies) && array_key_exists($requestedName, $dependencies);
}
/**
* {@inheritDoc}
*/
public function __invoke(\Interop\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 (! array_key_exists(self::class, $config)) {
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('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 message must be an array of strings, ' . $problem . ' given');
}
$arguments = array_map([$container, 'get'], $serviceDependencies);
return new $requestedName(...$arguments);
}
}
PK PݕW' 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);
}
/**
* 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 ReflectionClass $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 ReflectionClass $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 ReflectionClass $parameter
* @param ContainerInterface $container
* @param string $requestedName
* @return mixed
* @throws ServiceNotFoundException If type-hinted parameter cannot be
* resolved to a service in the container.
*/
private function resolveParameter(ReflectionParameter $parameter, ContainerInterface $container, $requestedName)
{
if ($parameter->isArray()) {
return [];
}
if (! $parameter->getClass()) {
if (! $parameter->isDefaultValueAvailable()) {
throw new ServiceNotFoundException(sprintf(
'Unable to create service "%s"; unable to resolve parameter "%s" '
. 'to a class, interface, or array type',
$requestedName,
$parameter->getName()
));
}
return $parameter->getDefaultValue();
}
$type = $parameter->getClass()->getName();
$type = isset($this->aliases[$type]) ? $this->aliases[$type] : $type;
if (! $container->has($type)) {
throw new ServiceNotFoundException(sprintf(
'Unable to create service "%s"; unable to resolve parameter "%s" using type hint "%s"',
$requestedName,
$parameter->getName(),
$type
));
}
return $container->get($type);
}
}
PK PވS S 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 Pcg 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)
{
$requestedName = $name;
// We start by checking if we have cached the requested service (this
// is the fastest method).
if (isset($this->services[$requestedName])) {
return $this->services[$requestedName];
}
$name = isset($this->resolvedAliases[$name]) ? $this->resolvedAliases[$name] : $name;
// Next, if the alias should be shared, and we have cached the resolved
// service, use it.
if ($requestedName !== $name
&& (! isset($this->shared[$requestedName]) || $this->shared[$requestedName])
&& isset($this->services[$name])
) {
$this->services[$requestedName] = $this->services[$name];
return $this->services[$name];
}
// At this point, we need to create the instance; we use the resolved
// name for that.
$object = $this->doCreate($name);
// Cache it for later, if it is supposed to be shared.
if (($this->sharedByDefault && ! isset($this->shared[$name]))
|| (isset($this->shared[$name]) && $this->shared[$name])
) {
$this->services[$name] = $object;
}
// Also do so for aliases; this allows sharing based on service name used.
if ($requestedName !== $name
&& (($this->sharedByDefault && ! isset($this->shared[$requestedName]))
|| (isset($this->shared[$requestedName]) && $this->shared[$requestedName]))
) {
$this->services[$requestedName] = $object;
}
return $object;
}
/**
* {@inheritDoc}
*/
public function build($name, array $options = null)
{
// We never cache when using "build"
$name = isset($this->resolvedAliases[$name]) ? $this->resolvedAliases[$name] : $name;
return $this->doCreate($name, $options);
}
/**
* {@inheritDoc}
*/
public function has($name)
{
$name = isset($this->resolvedAliases[$name]) ? $this->resolvedAliases[$name] : $name;
$found = isset($this->services[$name]) || isset($this->factories[$name]);
if ($found) {
return $found;
}
// Check abstract factories
foreach ($this->abstractFactories as $abstractFactory) {
if ($abstractFactory->canCreate($this->creationContext, $name)) {
return true;
}
}
return false;
}
/**
* Indicate whether or not the instance is immutable.
*
* @param bool $flag
*/
public function setAllowOverride($flag)
{
$this->allowOverride = (bool) $flag;
}
/**
* Retrieve the flag indicating immutability status.
*
* @return bool
*/
public function getAllowOverride()
{
return $this->allowOverride;
}
/**
* Configure the service manager
*
* Valid top keys are:
*
* - services: service name => service instance pairs
* - invokables: service name => class name pairs for classes that do not
* have required constructor arguments; internally, maps the class to an
* InvokableFactory instance, and creates an alias if the service name
* and class name do not match.
* - factories: service name => factory pairs; factories may be any
* callable, string name resolving to an invokable class, or string name
* resolving to a FactoryInterface instance.
* - abstract_factories: an array of abstract factories; these may be
* instances of AbstractFactoryInterface, or string names resolving to
* classes that implement that interface.
* - delegators: service name => list of delegator factories for the given
* service; each item in the list may be a callable, a string name
* resolving to an invokable class, or a string name resolving to a class
* implementing DelegatorFactoryInterface.
* - shared: service name => flag pairs; the flag is a boolean indicating
* whether or not the service is shared.
* - aliases: alias => service name pairs.
* - lazy_services: lazy service configuration; can contain the keys:
* - class_map: service name => class name pairs.
* - proxies_namespace: string namespace to use for generated proxy
* classes.
* - proxies_target_dir: directory in which to write generated proxy
* classes; uses system temporary by default.
* - write_proxy_files: boolean indicating whether generated proxy
* classes should be written; defaults to boolean false.
* - shared_by_default: boolean, indicating if services in this instance
* should be shared by default.
*
* @param array $config
* @return self
* @throws ContainerModificationsNotAllowedException if the allow
* override flag has been toggled off, and a service instance
* exists for a given service.
*/
public function configure(array $config)
{
$this->validateOverrides($config);
if (isset($config['services'])) {
$this->services = $config['services'] + $this->services;
}
if (isset($config['invokables']) && ! empty($config['invokables'])) {
$aliases = $this->createAliasesForInvokables($config['invokables']);
$factories = $this->createFactoriesForInvokables($config['invokables']);
if (! empty($aliases)) {
$config['aliases'] = (isset($config['aliases']))
? array_merge($config['aliases'], $aliases)
: $aliases;
}
$config['factories'] = (isset($config['factories']))
? array_merge($config['factories'], $factories)
: $factories;
}
if (isset($config['factories'])) {
$this->factories = $config['factories'] + $this->factories;
}
if (isset($config['delegators'])) {
$this->delegators = array_merge_recursive($this->delegators, $config['delegators']);
}
if (isset($config['shared'])) {
$this->shared = $config['shared'] + $this->shared;
}
if (isset($config['aliases'])) {
$this->configureAliases($config['aliases']);
} elseif (! $this->configured && ! empty($this->aliases)) {
$this->resolveAliases($this->aliases);
}
if (isset($config['shared_by_default'])) {
$this->sharedByDefault = $config['shared_by_default'];
}
// If lazy service configuration was provided, reset the lazy services
// delegator factory.
if (isset($config['lazy_services']) && ! empty($config['lazy_services'])) {
$this->lazyServices = array_merge_recursive($this->lazyServices, $config['lazy_services']);
$this->lazyServicesDelegator = null;
}
// For abstract factories and initializers, we always directly
// instantiate them to avoid checks during service construction.
if (isset($config['abstract_factories'])) {
$this->resolveAbstractFactories($config['abstract_factories']);
}
if (isset($config['initializers'])) {
$this->resolveInitializers($config['initializers']);
}
$this->configured = true;
return $this;
}
/**
* @param string[] $aliases
*
* @return void
*/
private function configureAliases(array $aliases)
{
if (! $this->configured) {
$this->aliases = $aliases + $this->aliases;
$this->resolveAliases($this->aliases);
return;
}
// Performance optimization. If there are no collisions, then we don't need to recompute loops
$intersecting = $this->aliases && \array_intersect_key($this->aliases, $aliases);
$this->aliases = $this->aliases ? \array_merge($this->aliases, $aliases) : $aliases;
if ($intersecting) {
$this->resolveAliases($this->aliases);
return;
}
$this->resolveAliases($aliases);
$this->resolveNewAliasesWithPreviouslyResolvedAliases($aliases);
}
/**
* Add an alias.
*
* @param string $alias
* @param string $target
*/
public function setAlias($alias, $target)
{
$this->configure(['aliases' => [$alias => $target]]);
}
/**
* Add an invokable class mapping.
*
* @param string $name Service name
* @param null|string $class Class to which to map; if omitted, $name is
* assumed.
*/
public function setInvokableClass($name, $class = null)
{
$this->configure(['invokables' => [$name => $class ?: $name]]);
}
/**
* Specify a factory for a given service name.
*
* @param string $name Service name
* @param string|callable|Factory\FactoryInterface $factory Factory to which
* to map.
*/
public function setFactory($name, $factory)
{
$this->configure(['factories' => [$name => $factory]]);
}
/**
* Create a lazy service mapping to a class.
*
* @param string $name Service name to map
* @param null|string $class Class to which to map; if not provided, $name
* will be used for the mapping.
*/
public function mapLazyService($name, $class = null)
{
$this->configure(['lazy_services' => ['class_map' => [$name => $class ?: $name]]]);
}
/**
* Add an abstract factory for resolving services.
*
* @param string|Factory\AbstractFactoryInterface $factory Service name
*/
public function addAbstractFactory($factory)
{
$this->configure(['abstract_factories' => [$factory]]);
}
/**
* Add a delegator for a given service.
*
* @param string $name Service name
* @param string|callable|Factory\DelegatorFactoryInterface $factory Delegator
* factory to assign.
*/
public function addDelegator($name, $factory)
{
$this->configure(['delegators' => [$name => [$factory]]]);
}
/**
* Add an initializer.
*
* @param string|callable|Initializer\InitializerInterface $initializer
*/
public function addInitializer($initializer)
{
$this->configure(['initializers' => [$initializer]]);
}
/**
* Map a service.
*
* @param string $name Service name
* @param array|object $service
*/
public function setService($name, $service)
{
$this->configure(['services' => [$name => $service]]);
}
/**
* Add a service sharing rule.
*
* @param string $name Service name
* @param boolean $flag Whether or not the service should be shared.
*/
public function setShared($name, $flag)
{
$this->configure(['shared' => [$name => (bool) $flag]]);
}
/**
* Instantiate abstract factories for to avoid checks during service construction.
*
* @param string[]|Factory\AbstractFactoryInterface[] $abstractFactories
*
* @return void
*/
private function resolveAbstractFactories(array $abstractFactories)
{
foreach ($abstractFactories as $abstractFactory) {
if (is_string($abstractFactory) && class_exists($abstractFactory)) {
//Cached string
if (! isset($this->cachedAbstractFactories[$abstractFactory])) {
$this->cachedAbstractFactories[$abstractFactory] = new $abstractFactory();
}
$abstractFactory = $this->cachedAbstractFactories[$abstractFactory];
}
if ($abstractFactory instanceof Factory\AbstractFactoryInterface) {
$abstractFactoryObjHash = spl_object_hash($abstractFactory);
$this->abstractFactories[$abstractFactoryObjHash] = $abstractFactory;
continue;
}
// Error condition; let's find out why.
// If we still have a string, we have a class name that does not resolve
if (is_string($abstractFactory)) {
throw new InvalidArgumentException(
sprintf(
'An invalid abstract factory was registered; resolved to class "%s" ' .
'which does not exist; please provide a valid class name resolving ' .
'to an implementation of %s',
$abstractFactory,
AbstractFactoryInterface::class
)
);
}
// Otherwise, we have an invalid type.
throw new InvalidArgumentException(
sprintf(
'An invalid abstract factory was registered. Expected an instance of "%s", ' .
'but "%s" was received',
AbstractFactoryInterface::class,
(is_object($abstractFactory) ? get_class($abstractFactory) : gettype($abstractFactory))
)
);
}
}
/**
* Instantiate initializers for to avoid checks during service construction.
*
* @param string[]|callable[]|Initializer\InitializerInterface[] $initializers
*
* @return void
*/
private function resolveInitializers(array $initializers)
{
foreach ($initializers as $initializer) {
if (is_string($initializer) && class_exists($initializer)) {
$initializer = new $initializer();
}
if (is_callable($initializer)) {
$this->initializers[] = $initializer;
continue;
}
// Error condition; let's find out why.
if (is_string($initializer)) {
throw new InvalidArgumentException(
sprintf(
'An invalid initializer was registered; resolved to class or function "%s" ' .
'which does not exist; please provide a valid function name or class ' .
'name resolving to an implementation of %s',
$initializer,
Initializer\InitializerInterface::class
)
);
}
// Otherwise, we have an invalid type.
throw new InvalidArgumentException(
sprintf(
'An invalid initializer was registered. Expected a callable, or an instance of ' .
'(or string class name resolving to) "%s", ' .
'but "%s" was received',
Initializer\InitializerInterface::class,
(is_object($initializer) ? get_class($initializer) : gettype($initializer))
)
);
}
}
/**
* Resolve aliases to their canonical service names.
*
* @param string[] $aliases
*
* @returns void
*/
private function resolveAliases(array $aliases)
{
foreach ($aliases as $alias => $service) {
$visited = [];
$name = $alias;
while (isset($this->aliases[$name])) {
if (isset($visited[$name])) {
throw CyclicAliasException::fromAliasesMap($aliases);
}
$visited[$name] = true;
$name = $this->aliases[$name];
}
$this->resolvedAliases[$alias] = $name;
}
}
/**
* Rewrites the map of aliases by resolving the given $aliases with the existing resolved ones.
* This is mostly done for performance reasons.
*
* @param string[] $aliases
*
* @return void
*/
private function resolveNewAliasesWithPreviouslyResolvedAliases(array $aliases)
{
foreach ($this->resolvedAliases as $name => $target) {
if (isset($aliases[$target])) {
$this->resolvedAliases[$name] = $this->resolvedAliases[$target];
}
}
}
/**
* Get a factory for the given service name
*
* @param string $name
* @return callable
* @throws ServiceNotFoundException
*/
private function getFactory($name)
{
$factory = isset($this->factories[$name]) ? $this->factories[$name] : null;
$lazyLoaded = false;
if (is_string($factory) && class_exists($factory)) {
$factory = new $factory();
$lazyLoaded = true;
}
if (is_callable($factory)) {
if ($lazyLoaded) {
$this->factories[$name] = $factory;
}
return $factory;
}
// Check abstract factories
foreach ($this->abstractFactories as $abstractFactory) {
if ($abstractFactory->canCreate($this->creationContext, $name)) {
return $abstractFactory;
}
}
throw new ServiceNotFoundException(sprintf(
'Unable to resolve service "%s" to a factory; are you certain you provided it during configuration?',
$name
));
}
/**
* @param string $name
* @param null|array $options
* @return object
*/
private function createDelegatorFromName($name, array $options = null)
{
$creationCallback = function () use ($name, $options) {
// Code is inlined for performance reason, instead of abstracting the creation
$factory = $this->getFactory($name);
return $factory($this->creationContext, $name, $options);
};
foreach ($this->delegators[$name] as $index => $delegatorFactory) {
$delegatorFactory = $this->delegators[$name][$index];
if ($delegatorFactory === Proxy\LazyServiceFactory::class) {
$delegatorFactory = $this->createLazyServiceDelegatorFactory();
}
if (is_string($delegatorFactory) && class_exists($delegatorFactory)) {
$delegatorFactory = new $delegatorFactory();
}
if (! is_callable($delegatorFactory)) {
if (is_string($delegatorFactory)) {
throw new ServiceNotCreatedException(sprintf(
'An invalid delegator factory was registered; resolved to class or function "%s" '
. 'which does not exist; please provide a valid function name or class name resolving '
. 'to an implementation of %s',
$delegatorFactory,
DelegatorFactoryInterface::class
));
}
throw new ServiceNotCreatedException(sprintf(
'A non-callable delegator, "%s", was provided; expected a callable or instance of "%s"',
is_object($delegatorFactory) ? get_class($delegatorFactory) : gettype($delegatorFactory),
DelegatorFactoryInterface::class
));
}
$this->delegators[$name][$index] = $delegatorFactory;
$creationCallback = function () use ($delegatorFactory, $name, $creationCallback, $options) {
return $delegatorFactory($this->creationContext, $name, $creationCallback, $options);
};
}
return $creationCallback($this->creationContext, $name, $creationCallback, $options);
}
/**
* Create a new instance with an already resolved name
*
* This is a highly performance sensitive method, do not modify if you have not benchmarked it carefully
*
* @param string $resolvedName
* @param null|array $options
* @return mixed
* @throws ServiceNotFoundException if unable to resolve the service.
* @throws ServiceNotCreatedException if an exception is raised when
* creating a service.
* @throws ContainerException if any other error occurs
*/
private function doCreate($resolvedName, array $options = null)
{
try {
if (! isset($this->delegators[$resolvedName])) {
// Let's create the service by fetching the factory
$factory = $this->getFactory($resolvedName);
$object = $factory($this->creationContext, $resolvedName, $options);
} else {
$object = $this->createDelegatorFromName($resolvedName, $options);
}
} catch (ContainerException $exception) {
throw $exception;
} catch (Exception $exception) {
throw new ServiceNotCreatedException(sprintf(
'Service with name "%s" could not be created. Reason: %s',
$resolvedName,
$exception->getMessage()
), (int) $exception->getCode(), $exception);
}
foreach ($this->initializers as $initializer) {
$initializer($this->creationContext, $object);
}
return $object;
}
/**
* Create the lazy services delegator factory.
*
* Creates the lazy services delegator factory based on the lazy_services
* configuration present.
*
* @return Proxy\LazyServiceFactory
* @throws ServiceNotCreatedException when the lazy service class_map
* configuration is missing
*/
private function createLazyServiceDelegatorFactory()
{
if ($this->lazyServicesDelegator) {
return $this->lazyServicesDelegator;
}
if (! isset($this->lazyServices['class_map'])) {
throw new ServiceNotCreatedException('Missing "class_map" config key in "lazy_services"');
}
$factoryConfig = new ProxyConfiguration();
if (isset($this->lazyServices['proxies_namespace'])) {
$factoryConfig->setProxiesNamespace($this->lazyServices['proxies_namespace']);
}
if (isset($this->lazyServices['proxies_target_dir'])) {
$factoryConfig->setProxiesTargetDir($this->lazyServices['proxies_target_dir']);
}
if (! isset($this->lazyServices['write_proxy_files']) || ! $this->lazyServices['write_proxy_files']) {
$factoryConfig->setGeneratorStrategy(new EvaluatingGeneratorStrategy());
} else {
$factoryConfig->setGeneratorStrategy(new FileWriterGeneratorStrategy(
new FileLocator($factoryConfig->getProxiesTargetDir())
));
}
spl_autoload_register($factoryConfig->getProxyAutoloader());
$this->lazyServicesDelegator = new Proxy\LazyServiceFactory(
new LazyLoadingValueHolderFactory($factoryConfig),
$this->lazyServices['class_map']
);
return $this->lazyServicesDelegator;
}
/**
* Create aliases for invokable classes.
*
* If an invokable service name does not match the class it maps to, this
* creates an alias to the class (which will later be mapped as an
* invokable factory).
*
* @param array $invokables
* @return array
*/
private function createAliasesForInvokables(array $invokables)
{
$aliases = [];
foreach ($invokables as $name => $class) {
if ($name === $class) {
continue;
}
$aliases[$name] = $class;
}
return $aliases;
}
/**
* Create invokable factories for invokable classes.
*
* If an invokable service name does not match the class it maps to, this
* creates an invokable factory entry for the class name; otherwise, it
* creates an invokable factory for the entry name.
*
* @param array $invokables
* @return array
*/
private function createFactoriesForInvokables(array $invokables)
{
$factories = [];
foreach ($invokables as $name => $class) {
if ($name === $class) {
$factories[$name] = Factory\InvokableFactory::class;
continue;
}
$factories[$class] = Factory\InvokableFactory::class;
}
return $factories;
}
/**
* Determine if one or more services already exist in the container.
*
* If the allow override flag is true or it's first time configured,
* this method does nothing.
*
* Otherwise, it checks against each of the following service types,
* if present, and validates that none are defining services that
* already exist; if they do, it raises an exception indicating
* modification is not allowed.
*
* @param array $config
* @throws ContainerModificationsNotAllowedException if any services
* provided already have instances available.
*/
private function validateOverrides(array $config)
{
if ($this->allowOverride || ! $this->configured) {
return;
}
if (isset($config['services'])) {
$this->validateOverrideSet(array_keys($config['services']), 'service');
}
if (isset($config['aliases'])) {
$this->validateOverrideSet(array_keys($config['aliases']), 'alias');
}
if (isset($config['invokables'])) {
$this->validateOverrideSet(array_keys($config['invokables']), 'invokable class');
}
if (isset($config['factories'])) {
$this->validateOverrideSet(array_keys($config['factories']), 'factory');
}
if (isset($config['delegators'])) {
$this->validateOverrideSet(array_keys($config['delegators']), 'delegator');
}
if (isset($config['shared'])) {
$this->validateOverrideSet(array_keys($config['shared']), 'sharing rule');
}
if (isset($config['lazy_services']['class_map'])) {
$this->validateOverrideSet(array_keys($config['lazy_services']['class_map']), 'lazy service');
}
}
/**
* Determine if one or more services already exist for a given type.
*
* Loops through the provided service names, checking if any have current
* service instances; if not, it returns, but otherwise, it raises an
* exception indicating modification is not allowed.
*
* @param string[] $services
* @param string $type Type of service being checked.
* @throws ContainerModificationsNotAllowedException if any services
* provided already have instances available.
*/
private function validateOverrideSet(array $services, $type)
{
$detected = [];
foreach ($services as $service) {
if (isset($this->services[$service])) {
$detected[] = $service;
}
}
if (empty($detected)) {
return;
}
throw new ContainerModificationsNotAllowedException(sprintf(
'An updated/new %s is not allowed, as the container does not allow '
. 'changes for services with existing instances; the following '
. 'already exist in the container: %s',
$type,
implode(', ', $detected)
));
}
}
PK PAk src/FactoryInterface.phpnu ٘ getClassName($className);
return sprintf(
self::FACTORY_TEMPLATE,
str_replace('\\' . $class, '', $className),
$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
);
}
}
}
PK P@= 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 P&7D~ 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 (! count($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 (! count($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 Pm " 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 (! count($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 P/Fe e ( 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 PP%MH H CHANGELOG.mdnu ٘ # Changelog
All notable changes to this project will be documented in this file, in reverse chronological order by release.
## 3.2.1 - 2017-02-15
### Added
- [zendframework/zend-servicemanager#176](https://github.com/zendframework/zend-servicemanager/pull/176) adds
the options `-i` or `--ignore-unresolved` to the shipped
`generate-deps-for-config-factory` command. This flag allows it to build
configuration for classes resolved by the `ConfigAbstractFactory` that
typehint on interfaces, which was previously unsupported.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- [zendframework/zend-servicemanager#174](https://github.com/zendframework/zend-servicemanager/pull/174) updates
the `ConfigAbstractFactory` to allow the `config` service to be either an
`array` or an `ArrayObject`; previously, only `array` was supported.
## 3.2.0 - 2016-12-19
### Added
- [zendframework/zend-servicemanager#146](https://github.com/zendframework/zend-servicemanager/pull/146) adds
`Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory`, which enables a
configuration-based approach to providing class dependencies when all
dependencies are services known to the `ServiceManager`. Please see
[the documentation](doc/book/config-abstract-factory.md) for details.
- [zendframework/zend-servicemanager#154](https://github.com/zendframework/zend-servicemanager/pull/154) adds
`Laminas\ServiceManager\Tool\ConfigDumper`, which will introspect a given class
to determine dependencies, and then create configuration for
`Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory`, merging it with
the provided configuration file. It also adds a vendor binary,
`generate-deps-for-config-factory`, for generating these from the command
line.
- [zendframework/zend-servicemanager#154](https://github.com/zendframework/zend-servicemanager/pull/154) adds
`Laminas\ServiceManager\Tool\FactoryCreator`, which will introspect a given class
and generate a factory for it. It also adds a vendor binary,
`generate-factory-for-class`, for generating these from the command line.
- [zendframework/zend-servicemanager#153](https://github.com/zendframework/zend-servicemanager/pull/153) adds
`Laminas\ServiceManager\AbstractFactory\ReflectionBasedAbstractFactory`. This
class may be used as either a mapped factory or an abstract factory, and will
use reflection in order to determine which dependencies to use from the
container when instantiating the requested service, with the following rules:
- Scalar values are not allowed, unless they have default values associated.
- Values named `$config` type-hinted against `array` will be injected with the
`config` service, if present.
- All other array values will be provided an empty array.
- Class/interface typehints will be pulled from the container.
- [zendframework/zend-servicemanager#150](https://github.com/zendframework/zend-servicemanager/pull/150) adds
a "cookbook" section to the documentation, with an initial document detailing
the pros and cons of abstract factory usage.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- [zendframework/zend-servicemanager#106](https://github.com/zendframework/zend-servicemanager/pull/106) adds
detection of multiple attempts to register the same instance or named abstract
factory, using a previous instance when detected. You may still use multiple
discrete instances, however.
## 3.1.2 - 2016-12-19
### Added
- Nothing.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- [zendframework/zend-servicemanager#167](https://github.com/zendframework/zend-servicemanager/pull/167) fixes
how exception codes are provided to ServiceNotCreatedException. Previously,
the code was provided as-is. However, some PHP internal exception classes,
notably PDOException, can sometimes return other values (such as strings),
which can lead to fatal errors when instantiating the new exception. The
patch provided casts exception codes to integers to prevent these errors.
## 3.1.1 - 2016-07-15
### Added
- Nothing.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- [zendframework/zend-servicemanager#136](https://github.com/zendframework/zend-servicemanager/pull/136) removes
several imports to classes in subnamespaces within the `ServiceManager`
classfile, removing potential name resolution conflicts that occurred in edge
cases when testing.
## 3.1.0 - 2016-06-01
### Added
- [zendframework/zend-servicemanager#103](https://github.com/zendframework/zend-servicemanager/pull/103) Allowing
installation of `ocramius/proxy-manager` `^2.0` together with
`zendframework/zend-servicemanager`.
- [zendframework/zend-servicemanager#103](https://github.com/zendframework/zend-servicemanager/pull/103) Disallowing
test failures when running tests against PHP `7.0.*`.
- [zendframework/zend-servicemanager#113](https://github.com/zendframework/zend-servicemanager/pull/113) Improved performance
when dealing with registering aliases and factories via `ServiceManager#setFactory()` and
`ServiceManager#setAlias()`
- [zendframework/zend-servicemanager#120](https://github.com/zendframework/zend-servicemanager/pull/120) The
`laminas/laminas-servicemanager` component now provides a
`container-interop/container-interop-implementation` implementation
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- [zendframework/zend-servicemanager#97](https://github.com/zendframework/zend-servicemanager/pull/97) Typo corrections
in the delegator factories documentation.
- [zendframework/zend-servicemanager#98](https://github.com/zendframework/zend-servicemanager/pull/98) Using coveralls ^1.0
for tracking test code coverage changes.
## 3.0.4 - TBD
### Added
- Nothing.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- Nothing.
## 3.0.3 - 2016-02-02
### Added
- [zendframework/zend-servicemanager#89](https://github.com/zendframework/zend-servicemanager/pull/89) adds
cyclic alias detection to the `ServiceManager`; it now raises a
`Laminas\ServiceManager\Exception\CyclicAliasException` when one is detected,
detailing the cycle detected.
- [zendframework/zend-servicemanager#95](https://github.com/zendframework/zend-servicemanager/pull/95) adds
GitHub Pages publication automation, and moves the documentation to
https://docs.laminas.dev/laminas-servicemanager/
- [zendframework/zend-servicemanager#93](https://github.com/zendframework/zend-servicemanager/pull/93) adds
`Laminas\ServiceManager\Test\CommonPluginManagerTrait`, which can be used to
validate that a plugin manager instance is ready for version 3.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- [zendframework/zend-servicemanager#90](https://github.com/zendframework/zend-servicemanager/pull/90) fixes
several examples in the configuration chapter of the documentation, ensuring
that the signatures are correct.
- [zendframework/zend-servicemanager#92](https://github.com/zendframework/zend-servicemanager/pull/92) ensures
that alias resolution is skipped during configuration if no aliases are
present, and forward-ports the test from [zendframework/zend-servicemanager#81](https://github.com/zendframework/zend-servicemanager/pull/81)
to validate v2/v3 compatibility for plugin managers.
## 3.0.2 - 2016-01-24
### Added
- [zendframework/zend-servicemanager#64](https://github.com/zendframework/zend-servicemanager/pull/64) performance optimizations
when dealing with alias resolution during service manager instantiation
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- [zendframework/zend-servicemanager#62](https://github.com/zendframework/zend-servicemanager/pull/62)
[zendframework/zend-servicemanager#64](https://github.com/zendframework/zend-servicemanager/pull/64) corrected benchmark assets signature
- [zendframework/zend-servicemanager#72](https://github.com/zendframework/zend-servicemanager/pull/72) corrected link to the Proxy Pattern Wikipedia
page in the documentation
- [zendframework/zend-servicemanager#78](https://github.com/zendframework/zend-servicemanager/issues/78)
[zendframework/zend-servicemanager#79](https://github.com/zendframework/zend-servicemanager/pull/79) creation context was not being correctly passed
to abstract factories when using plugin managers
- [zendframework/zend-servicemanager#82](https://github.com/zendframework/zend-servicemanager/pull/82) corrected migration guide in the DocBlock of
the `InitializerInterface`
## 3.0.1 - 2016-01-19
### Added
- Nothing.
### Deprecated
- Nothing.
### Removed
- [zendframework/zend-servicemanager#68](https://github.com/zendframework/zend-servicemanager/pull/68) removes
the dependency on laminas-stdlib by inlining the `ArrayUtils::merge()` routine
as a private method of `Laminas\ServiceManager\Config`.
### Fixed
- Nothing.
## 3.0.0 - 2016-01-11
First stable release of version 3 of laminas-servicemanager.
Documentation is now available at http://laminas-servicemanager.rtfd.org
### Added
- You can now map multiple key names to the same factory. It was previously
possible in Laminas but it was not enforced by the `FactoryInterface` interface.
Now the interface receives the `$requestedName` as the *second* parameter
(previously, it was the third).
Example:
```php
$sm = new \Laminas\ServiceManager\ServiceManager([
'factories' => [
MyClassA::class => MyFactory::class,
MyClassB::class => MyFactory::class,
'MyClassC' => 'MyFactory' // This is equivalent as using ::class
],
]);
$sm->get(MyClassA::class); // MyFactory will receive MyClassA::class as second parameter
```
- Writing a plugin manager has been simplified. If you have simple needs, you no
longer need to implement the complete `validate` method.
In versions 2.x, if your plugin manager only allows creating instances that
implement `Laminas\Validator\ValidatorInterface`, you needed to write the
following code:
```php
class MyPluginManager extends AbstractPluginManager
{
public function validate($instance)
{
if ($instance instanceof \Laminas\Validator\ValidatorInterface) {
return;
}
throw new InvalidServiceException(sprintf(
'Plugin manager "%s" expected an instance of type "%s", but "%s" was received',
__CLASS__,
\Laminas\Validator\ValidatorInterface::class,
is_object($instance) ? get_class($instance) : gettype($instance)
));
}
}
```
In version 3, this becomes:
```php
use Laminas\ServiceManager\AbstractPluginManager;
use Laminas\Validator\ValidatorInterface;
class MyPluginManager extends AbstractPluginManager
{
protected $instanceOf = ValidatorInterface::class;
}
```
Of course, you can still override the `validate` method if your logic is more
complex.
To aid migration, `validate()` will check for a `validatePlugin()` method (which
was required in v2), and proxy to it if found, after emitting an
`E_USER_DEPRECATED` notice prompting you to rename the method.
- A new method, `configure()`, was added, allowing full configuration of the
`ServiceManager` instance at once. Each of the various configuration methods —
`setAlias()`, `setInvokableClass()`, etc. — now proxy to this method.
- A new method, `mapLazyService($name, $class = null)`, was added, to allow
mapping a lazy service, and as an analog to the other various service
definition methods.
### Deprecated
- Nothing
### Removed
- Peering has been removed. It was a complex and rarely used feature that was
misunderstood most of the time.
- Integration with `Laminas\Di` has been removed. It may be re-integrated later.
- `MutableCreationOptionsInterface` has been removed, as options can now be
passed directly through factories.
- `ServiceLocatorAwareInterface` and its associated trait has been removed. It
was an anti-pattern, and you are encouraged to inject your dependencies in
factories instead of injecting the whole service locator.
### Changed/Fixed
v3 of the ServiceManager component is a completely rewritten, more efficient
implementation of the service locator pattern. It includes a number of breaking
changes, outlined in this section.
- You no longer need a `Laminas\ServiceManager\Config` object to configure the
service manager; you can pass the configuration array directly instead.
In version 2.x:
```php
$config = new \Laminas\ServiceManager\Config([
'factories' => [...]
]);
$sm = new \Laminas\ServiceManager\ServiceManager($config);
```
In Laminas 3.x:
```php
$sm = new \Laminas\ServiceManager\ServiceManager([
'factories' => [...]
]);
```
`Config` and `ConfigInterface` still exist, however, but primarily for the
purposes of codifying and aggregating configuration to use.
- `ConfigInterface` has two important changes:
- `configureServiceManager()` now **must** return the updated service manager
instance.
- A new method, `toArray()`, was added, to allow pulling the configuration in
order to pass to a ServiceManager or plugin manager's constructor or
`configure()` method.
- Interfaces for `FactoryInterface`, `DelegatorFactoryInterface` and
`AbstractFactoryInterface` have changed. All are now directly invokable. This
allows a number of performance optimization internally.
Additionally, all signatures that accepted a "canonical name" argument now
remove it.
Most of the time, rewriting a factory to match the new interface implies
replacing the method name by `__invoke`, and removing the canonical name
argument if present.
For instance, here is a simple version 2.x factory:
```php
class MyFactory implements FactoryInterface
{
function createService(ServiceLocatorInterface $sl)
{
// ...
}
}
```
The equivalent version 3 factory:
```php
class MyFactory implements FactoryInterface
{
function __invoke(ServiceLocatorInterface $sl, $requestedName)
{
// ...
}
}
```
Note another change in the above: factories also receive a second parameter,
enforced through the interface, that allows you to easily map multiple service
names to the same factory.
To provide forwards compatibility, the original interfaces have been retained,
but extend the new interfaces (which are under new namespaces). You can implement
the new methods in your existing v2 factories in order to make them forwards
compatible with v3.
- The for `AbstractFactoryInterface` interface renames the method `canCreateServiceWithName()`
to `canCreate()`, and merges the `$name` and `$requestedName` arguments.
- Plugin managers will now receive the parent service locator instead of itself
in factories. In version 2.x, you needed to call the method
`getServiceLocator()` to retrieve the parent (application) service locator.
This was confusing, and not IDE friendly as this method was not enforced
through the interface.
In version 2.x, if a factory was set to a service name defined in a plugin manager:
```php
class MyFactory implements FactoryInterface
{
function createService(ServiceLocatorInterface $sl)
{
// $sl is actually a plugin manager
$parentLocator = $sl->getServiceLocator();
// ...
}
}
```
In version 3:
```php
class MyFactory implements FactoryInterface
{
function __invoke(ServiceLocatorInterface $sl, $requestedName)
{
// $sl is already the main, parent service locator. If you need to
// retrieve the plugin manager again, you can retrieve it through the
// servicelocator:
$pluginManager = $sl->get(MyPluginManager::class);
// ...
}
}
```
In practice, this should reduce code, as dependencies often come from the main
service locator, and not the plugin manager itself.
To assist in migration, the method `getServiceLocator()` was added to `ServiceManager`
to ensure that existing factories continue to work; the method emits an `E_USER_DEPRECATED`
message to signal developers to update their factories.
- `PluginManager` now enforces the need for the main service locator in its
constructor. In v2.x, people often forgot to set the parent locator, which led
to bugs in factories trying to fetch dependencies from the parent locator.
Additionally, plugin managers now pull dependencies from the parent locator by
default; if you need to pull a peer plugin, your factories will now need to
pull the corresponding plugin manager first.
If you omit passing a service locator to the constructor, your plugin manager
will continue to work, but will emit a deprecation notice indicatin you
should update your initialization code.
- It's so fast now that your app will fly!
## 2.7.0 - 2016-01-11
### Added
- [zendframework/zend-servicemanager#60](https://github.com/zendframework/zend-servicemanager/pull/60) adds
forward compatibility features for `AbstractPluingManager` and introduces
`InvokableFactory` to help forward migration to version 3.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- [zendframework/zend-servicemanager#46](https://github.com/zendframework/zend-servicemanager/pull/46) updates
the exception hierarchy to inherit from the container-interop exceptions.
This ensures that all exceptions thrown by the component follow the
recommendations of that project.
- [zendframework/zend-servicemanager#52](https://github.com/zendframework/zend-servicemanager/pull/52) fixes
the exception message thrown by `ServiceManager::setFactory()` to remove
references to abstract factories.
## 2.6.0 - 2015-07-23
### Added
- [zendframework/zend-servicemanager#4](https://github.com/zendframework/zend-servicemanager/pull/4) updates the
`ServiceManager` to [implement the container-interop interface](https://github.com/container-interop/container-interop),
allowing interoperability with applications that consume that interface.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- [zendframework/zend-servicemanager#3](https://github.com/zendframework/zend-servicemanager/pull/3) properly updates the
codebase to PHP 5.5, by taking advantage of the default closure binding
(`$this` in a closure is the invoking object when created within a method). It
also removes several `@requires PHP 5.4.0` annotations.
PK PeU U $ bin/generate-deps-for-config-factorynu ȯ #!/usr/bin/env php
F ; src/Exception/ContainerModificationsNotAllowedException.phpnu ٘ PK P-ڸ &