Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Tree: d66fd64cdc
Fetching contributors…

Cannot retrieve contributors at this time

987 lines (873 sloc) 30.333 kB
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\ServiceManager;
use ReflectionClass;
class ServiceManager implements ServiceLocatorInterface
{
/**@#+
* Constants
*/
const SCOPE_PARENT = 'parent';
const SCOPE_CHILD = 'child';
/**@#-*/
/**
* Lookup for canonicalized names.
*
* @var array
*/
protected $canonicalNames = array();
/**
* @var bool
*/
protected $allowOverride = false;
/**
* @var array
*/
protected $invokableClasses = array();
/**
* @var string|callable|\Closure|FactoryInterface[]
*/
protected $factories = array();
/**
* @var AbstractFactoryInterface[]
*/
protected $abstractFactories = array();
/**
* @var array
*/
protected $pendingAbstractFactoryRequests = array();
/**
* @var array
*/
protected $shared = array();
/**
* Registered services and cached values
*
* @var array
*/
protected $instances = array();
/**
* @var array
*/
protected $aliases = array();
/**
* @var array
*/
protected $initializers = array();
/**
* @var ServiceManager[]
*/
protected $peeringServiceManagers = array();
/**
* Whether or not to share by default
*
* @var bool
*/
protected $shareByDefault = true;
/**
* @var bool
*/
protected $retrieveFromPeeringManagerFirst = false;
/**
* @var bool Track whether not to throw exceptions during create()
*/
protected $throwExceptionInCreate = true;
/**
* @var array map of characters to be replaced through strtr
*/
protected $canonicalNamesReplacements = array('-' => '', '_' => '', ' ' => '', '\\' => '', '/' => '');
/**
* Constructor
*
* @param ConfigInterface $config
*/
public function __construct(ConfigInterface $config = null)
{
if ($config) {
$config->configureServiceManager($this);
}
}
/**
* Set allow override
*
* @param $allowOverride
* @return ServiceManager
*/
public function setAllowOverride($allowOverride)
{
$this->allowOverride = (bool) $allowOverride;
return $this;
}
/**
* Get allow override
*
* @return bool
*/
public function getAllowOverride()
{
return $this->allowOverride;
}
/**
* Set flag indicating whether services are shared by default
*
* @param bool $shareByDefault
* @return ServiceManager
* @throws Exception\RuntimeException if allowOverride is false
*/
public function setShareByDefault($shareByDefault)
{
if ($this->allowOverride === false) {
throw new Exception\RuntimeException(sprintf(
'%s: cannot alter default shared service setting; container is marked immutable (allow_override is false)',
__METHOD__
));
}
$this->shareByDefault = (bool) $shareByDefault;
return $this;
}
/**
* Are services shared by default?
*
* @return bool
*/
public function shareByDefault()
{
return $this->shareByDefault;
}
/**
* Set throw exceptions in create
*
* @param bool $throwExceptionInCreate
* @return ServiceManager
*/
public function setThrowExceptionInCreate($throwExceptionInCreate)
{
$this->throwExceptionInCreate = $throwExceptionInCreate;
return $this;
}
/**
* Get throw exceptions in create
*
* @return bool
*/
public function getThrowExceptionInCreate()
{
return $this->throwExceptionInCreate;
}
/**
* Set flag indicating whether to pull from peering manager before attempting creation
*
* @param bool $retrieveFromPeeringManagerFirst
* @return ServiceManager
*/
public function setRetrieveFromPeeringManagerFirst($retrieveFromPeeringManagerFirst = true)
{
$this->retrieveFromPeeringManagerFirst = (bool) $retrieveFromPeeringManagerFirst;
return $this;
}
/**
* Should we retrieve from the peering manager prior to attempting to create a service?
*
* @return bool
*/
public function retrieveFromPeeringManagerFirst()
{
return $this->retrieveFromPeeringManagerFirst;
}
/**
* Set invokable class
*
* @param string $name
* @param string $invokableClass
* @param bool $shared
* @return ServiceManager
* @throws Exception\InvalidServiceNameException
*/
public function setInvokableClass($name, $invokableClass, $shared = null)
{
$cName = $this->canonicalizeName($name);
if ($this->has(array($cName, $name), false)) {
if ($this->allowOverride === false) {
throw new Exception\InvalidServiceNameException(sprintf(
'A service by the name or alias "%s" already exists and cannot be overridden; please use an alternate name',
$cName
));
}
$this->unregisterService($cName);
}
if ($shared === null) {
$shared = $this->shareByDefault();
}
$this->invokableClasses[$cName] = $invokableClass;
$this->shared[$cName] = (bool) $shared;
return $this;
}
/**
* Set factory
*
* @param string $name
* @param string|FactoryInterface|callable $factory
* @param bool $shared
* @return ServiceManager
* @throws Exception\InvalidArgumentException
* @throws Exception\InvalidServiceNameException
*/
public function setFactory($name, $factory, $shared = null)
{
$cName = $this->canonicalizeName($name);
if (!is_string($factory) && !$factory instanceof FactoryInterface && !is_callable($factory)) {
throw new Exception\InvalidArgumentException(
'Provided abstract factory must be the class name of an abstract factory or an instance of an AbstractFactoryInterface.'
);
}
if ($this->has(array($cName, $name), false)) {
if ($this->allowOverride === false) {
throw new Exception\InvalidServiceNameException(sprintf(
'A service by the name or alias "%s" already exists and cannot be overridden, please use an alternate name',
$cName
));
}
$this->unregisterService($cName);
}
if ($shared === null) {
$shared = $this->shareByDefault();
}
$this->factories[$cName] = $factory;
$this->shared[$cName] = (bool) $shared;
return $this;
}
/**
* Add abstract factory
*
* @param AbstractFactoryInterface|string $factory
* @param bool $topOfStack
* @return ServiceManager
* @throws Exception\InvalidArgumentException if the abstract factory is invalid
*/
public function addAbstractFactory($factory, $topOfStack = true)
{
if (!is_string($factory) && !$factory instanceof AbstractFactoryInterface) {
throw new Exception\InvalidArgumentException(
'Provided abstract factory must be the class name of an abstract factory or an instance of an AbstractFactoryInterface.'
);
}
if (is_string($factory)) {
if (!class_exists($factory, true)) {
throw new Exception\InvalidArgumentException(
'Provided abstract factory must be the class name of an abstract factory or an instance of an AbstractFactoryInterface.'
);
}
$refl = new ReflectionClass($factory);
if (!$refl->implementsInterface(__NAMESPACE__ . '\\AbstractFactoryInterface')) {
throw new Exception\InvalidArgumentException(
'Provided abstract factory must be the class name of an abstract factory or an instance of an AbstractFactoryInterface.'
);
}
}
if ($topOfStack) {
array_unshift($this->abstractFactories, $factory);
} else {
array_push($this->abstractFactories, $factory);
}
return $this;
}
/**
* Add initializer
*
* @param callable|InitializerInterface $initializer
* @param bool $topOfStack
* @return ServiceManager
* @throws Exception\InvalidArgumentException
*/
public function addInitializer($initializer, $topOfStack = true)
{
if (!is_callable($initializer) && !$initializer instanceof InitializerInterface) {
if (!is_string($initializer)
|| !$this->isSubclassOf($initializer, __NAMESPACE__ . '\InitializerInterface')
) {
throw new Exception\InvalidArgumentException('$initializer should be callable.');
}
$initializer = new $initializer;
}
if ($topOfStack) {
array_unshift($this->initializers, $initializer);
} else {
array_push($this->initializers, $initializer);
}
return $this;
}
/**
* Register a service with the locator
*
* @param string $name
* @param mixed $service
* @return ServiceManager
* @throws Exception\InvalidServiceNameException
*/
public function setService($name, $service)
{
$cName = $this->canonicalizeName($name);
if ($this->has($cName, false)) {
if ($this->allowOverride === false) {
throw new Exception\InvalidServiceNameException(sprintf(
'%s: A service by the name "%s" or alias already exists and cannot be overridden, please use an alternate name.',
__METHOD__,
$name
));
}
$this->unregisterService($cName);
}
$this->instances[$cName] = $service;
return $this;
}
/**
* @param string $name
* @param bool $isShared
* @return ServiceManager
* @throws Exception\ServiceNotFoundException
*/
public function setShared($name, $isShared)
{
$cName = $this->canonicalizeName($name);
if (
!isset($this->invokableClasses[$cName])
&& !isset($this->factories[$cName])
&& !$this->canCreateFromAbstractFactory($cName, $name)
) {
throw new Exception\ServiceNotFoundException(sprintf(
'%s: A service by the name "%s" was not found and could not be marked as shared',
__METHOD__,
$name
));
}
$this->shared[$cName] = (bool) $isShared;
return $this;
}
/**
* Retrieve a registered instance
*
* @param string $name
* @param bool $usePeeringServiceManagers
* @throws Exception\ServiceNotFoundException
* @return object|array
*/
public function get($name, $usePeeringServiceManagers = true)
{
$cName = $this->canonicalizeName($name);
$isAlias = false;
if ($this->hasAlias($cName)) {
$isAlias = true;
do {
$cName = $this->aliases[$cName];
} while ($this->hasAlias($cName));
}
$instance = null;
$retrieveFromPeeringManagerFirst = $this->retrieveFromPeeringManagerFirst();
if ($usePeeringServiceManagers && $retrieveFromPeeringManagerFirst) {
$instance = $this->retrieveFromPeeringManager($name);
if(null !== $instance) {
return $instance;
}
}
if (isset($this->instances[$cName])) {
return $this->instances[$cName];
}
if (!$instance) {
if ($this->canCreate(array($cName, $name))) {
$instance = $this->create(array($cName, $name));
} elseif ($usePeeringServiceManagers && !$retrieveFromPeeringManagerFirst) {
$instance = $this->retrieveFromPeeringManager($name);
}
}
// Still no instance? raise an exception
if ($instance === null && !is_array($instance)) {
if ($isAlias) {
throw new Exception\ServiceNotFoundException(sprintf(
'An alias "%s" was requested but no service could be found.',
$name
));
}
throw new Exception\ServiceNotFoundException(sprintf(
'%s was unable to fetch or create an instance for %s',
__METHOD__,
$name
));
}
if (
($this->shareByDefault() && !isset($this->shared[$cName]))
|| (isset($this->shared[$cName]) && $this->shared[$cName] === true)
) {
$this->instances[$cName] = $instance;
}
return $instance;
}
/**
* Create an instance
*
* @param string|array $name
* @return bool|object
* @throws Exception\ServiceNotFoundException
* @throws Exception\ServiceNotCreatedException
*/
public function create($name)
{
$instance = false;
if (is_array($name)) {
list($cName, $rName) = $name;
} else {
$rName = $name;
$cName = $this->canonicalizeName($rName);
}
if (isset($this->factories[$cName])) {
$instance = $this->createFromFactory($cName, $rName);
}
if ($instance === false && isset($this->invokableClasses[$cName])) {
$instance = $this->createFromInvokable($cName, $rName);
}
if ($instance === false && $this->canCreateFromAbstractFactory($cName, $rName)) {
$instance = $this->createFromAbstractFactory($cName, $rName);
}
if ($this->throwExceptionInCreate == true && $instance === false) {
throw new Exception\ServiceNotFoundException(sprintf(
'No valid instance was found for %s%s',
$cName,
($rName ? '(alias: ' . $rName . ')' : '')
));
}
foreach ($this->initializers as $initializer) {
if ($initializer instanceof InitializerInterface) {
$initializer->initialize($instance, $this);
} elseif (is_object($initializer) && is_callable($initializer)) {
$initializer($instance, $this);
} else {
call_user_func($initializer, $instance, $this);
}
}
return $instance;
}
/**
* Determine if we can create an instance.
*
* @param string|array $name
* @param bool $checkAbstractFactories
* @return bool
*/
public function canCreate($name, $checkAbstractFactories = true)
{
if (is_array($name)) {
list($cName, $rName) = $name;
} else {
$rName = $name;
$cName = $this->canonicalizeName($rName);
}
if (
isset($this->invokableClasses[$cName])
|| isset($this->factories[$cName])
|| isset($this->aliases[$cName])
|| isset($this->instances[$cName])
) {
return true;
}
if ($checkAbstractFactories && $this->canCreateFromAbstractFactory($cName, $rName)) {
return true;
}
return false;
}
/**
* @param string|array $name
* @param bool $checkAbstractFactories
* @param bool $usePeeringServiceManagers
* @return bool
*/
public function has($name, $checkAbstractFactories = true, $usePeeringServiceManagers = true)
{
if (is_array($name)) {
list($cName, $rName) = $name;
} else {
$rName = $name;
$cName = $this->canonicalizeName($rName);
}
if ($this->canCreate(array($cName, $rName), $checkAbstractFactories)) {
return true;
}
if ($usePeeringServiceManagers) {
foreach ($this->peeringServiceManagers as $peeringServiceManager) {
if ($peeringServiceManager->has($rName)) {
return true;
}
}
}
return false;
}
/**
* Determine if we can create an instance from an abstract factory.
*
* @param string $cName
* @param string $rName
* @return bool
*/
public function canCreateFromAbstractFactory($cName, $rName)
{
// check abstract factories
foreach ($this->abstractFactories as $index => $abstractFactory) {
// Support string abstract factory class names
if (is_string($abstractFactory) && class_exists($abstractFactory, true)) {
$this->abstractFactories[$index] = $abstractFactory = new $abstractFactory();
}
if (
isset($this->pendingAbstractFactoryRequests[get_class($abstractFactory)])
&& $this->pendingAbstractFactoryRequests[get_class($abstractFactory)] == $rName
) {
return false;
}
if ($abstractFactory->canCreateServiceWithName($this, $cName, $rName)) {
return true;
}
}
return false;
}
/**
* @param string $alias
* @param string $nameOrAlias
* @return ServiceManager
* @throws Exception\ServiceNotFoundException
* @throws Exception\InvalidServiceNameException
*/
public function setAlias($alias, $nameOrAlias)
{
if (!is_string($alias) || !is_string($nameOrAlias)) {
throw new Exception\InvalidServiceNameException('Service or alias names must be strings.');
}
$cAlias = $this->canonicalizeName($alias);
$nameOrAlias = $this->canonicalizeName($nameOrAlias);
if ($alias == '' || $nameOrAlias == '') {
throw new Exception\InvalidServiceNameException('Invalid service name alias');
}
if ($this->allowOverride === false && $this->has(array($cAlias, $alias), false)) {
throw new Exception\InvalidServiceNameException(sprintf(
'An alias by the name "%s" or "%s" already exists',
$cAlias,
$alias
));
}
$this->aliases[$cAlias] = $nameOrAlias;
return $this;
}
/**
* Determine if we have an alias
*
* @param string $alias
* @return bool
*/
public function hasAlias($alias)
{
$alias = $this->canonicalizeName($alias);
return (isset($this->aliases[$alias]));
}
/**
* Create scoped service manager
*
* @param string $peering
* @return ServiceManager
*/
public function createScopedServiceManager($peering = self::SCOPE_PARENT)
{
$scopedServiceManager = new ServiceManager();
if ($peering == self::SCOPE_PARENT) {
$scopedServiceManager->peeringServiceManagers[] = $this;
}
if ($peering == self::SCOPE_CHILD) {
$this->peeringServiceManagers[] = $scopedServiceManager;
}
return $scopedServiceManager;
}
/**
* Add a peering relationship
*
* @param ServiceManager $manager
* @param string $peering
* @return ServiceManager
*/
public function addPeeringServiceManager(ServiceManager $manager, $peering = self::SCOPE_PARENT)
{
if ($peering == self::SCOPE_PARENT) {
$this->peeringServiceManagers[] = $manager;
}
if ($peering == self::SCOPE_CHILD) {
$manager->peeringServiceManagers[] = $this;
}
return $this;
}
/**
* Canonicalize name
*
* @param string $name
* @return string
*/
protected function canonicalizeName($name)
{
if (isset($this->canonicalNames[$name])) {
return $this->canonicalNames[$name];
}
// this is just for performance instead of using str_replace
return $this->canonicalNames[$name] = strtolower(strtr($name, $this->canonicalNamesReplacements));
}
/**
* Create service via callback
*
* @param callable $callable
* @param string $cName
* @param string $rName
* @throws Exception\ServiceNotCreatedException
* @throws Exception\ServiceNotFoundException
* @throws Exception\CircularDependencyFoundException
* @return object
*/
protected function createServiceViaCallback($callable, $cName, $rName)
{
static $circularDependencyResolver = array();
$depKey = spl_object_hash($this) . '-' . $cName;
if (isset($circularDependencyResolver[$depKey])) {
$circularDependencyResolver = array();
throw new Exception\CircularDependencyFoundException('Circular dependency for LazyServiceLoader was found for instance ' . $rName);
}
try {
$circularDependencyResolver[$depKey] = true;
$instance = call_user_func($callable, $this, $cName, $rName);
unset($circularDependencyResolver[$depKey]);
} catch (Exception\ServiceNotFoundException $e) {
unset($circularDependencyResolver[$depKey]);
throw $e;
} catch (\Exception $e) {
unset($circularDependencyResolver[$depKey]);
throw new Exception\ServiceNotCreatedException(
sprintf('An exception was raised while creating "%s"; no instance returned', $rName),
$e->getCode(),
$e
);
}
if ($instance === null) {
throw new Exception\ServiceNotCreatedException('The factory was called but did not return an instance.');
}
return $instance;
}
/**
* Retrieve a keyed list of all registered services. Handy for debugging!
*
* @return array
*/
public function getRegisteredServices()
{
return array(
'invokableClasses' => array_keys($this->invokableClasses),
'factories' => array_keys($this->factories),
'aliases' => array_keys($this->aliases),
'instances' => array_keys($this->instances),
);
}
/**
* Retrieve a keyed list of all canonical names. Handy for debugging!
*
* @return array
*/
public function getCanonicalNames()
{
return $this->canonicalNames;
}
/**
* Allows to override the canonical names lookup map with predefined
* values.
*
* @param array $canonicalNames
* @return ServiceManager
*/
public function setCanonicalNames($canonicalNames)
{
$this->canonicalNames = $canonicalNames;
return $this;
}
/**
* Attempt to retrieve an instance via a peering manager
*
* @param string $name
* @return mixed
*/
protected function retrieveFromPeeringManager($name)
{
foreach ($this->peeringServiceManagers as $peeringServiceManager) {
if ($peeringServiceManager->has($name)) {
return $peeringServiceManager->get($name);
}
}
$name = $this->canonicalizeName($name);
if ($this->hasAlias($name)) {
do {
$name = $this->aliases[$name];
} while ($this->hasAlias($name));
}
foreach ($this->peeringServiceManagers as $peeringServiceManager) {
if ($peeringServiceManager->has($name)) {
return $peeringServiceManager->get($name);
}
}
return null;
}
/**
* Attempt to create an instance via an invokable class
*
* @param string $canonicalName
* @param string $requestedName
* @return null|\stdClass
* @throws Exception\ServiceNotFoundException If resolved class does not exist
*/
protected function createFromInvokable($canonicalName, $requestedName)
{
$invokable = $this->invokableClasses[$canonicalName];
if (!class_exists($invokable)) {
throw new Exception\ServiceNotFoundException(sprintf(
'%s: failed retrieving "%s%s" via invokable class "%s"; class does not exist',
__METHOD__,
$canonicalName,
($requestedName ? '(alias: ' . $requestedName . ')' : ''),
$invokable
));
}
$instance = new $invokable;
return $instance;
}
/**
* Attempt to create an instance via a factory
*
* @param string $canonicalName
* @param string $requestedName
* @return mixed
* @throws Exception\ServiceNotCreatedException If factory is not callable
*/
protected function createFromFactory($canonicalName, $requestedName)
{
$factory = $this->factories[$canonicalName];
if (is_string($factory) && class_exists($factory, true)) {
$factory = new $factory;
$this->factories[$canonicalName] = $factory;
}
if ($factory instanceof FactoryInterface) {
$instance = $this->createServiceViaCallback(array($factory, 'createService'), $canonicalName, $requestedName);
} elseif (is_callable($factory)) {
$instance = $this->createServiceViaCallback($factory, $canonicalName, $requestedName);
} else {
throw new Exception\ServiceNotCreatedException(sprintf(
'While attempting to create %s%s an invalid factory was registered for this instance type.',
$canonicalName,
($requestedName ? '(alias: ' . $requestedName . ')' : '')
));
}
return $instance;
}
/**
* Attempt to create an instance via an abstract factory
*
* @param string $canonicalName
* @param string $requestedName
* @return object|null
* @throws Exception\ServiceNotCreatedException If abstract factory is not callable
*/
protected function createFromAbstractFactory($canonicalName, $requestedName)
{
foreach ($this->abstractFactories as $index => $abstractFactory) {
// support factories as strings
if (is_string($abstractFactory) && class_exists($abstractFactory, true)) {
$this->abstractFactories[$index] = $abstractFactory = new $abstractFactory;
} elseif (!$abstractFactory instanceof AbstractFactoryInterface) {
throw new Exception\ServiceNotCreatedException(sprintf(
'While attempting to create %s%s an abstract factory could not produce a valid instance.',
$canonicalName,
($requestedName ? '(alias: ' . $requestedName . ')' : '')
));
}
try {
if ($abstractFactory->canCreateServiceWithName($this, $canonicalName, $requestedName)) {
$this->pendingAbstractFactoryRequests[get_class($abstractFactory)] = $requestedName;
$instance = $this->createServiceViaCallback(
array($abstractFactory, 'createServiceWithName'),
$canonicalName,
$requestedName
);
unset($this->pendingAbstractFactoryRequests[get_class($abstractFactory)]);
} else {
$instance = false;
}
} catch (\Exception $e) {
unset($this->pendingAbstractFactoryRequests[get_class($abstractFactory)]);
throw new Exception\ServiceNotCreatedException(
sprintf(
'An abstract factory could not create an instance of %s%s.',
$canonicalName,
($requestedName ? '(alias: ' . $requestedName . ')' : '')
),
$e->getCode(),
$e
);
}
if (is_object($instance)) {
break;
}
}
return $instance;
}
/**
* Checks if the object has this class as one of its parents
*
* @see https://bugs.php.net/bug.php?id=53727
* @see https://github.com/zendframework/zf2/pull/1807
*
* @param string $className
* @param string $type
* @return bool
*/
protected static function isSubclassOf($className, $type)
{
if (is_subclass_of($className, $type)) {
return true;
}
if (version_compare(PHP_VERSION, '5.3.7', '>=')) {
return false;
}
if (!interface_exists($type)) {
return false;
}
$r = new ReflectionClass($className);
return $r->implementsInterface($type);
}
/**
* Unregister a service
*
* Called when $allowOverride is true and we detect that a service being
* added to the instance already exists. This will remove the duplicate
* entry, and also any shared flags previously registered.
*
* @param string $canonical
* @return void
*/
protected function unregisterService($canonical)
{
$types = array('invokableClasses', 'factories', 'aliases');
foreach ($types as $type) {
if (isset($this->{$type}[$canonical])) {
unset($this->{$type}[$canonical]);
break;
}
}
if (isset($this->instances[$canonical])) {
unset($this->instances[$canonical]);
}
if (isset($this->shared[$canonical])) {
unset($this->shared[$canonical]);
}
}
}
Jump to Line
Something went wrong with that request. Please try again.