<?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)',
                
get_called_class() . '::' __FUNCTION__
            
));
        }
        
$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',
                    
$name
                
));
            }
            
$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',
                    
$name
                
));
            }
            
$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($factorytrue)) {
                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($cNamefalse)) {
            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.',
                    
get_called_class() . '::' __FUNCTION__,
                    
$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',
                
get_called_class() . '::' __FUNCTION__,
                
$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',
                
get_called_class() . '::' __FUNCTION__,
                
$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($abstractFactorytrue)) {
                
$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',
                
get_called_class() . '::' __FUNCTION__,
                
$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($factorytrue)) {
            
$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($abstractFactorytrue)) {
                
$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]);
        }
    }
}