1<?php
2/**
3 * Zend Framework (http://framework.zend.com/)
4 *
5 * @link      http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license   http://framework.zend.com/license/new-bsd New BSD License
8 */
9
10namespace Zend\ServiceManager;
11
12class ServiceManager implements ServiceLocatorInterface
13{
14    /**@#+
15     * Constants
16     */
17    const SCOPE_PARENT = 'parent';
18    const SCOPE_CHILD = 'child';
19    /**@#-*/
20
21    /**
22     * Lookup for canonicalized names.
23     *
24     * @var array
25     */
26    protected $canonicalNames = array();
27
28    /**
29     * @var bool
30     */
31    protected $allowOverride = false;
32
33    /**
34     * @var array
35     */
36    protected $invokableClasses = array();
37
38    /**
39     * @var string|callable|\Closure|FactoryInterface[]
40     */
41    protected $factories = array();
42
43    /**
44     * @var AbstractFactoryInterface[]
45     */
46    protected $abstractFactories = array();
47
48    /**
49     * @var array[]
50     */
51    protected $delegators = array();
52
53    /**
54     * @var array
55     */
56    protected $pendingAbstractFactoryRequests = array();
57
58    /**
59     * @var integer
60     */
61    protected $nestedContextCounter = -1;
62
63    /**
64     * @var array
65     */
66    protected $nestedContext = array();
67
68    /**
69     * @var array
70     */
71    protected $shared = array();
72
73    /**
74     * Registered services and cached values
75     *
76     * @var array
77     */
78    protected $instances = array();
79
80    /**
81     * @var array
82     */
83    protected $aliases = array();
84
85    /**
86     * @var array
87     */
88    protected $initializers = array();
89
90    /**
91     * @var ServiceManager[]
92     */
93    protected $peeringServiceManagers = array();
94
95    /**
96     * Whether or not to share by default
97     *
98     * @var bool
99     */
100    protected $shareByDefault = true;
101
102    /**
103     * @var bool
104     */
105    protected $retrieveFromPeeringManagerFirst = false;
106
107    /**
108     * @var bool Track whether not to throw exceptions during create()
109     */
110    protected $throwExceptionInCreate = true;
111
112    /**
113     * @var array map of characters to be replaced through strtr
114     */
115    protected $canonicalNamesReplacements = array('-' => '', '_' => '', ' ' => '', '\\' => '', '/' => '');
116
117    /**
118     * @var ServiceLocatorInterface
119     */
120    protected $serviceManagerCaller;
121
122    /**
123     * Constructor
124     *
125     * @param ConfigInterface $config
126     */
127    public function __construct(ConfigInterface $config = null)
128    {
129        if ($config) {
130            $config->configureServiceManager($this);
131        }
132    }
133
134    /**
135     * Set allow override
136     *
137     * @param $allowOverride
138     * @return ServiceManager
139     */
140    public function setAllowOverride($allowOverride)
141    {
142        $this->allowOverride = (bool) $allowOverride;
143        return $this;
144    }
145
146    /**
147     * Get allow override
148     *
149     * @return bool
150     */
151    public function getAllowOverride()
152    {
153        return $this->allowOverride;
154    }
155
156    /**
157     * Set flag indicating whether services are shared by default
158     *
159     * @param  bool $shareByDefault
160     * @return ServiceManager
161     * @throws Exception\RuntimeException if allowOverride is false
162     */
163    public function setShareByDefault($shareByDefault)
164    {
165        if ($this->allowOverride === false) {
166            throw new Exception\RuntimeException(sprintf(
167                '%s: cannot alter default shared service setting; container is marked immutable (allow_override is false)',
168                get_class($this) . '::' . __FUNCTION__
169            ));
170        }
171        $this->shareByDefault = (bool) $shareByDefault;
172        return $this;
173    }
174
175    /**
176     * Are services shared by default?
177     *
178     * @return bool
179     */
180    public function shareByDefault()
181    {
182        return $this->shareByDefault;
183    }
184
185    /**
186     * Set throw exceptions in create
187     *
188     * @param  bool $throwExceptionInCreate
189     * @return ServiceManager
190     */
191    public function setThrowExceptionInCreate($throwExceptionInCreate)
192    {
193        $this->throwExceptionInCreate = $throwExceptionInCreate;
194        return $this;
195    }
196
197    /**
198     * Get throw exceptions in create
199     *
200     * @return bool
201     */
202    public function getThrowExceptionInCreate()
203    {
204        return $this->throwExceptionInCreate;
205    }
206
207    /**
208     * Set flag indicating whether to pull from peering manager before attempting creation
209     *
210     * @param  bool $retrieveFromPeeringManagerFirst
211     * @return ServiceManager
212     */
213    public function setRetrieveFromPeeringManagerFirst($retrieveFromPeeringManagerFirst = true)
214    {
215        $this->retrieveFromPeeringManagerFirst = (bool) $retrieveFromPeeringManagerFirst;
216        return $this;
217    }
218
219    /**
220     * Should we retrieve from the peering manager prior to attempting to create a service?
221     *
222     * @return bool
223     */
224    public function retrieveFromPeeringManagerFirst()
225    {
226        return $this->retrieveFromPeeringManagerFirst;
227    }
228
229    /**
230     * Set invokable class
231     *
232     * @param  string  $name
233     * @param  string  $invokableClass
234     * @param  bool $shared
235     * @return ServiceManager
236     * @throws Exception\InvalidServiceNameException
237     */
238    public function setInvokableClass($name, $invokableClass, $shared = null)
239    {
240        $cName = $this->canonicalizeName($name);
241
242        if ($this->has(array($cName, $name), false)) {
243            if ($this->allowOverride === false) {
244                throw new Exception\InvalidServiceNameException(sprintf(
245                    'A service by the name or alias "%s" already exists and cannot be overridden; please use an alternate name',
246                    $name
247                ));
248            }
249            $this->unregisterService($cName);
250        }
251
252        if ($shared === null) {
253            $shared = $this->shareByDefault;
254        }
255
256        $this->invokableClasses[$cName] = $invokableClass;
257        $this->shared[$cName]           = (bool) $shared;
258
259        return $this;
260    }
261
262    /**
263     * Set factory
264     *
265     * @param  string                           $name
266     * @param  string|FactoryInterface|callable $factory
267     * @param  bool                             $shared
268     * @return ServiceManager
269     * @throws Exception\InvalidArgumentException
270     * @throws Exception\InvalidServiceNameException
271     */
272    public function setFactory($name, $factory, $shared = null)
273    {
274        $cName = $this->canonicalizeName($name);
275
276        if (!($factory instanceof FactoryInterface || is_string($factory) || is_callable($factory))) {
277            throw new Exception\InvalidArgumentException(
278                'Provided abstract factory must be the class name of an abstract factory or an instance of an AbstractFactoryInterface.'
279            );
280        }
281
282        if ($this->has(array($cName, $name), false)) {
283            if ($this->allowOverride === false) {
284                throw new Exception\InvalidServiceNameException(sprintf(
285                    'A service by the name or alias "%s" already exists and cannot be overridden, please use an alternate name',
286                    $name
287                ));
288            }
289            $this->unregisterService($cName);
290        }
291
292        if ($shared === null) {
293            $shared = $this->shareByDefault;
294        }
295
296        $this->factories[$cName] = $factory;
297        $this->shared[$cName]    = (bool) $shared;
298
299        return $this;
300    }
301
302    /**
303     * Add abstract factory
304     *
305     * @param  AbstractFactoryInterface|string $factory
306     * @param  bool                            $topOfStack
307     * @return ServiceManager
308     * @throws Exception\InvalidArgumentException if the abstract factory is invalid
309     */
310    public function addAbstractFactory($factory, $topOfStack = true)
311    {
312        if (!$factory instanceof AbstractFactoryInterface && is_string($factory)) {
313            $factory = new $factory();
314        }
315
316        if (!$factory instanceof AbstractFactoryInterface) {
317            throw new Exception\InvalidArgumentException(
318                'Provided abstract factory must be the class name of an abstract'
319                . ' factory or an instance of an AbstractFactoryInterface.'
320            );
321        }
322
323        if ($topOfStack) {
324            array_unshift($this->abstractFactories, $factory);
325        } else {
326            array_push($this->abstractFactories, $factory);
327        }
328        return $this;
329    }
330
331    /**
332     * Sets the given service name as to be handled by a delegator factory
333     *
334     * @param  string $serviceName          name of the service being the delegate
335     * @param  string $delegatorFactoryName name of the service being the delegator factory
336     *
337     * @return ServiceManager
338     */
339    public function addDelegator($serviceName, $delegatorFactoryName)
340    {
341        $cName = $this->canonicalizeName($serviceName);
342
343        if (!isset($this->delegators[$cName])) {
344            $this->delegators[$cName] = array();
345        }
346
347        $this->delegators[$cName][] = $delegatorFactoryName;
348
349        return $this;
350    }
351
352    /**
353     * Add initializer
354     *
355     * @param  callable|InitializerInterface $initializer
356     * @param  bool                          $topOfStack
357     * @return ServiceManager
358     * @throws Exception\InvalidArgumentException
359     */
360    public function addInitializer($initializer, $topOfStack = true)
361    {
362        if (!($initializer instanceof InitializerInterface || is_callable($initializer))) {
363            if (is_string($initializer)) {
364                $initializer = new $initializer;
365            }
366
367            if (!($initializer instanceof InitializerInterface || is_callable($initializer))) {
368                throw new Exception\InvalidArgumentException('$initializer should be callable.');
369            }
370        }
371
372        if ($topOfStack) {
373            array_unshift($this->initializers, $initializer);
374        } else {
375            array_push($this->initializers, $initializer);
376        }
377        return $this;
378    }
379
380    /**
381     * Register a service with the locator
382     *
383     * @param  string  $name
384     * @param  mixed   $service
385     * @return ServiceManager
386     * @throws Exception\InvalidServiceNameException
387     */
388    public function setService($name, $service)
389    {
390        $cName = $this->canonicalizeName($name);
391
392        if ($this->has($cName, false)) {
393            if ($this->allowOverride === false) {
394                throw new Exception\InvalidServiceNameException(sprintf(
395                    '%s: A service by the name "%s" or alias already exists and cannot be overridden, please use an alternate name.',
396                    get_class($this) . '::' . __FUNCTION__,
397                    $name
398                ));
399            }
400            $this->unregisterService($cName);
401        }
402
403        $this->instances[$cName] = $service;
404
405        return $this;
406    }
407
408    /**
409     * @param  string $name
410     * @param  bool   $isShared
411     * @return ServiceManager
412     * @throws Exception\ServiceNotFoundException
413     */
414    public function setShared($name, $isShared)
415    {
416        $cName = $this->canonicalizeName($name);
417
418        if (
419            !isset($this->invokableClasses[$cName])
420            && !isset($this->factories[$cName])
421            && !$this->canCreateFromAbstractFactory($cName, $name)
422        ) {
423            throw new Exception\ServiceNotFoundException(sprintf(
424                '%s: A service by the name "%s" was not found and could not be marked as shared',
425                get_class($this) . '::' . __FUNCTION__,
426                $name
427            ));
428        }
429
430        $this->shared[$cName] = (bool) $isShared;
431        return $this;
432    }
433
434    /**
435     * @param  string $name
436     * @return bool
437     * @throws Exception\ServiceNotFoundException
438     */
439    public function isShared($name)
440    {
441        $cName = $this->canonicalizeName($name);
442
443        if (!$this->has($name)) {
444            throw new Exception\ServiceNotFoundException(sprintf(
445                '%s: A service by the name "%s" was not found',
446                get_class($this) . '::' . __FUNCTION__,
447                $name
448            ));
449        }
450
451        if (!isset($this->shared[$cName])) {
452            return $this->shareByDefault();
453        }
454
455        return $this->shared[$cName];
456    }
457
458    /**
459     * Resolve the alias for the given canonical name
460     *
461     * @param  string $cName The canonical name to resolve
462     * @return string The resolved canonical name
463     */
464    protected function resolveAlias($cName)
465    {
466        $stack = array();
467
468        while ($this->hasAlias($cName)) {
469            if (isset($stack[$cName])) {
470                throw new Exception\CircularReferenceException(sprintf(
471                    'Circular alias reference: %s -> %s',
472                    implode(' -> ', $stack),
473                    $cName
474                ));
475            }
476
477            $stack[$cName] = $cName;
478            $cName = $this->aliases[$cName];
479        }
480
481        return $cName;
482    }
483
484    /**
485     * Retrieve a registered instance
486     *
487     * @param  string  $name
488     * @param  bool    $usePeeringServiceManagers
489     * @throws Exception\ServiceNotFoundException
490     * @return object|array
491     */
492    public function get($name, $usePeeringServiceManagers = true)
493    {
494        // inlined code from ServiceManager::canonicalizeName for performance
495        if (isset($this->canonicalNames[$name])) {
496            $cName = $this->canonicalNames[$name];
497        } else {
498            $cName = $this->canonicalizeName($name);
499        }
500
501        $isAlias = false;
502
503        if ($this->hasAlias($cName)) {
504            $isAlias = true;
505            $cName = $this->resolveAlias($cName);
506        }
507
508        $instance = null;
509
510        if ($usePeeringServiceManagers && $this->retrieveFromPeeringManagerFirst) {
511            $instance = $this->retrieveFromPeeringManager($name);
512
513            if (null !== $instance) {
514                return $instance;
515            }
516        }
517
518        if (isset($this->instances[$cName])) {
519            return $this->instances[$cName];
520        }
521
522        if (!$instance) {
523            $this->checkNestedContextStart($cName);
524            if (
525                isset($this->invokableClasses[$cName])
526                || isset($this->factories[$cName])
527                || isset($this->aliases[$cName])
528                || $this->canCreateFromAbstractFactory($cName, $name)
529            ) {
530                $instance = $this->create(array($cName, $name));
531            } elseif ($isAlias && $this->canCreateFromAbstractFactory($name, $cName)) {
532                /*
533                 * case of an alias leading to an abstract factory :
534                 * 'my-alias' => 'my-abstract-defined-service'
535                 *     $name = 'my-alias'
536                 *     $cName = 'my-abstract-defined-service'
537                 */
538                $instance = $this->create(array($name, $cName));
539            } elseif ($usePeeringServiceManagers && !$this->retrieveFromPeeringManagerFirst) {
540                $instance = $this->retrieveFromPeeringManager($name);
541            }
542            $this->checkNestedContextStop();
543        }
544
545        // Still no instance? raise an exception
546        if ($instance === null) {
547            $this->checkNestedContextStop(true);
548            if ($isAlias) {
549                throw new Exception\ServiceNotFoundException(sprintf(
550                    'An alias "%s" was requested but no service could be found.',
551                    $name
552                ));
553            }
554
555            throw new Exception\ServiceNotFoundException(sprintf(
556                '%s was unable to fetch or create an instance for %s',
557                get_class($this) . '::' . __FUNCTION__,
558                $name
559            ));
560        }
561
562        if (
563            ($this->shareByDefault && !isset($this->shared[$cName]))
564            || (isset($this->shared[$cName]) && $this->shared[$cName] === true)
565        ) {
566            $this->instances[$cName] = $instance;
567        }
568
569        return $instance;
570    }
571
572    /**
573     * Create an instance of the requested service
574     *
575     * @param  string|array $name
576     *
577     * @return bool|object
578     */
579    public function create($name)
580    {
581        if (is_array($name)) {
582            list($cName, $rName) = $name;
583        } else {
584            $rName = $name;
585
586            // inlined code from ServiceManager::canonicalizeName for performance
587            if (isset($this->canonicalNames[$rName])) {
588                $cName = $this->canonicalNames[$name];
589            } else {
590                $cName = $this->canonicalizeName($name);
591            }
592        }
593
594        if (isset($this->delegators[$cName])) {
595            return $this->createDelegatorFromFactory($cName, $rName);
596        }
597
598        return $this->doCreate($rName, $cName);
599    }
600
601    /**
602     * Creates a callback that uses a delegator to create a service
603     *
604     * @param DelegatorFactoryInterface|callable $delegatorFactory the delegator factory
605     * @param string                             $rName            requested service name
606     * @param string                             $cName            canonical service name
607     * @param callable                           $creationCallback callback for instantiating the real service
608     *
609     * @return callable
610     */
611    private function createDelegatorCallback($delegatorFactory, $rName, $cName, $creationCallback)
612    {
613        $serviceManager  = $this;
614
615        return function () use ($serviceManager, $delegatorFactory, $rName, $cName, $creationCallback) {
616            return $delegatorFactory instanceof DelegatorFactoryInterface
617                ? $delegatorFactory->createDelegatorWithName($serviceManager, $cName, $rName, $creationCallback)
618                : $delegatorFactory($serviceManager, $cName, $rName, $creationCallback);
619        };
620    }
621
622    /**
623     * Actually creates the service
624     *
625     * @param string $rName real service name
626     * @param string $cName canonicalized service name
627     *
628     * @return bool|mixed|null|object
629     * @throws Exception\ServiceNotFoundException
630     *
631     * @internal this method is internal because of PHP 5.3 compatibility - do not explicitly use it
632     */
633    public function doCreate($rName, $cName)
634    {
635        $instance = null;
636
637        if (isset($this->factories[$cName])) {
638            $instance = $this->createFromFactory($cName, $rName);
639        }
640
641        if ($instance === null && isset($this->invokableClasses[$cName])) {
642            $instance = $this->createFromInvokable($cName, $rName);
643        }
644        $this->checkNestedContextStart($cName);
645        if ($instance === null && $this->canCreateFromAbstractFactory($cName, $rName)) {
646            $instance = $this->createFromAbstractFactory($cName, $rName);
647        }
648        $this->checkNestedContextStop();
649
650        if ($instance === null && $this->throwExceptionInCreate) {
651            $this->checkNestedContextStop(true);
652            throw new Exception\ServiceNotFoundException(sprintf(
653                'No valid instance was found for %s%s',
654                $cName,
655                ($rName ? '(alias: ' . $rName . ')' : '')
656            ));
657        }
658
659        // Do not call initializers if we do not have an instance
660        if ($instance === null) {
661            return $instance;
662        }
663
664        foreach ($this->initializers as $initializer) {
665            if ($initializer instanceof InitializerInterface) {
666                $initializer->initialize($instance, $this);
667            } else {
668                call_user_func($initializer, $instance, $this);
669            }
670        }
671
672        return $instance;
673    }
674
675    /**
676     * Determine if we can create an instance.
677     * Proxies to has()
678     *
679     * @param  string|array $name
680     * @param  bool         $checkAbstractFactories
681     * @return bool
682     * @deprecated this method is being deprecated as of zendframework 2.3, and may be removed in future major versions
683     */
684    public function canCreate($name, $checkAbstractFactories = true)
685    {
686        trigger_error(sprintf('%s is deprecated; please use %s::has', __METHOD__, __CLASS__), E_USER_DEPRECATED);
687        return $this->has($name, $checkAbstractFactories, false);
688    }
689
690    /**
691     * Determine if an instance exists.
692     *
693     * @param  string|array  $name  An array argument accepts exactly two values.
694     *                              Example: array('canonicalName', 'requestName')
695     * @param  bool          $checkAbstractFactories
696     * @param  bool          $usePeeringServiceManagers
697     * @return bool
698     */
699    public function has($name, $checkAbstractFactories = true, $usePeeringServiceManagers = true)
700    {
701        if (is_string($name)) {
702            $rName = $name;
703
704            // inlined code from ServiceManager::canonicalizeName for performance
705            if (isset($this->canonicalNames[$rName])) {
706                $cName = $this->canonicalNames[$rName];
707            } else {
708                $cName = $this->canonicalizeName($name);
709            }
710        } elseif (is_array($name) && count($name) >= 2) {
711            list($cName, $rName) = $name;
712        } else {
713            return false;
714        }
715
716        if (isset($this->invokableClasses[$cName])
717            || isset($this->factories[$cName])
718            || isset($this->aliases[$cName])
719            || isset($this->instances[$cName])
720            || ($checkAbstractFactories && $this->canCreateFromAbstractFactory($cName, $rName))
721        ) {
722            return true;
723        }
724
725        if ($usePeeringServiceManagers) {
726            $caller = $this->serviceManagerCaller;
727            foreach ($this->peeringServiceManagers as $peeringServiceManager) {
728                // ignore peering service manager if they are the same instance
729                if ($caller === $peeringServiceManager) {
730                    continue;
731                }
732
733                $peeringServiceManager->serviceManagerCaller = $this;
734
735                if ($peeringServiceManager->has($name)) {
736                    $peeringServiceManager->serviceManagerCaller = null;
737                    return true;
738                }
739
740                $peeringServiceManager->serviceManagerCaller = null;
741            }
742        }
743
744        return false;
745    }
746
747    /**
748     * Determine if we can create an instance from an abstract factory.
749     *
750     * @param  string $cName
751     * @param  string $rName
752     * @return bool
753     */
754    public function canCreateFromAbstractFactory($cName, $rName)
755    {
756        if (array_key_exists($cName, $this->nestedContext)) {
757            $context = $this->nestedContext[$cName];
758            if ($context === false) {
759                return false;
760            } elseif (is_object($context)) {
761                return !isset($this->pendingAbstractFactoryRequests[get_class($context).$cName]);
762            }
763        }
764        $this->checkNestedContextStart($cName);
765        // check abstract factories
766        $result = false;
767        $this->nestedContext[$cName] = false;
768        foreach ($this->abstractFactories as $abstractFactory) {
769            $pendingKey = get_class($abstractFactory).$cName;
770            if (isset($this->pendingAbstractFactoryRequests[$pendingKey])) {
771                $result = false;
772                break;
773            }
774
775            if ($abstractFactory->canCreateServiceWithName($this, $cName, $rName)) {
776                $this->nestedContext[$cName] = $abstractFactory;
777                $result = true;
778                break;
779            }
780        }
781        $this->checkNestedContextStop();
782        return $result;
783    }
784
785    /**
786     * Ensure the alias definition will not result in a circular reference
787     *
788     * @param  string $alias
789     * @param  string $nameOrAlias
790     * @throws Exception\CircularReferenceException
791     * @return self
792     */
793    protected function checkForCircularAliasReference($alias, $nameOrAlias)
794    {
795        $aliases = $this->aliases;
796        $aliases[$alias] = $nameOrAlias;
797        $stack = array();
798
799        while (isset($aliases[$alias])) {
800            if (isset($stack[$alias])) {
801                throw new Exception\CircularReferenceException(sprintf(
802                    'The alias definition "%s" : "%s" results in a circular reference: "%s" -> "%s"',
803                    $alias,
804                    $nameOrAlias,
805                    implode('" -> "', $stack),
806                    $alias
807                ));
808            }
809
810            $stack[$alias] = $alias;
811            $alias = $aliases[$alias];
812        }
813
814        return $this;
815    }
816
817    /**
818     * @param  string $alias
819     * @param  string $nameOrAlias
820     * @return ServiceManager
821     * @throws Exception\ServiceNotFoundException
822     * @throws Exception\InvalidServiceNameException
823     */
824    public function setAlias($alias, $nameOrAlias)
825    {
826        if (!is_string($alias) || !is_string($nameOrAlias)) {
827            throw new Exception\InvalidServiceNameException('Service or alias names must be strings.');
828        }
829
830        $cAlias = $this->canonicalizeName($alias);
831        $nameOrAlias = $this->canonicalizeName($nameOrAlias);
832
833        if ($alias == '' || $nameOrAlias == '') {
834            throw new Exception\InvalidServiceNameException('Invalid service name alias');
835        }
836
837        if ($this->allowOverride === false && $this->has(array($cAlias, $alias), false)) {
838            throw new Exception\InvalidServiceNameException(sprintf(
839                'An alias by the name "%s" or "%s" already exists',
840                $cAlias,
841                $alias
842            ));
843        }
844
845        if ($this->hasAlias($alias)) {
846            $this->checkForCircularAliasReference($cAlias, $nameOrAlias);
847        }
848
849        $this->aliases[$cAlias] = $nameOrAlias;
850        return $this;
851    }
852
853    /**
854     * Determine if we have an alias
855     *
856     * @param  string $alias
857     * @return bool
858     */
859    public function hasAlias($alias)
860    {
861        return isset($this->aliases[$this->canonicalizeName($alias)]);
862    }
863
864    /**
865     * Create scoped service manager
866     *
867     * @param  string $peering
868     * @return ServiceManager
869     */
870    public function createScopedServiceManager($peering = self::SCOPE_PARENT)
871    {
872        $scopedServiceManager = new ServiceManager();
873        if ($peering == self::SCOPE_PARENT) {
874            $scopedServiceManager->peeringServiceManagers[] = $this;
875        }
876        if ($peering == self::SCOPE_CHILD) {
877            $this->peeringServiceManagers[] = $scopedServiceManager;
878        }
879        return $scopedServiceManager;
880    }
881
882    /**
883     * Add a peering relationship
884     *
885     * @param  ServiceManager $manager
886     * @param  string         $peering
887     * @return ServiceManager
888     */
889    public function addPeeringServiceManager(ServiceManager $manager, $peering = self::SCOPE_PARENT)
890    {
891        if ($peering == self::SCOPE_PARENT) {
892            $this->peeringServiceManagers[] = $manager;
893        }
894        if ($peering == self::SCOPE_CHILD) {
895            $manager->peeringServiceManagers[] = $this;
896        }
897        return $this;
898    }
899
900    /**
901     * Canonicalize name
902     *
903     * @param  string $name
904     * @return string
905     */
906    protected function canonicalizeName($name)
907    {
908        if (isset($this->canonicalNames[$name])) {
909            return $this->canonicalNames[$name];
910        }
911
912        // this is just for performance instead of using str_replace
913        return $this->canonicalNames[$name] = strtolower(strtr($name, $this->canonicalNamesReplacements));
914    }
915
916    /**
917     * Create service via callback
918     *
919     * @param  callable $callable
920     * @param  string   $cName
921     * @param  string   $rName
922     * @throws Exception\ServiceNotCreatedException
923     * @throws Exception\ServiceNotFoundException
924     * @throws Exception\CircularDependencyFoundException
925     * @return object
926     */
927    protected function createServiceViaCallback($callable, $cName, $rName)
928    {
929        static $circularDependencyResolver = array();
930        $depKey = spl_object_hash($this) . '-' . $cName;
931
932        if (isset($circularDependencyResolver[$depKey])) {
933            $circularDependencyResolver = array();
934            throw new Exception\CircularDependencyFoundException('Circular dependency for LazyServiceLoader was found for instance ' . $rName);
935        }
936
937        try {
938            $circularDependencyResolver[$depKey] = true;
939            $instance = call_user_func($callable, $this, $cName, $rName);
940            unset($circularDependencyResolver[$depKey]);
941        } catch (Exception\ServiceNotFoundException $e) {
942            unset($circularDependencyResolver[$depKey]);
943            throw $e;
944        } catch (\Exception $e) {
945            unset($circularDependencyResolver[$depKey]);
946            throw new Exception\ServiceNotCreatedException(
947                sprintf('An exception was raised while creating "%s"; no instance returned', $rName),
948                $e->getCode(),
949                $e
950            );
951        }
952        if ($instance === null) {
953            throw new Exception\ServiceNotCreatedException('The factory was called but did not return an instance.');
954        }
955
956        return $instance;
957    }
958
959    /**
960     * Retrieve a keyed list of all registered services. Handy for debugging!
961     *
962     * @return array
963     */
964    public function getRegisteredServices()
965    {
966        return array(
967            'invokableClasses' => array_keys($this->invokableClasses),
968            'factories' => array_keys($this->factories),
969            'aliases' => array_keys($this->aliases),
970            'instances' => array_keys($this->instances),
971        );
972    }
973
974    /**
975     * Retrieve a keyed list of all canonical names. Handy for debugging!
976     *
977     * @return array
978     */
979    public function getCanonicalNames()
980    {
981        return $this->canonicalNames;
982    }
983
984    /**
985     * Allows to override the canonical names lookup map with predefined
986     * values.
987     *
988     * @param array $canonicalNames
989     * @return ServiceManager
990     */
991    public function setCanonicalNames($canonicalNames)
992    {
993        $this->canonicalNames = $canonicalNames;
994
995        return $this;
996    }
997
998    /**
999     * Attempt to retrieve an instance via a peering manager
1000     *
1001     * @param  string $name
1002     * @return mixed
1003     */
1004    protected function retrieveFromPeeringManager($name)
1005    {
1006        if (null !== ($service = $this->loopPeeringServiceManagers($name))) {
1007            return $service;
1008        }
1009
1010        $name = $this->canonicalizeName($name);
1011
1012        if ($this->hasAlias($name)) {
1013            do {
1014                $name = $this->aliases[$name];
1015            } while ($this->hasAlias($name));
1016        }
1017
1018        if (null !== ($service = $this->loopPeeringServiceManagers($name))) {
1019            return $service;
1020        }
1021
1022        return;
1023    }
1024
1025    /**
1026     * Loop over peering service managers.
1027     *
1028     * @param string $name
1029     * @return mixed
1030     */
1031    protected function loopPeeringServiceManagers($name)
1032    {
1033        $caller = $this->serviceManagerCaller;
1034
1035        foreach ($this->peeringServiceManagers as $peeringServiceManager) {
1036            // ignore peering service manager if they are the same instance
1037            if ($caller === $peeringServiceManager) {
1038                continue;
1039            }
1040
1041            // pass this instance to peering service manager
1042            $peeringServiceManager->serviceManagerCaller = $this;
1043
1044            if ($peeringServiceManager->has($name)) {
1045                $this->shared[$name] = $peeringServiceManager->isShared($name);
1046                $instance = $peeringServiceManager->get($name);
1047                $peeringServiceManager->serviceManagerCaller = null;
1048                return $instance;
1049            }
1050
1051            $peeringServiceManager->serviceManagerCaller = null;
1052        }
1053
1054        return;
1055    }
1056
1057    /**
1058     * Attempt to create an instance via an invokable class
1059     *
1060     * @param  string $canonicalName
1061     * @param  string $requestedName
1062     * @return null|\stdClass
1063     * @throws Exception\ServiceNotFoundException If resolved class does not exist
1064     */
1065    protected function createFromInvokable($canonicalName, $requestedName)
1066    {
1067        $invokable = $this->invokableClasses[$canonicalName];
1068        if (!class_exists($invokable)) {
1069            throw new Exception\ServiceNotFoundException(sprintf(
1070                '%s: failed retrieving "%s%s" via invokable class "%s"; class does not exist',
1071                get_class($this) . '::' . __FUNCTION__,
1072                $canonicalName,
1073                ($requestedName ? '(alias: ' . $requestedName . ')' : ''),
1074                $invokable
1075            ));
1076        }
1077        $instance = new $invokable;
1078        return $instance;
1079    }
1080
1081    /**
1082     * Attempt to create an instance via a factory
1083     *
1084     * @param  string $canonicalName
1085     * @param  string $requestedName
1086     * @return mixed
1087     * @throws Exception\ServiceNotCreatedException If factory is not callable
1088     */
1089    protected function createFromFactory($canonicalName, $requestedName)
1090    {
1091        $factory = $this->factories[$canonicalName];
1092        if (is_string($factory) && class_exists($factory, true)) {
1093            $factory = new $factory;
1094            $this->factories[$canonicalName] = $factory;
1095        }
1096        if ($factory instanceof FactoryInterface) {
1097            $instance = $this->createServiceViaCallback(array($factory, 'createService'), $canonicalName, $requestedName);
1098        } elseif (is_callable($factory)) {
1099            $instance = $this->createServiceViaCallback($factory, $canonicalName, $requestedName);
1100        } else {
1101            throw new Exception\ServiceNotCreatedException(sprintf(
1102                'While attempting to create %s%s an invalid factory was registered for this instance type.',
1103                $canonicalName,
1104                ($requestedName ? '(alias: ' . $requestedName . ')' : '')
1105            ));
1106        }
1107        return $instance;
1108    }
1109
1110    /**
1111     * Attempt to create an instance via an abstract factory
1112     *
1113     * @param  string $canonicalName
1114     * @param  string $requestedName
1115     * @return object|null
1116     * @throws Exception\ServiceNotCreatedException If abstract factory is not callable
1117     */
1118    protected function createFromAbstractFactory($canonicalName, $requestedName)
1119    {
1120        if (isset($this->nestedContext[$canonicalName])) {
1121            $abstractFactory = $this->nestedContext[$canonicalName];
1122            $pendingKey = get_class($abstractFactory).$canonicalName;
1123            try {
1124                $this->pendingAbstractFactoryRequests[$pendingKey] = true;
1125                $instance = $this->createServiceViaCallback(
1126                    array($abstractFactory, 'createServiceWithName'),
1127                    $canonicalName,
1128                    $requestedName
1129                );
1130                unset($this->pendingAbstractFactoryRequests[$pendingKey]);
1131                return $instance;
1132            } catch (\Exception $e) {
1133                unset($this->pendingAbstractFactoryRequests[$pendingKey]);
1134                $this->checkNestedContextStop(true);
1135                throw new Exception\ServiceNotCreatedException(
1136                    sprintf(
1137                        'An abstract factory could not create an instance of %s%s.',
1138                        $canonicalName,
1139                        ($requestedName ? '(alias: ' . $requestedName . ')' : '')
1140                    ),
1141                    $e->getCode(),
1142                    $e
1143                );
1144            }
1145        }
1146        return;
1147    }
1148
1149    /**
1150     *
1151     * @param string $cName
1152     * @return self
1153     */
1154    protected function checkNestedContextStart($cName)
1155    {
1156        if ($this->nestedContextCounter === -1 || !isset($this->nestedContext[$cName])) {
1157            $this->nestedContext[$cName] = null;
1158        }
1159        $this->nestedContextCounter++;
1160        return $this;
1161    }
1162
1163    /**
1164     *
1165     * @param bool $force
1166     * @return self
1167     */
1168    protected function checkNestedContextStop($force = false)
1169    {
1170        if ($force) {
1171            $this->nestedContextCounter = -1;
1172            $this->nestedContext = array();
1173            return $this;
1174        }
1175
1176        $this->nestedContextCounter--;
1177        if ($this->nestedContextCounter === -1) {
1178            $this->nestedContext = array();
1179        }
1180        return $this;
1181    }
1182
1183    /**
1184     * @param $canonicalName
1185     * @param $requestedName
1186     * @return mixed
1187     * @throws Exception\ServiceNotCreatedException
1188     */
1189    protected function createDelegatorFromFactory($canonicalName, $requestedName)
1190    {
1191        $serviceManager     = $this;
1192        $delegatorsCount    = count($this->delegators[$canonicalName]);
1193        $creationCallback   = function () use ($serviceManager, $requestedName, $canonicalName) {
1194            return $serviceManager->doCreate($requestedName, $canonicalName);
1195        };
1196
1197        for ($i = 0; $i < $delegatorsCount; $i += 1) {
1198            $delegatorFactory = $this->delegators[$canonicalName][$i];
1199
1200            if (is_string($delegatorFactory)) {
1201                $delegatorFactory = !$this->has($delegatorFactory) && class_exists($delegatorFactory, true) ?
1202                    new $delegatorFactory
1203                    : $this->get($delegatorFactory);
1204                $this->delegators[$canonicalName][$i] = $delegatorFactory;
1205            }
1206
1207            if (!$delegatorFactory instanceof DelegatorFactoryInterface && !is_callable($delegatorFactory)) {
1208                throw new Exception\ServiceNotCreatedException(sprintf(
1209                    'While attempting to create %s%s an invalid factory was registered for this instance type.',
1210                    $canonicalName,
1211                    ($requestedName ? '(alias: ' . $requestedName . ')' : '')
1212                ));
1213            }
1214
1215            $creationCallback = $this->createDelegatorCallback(
1216                $delegatorFactory,
1217                $requestedName,
1218                $canonicalName,
1219                $creationCallback
1220            );
1221        }
1222
1223        return $creationCallback($serviceManager, $canonicalName, $requestedName, $creationCallback);
1224    }
1225
1226    /**
1227     * Checks if the object has this class as one of its parents
1228     *
1229     * @see https://bugs.php.net/bug.php?id=53727
1230     * @see https://github.com/zendframework/zf2/pull/1807
1231     *
1232     * @deprecated since zf 2.3 requires PHP >= 5.3.23
1233     *
1234     * @param string $className
1235     * @param string $type
1236     * @return bool
1237     *
1238     * @deprecated this method is being deprecated as of zendframework 2.2, and may be removed in future major versions
1239     */
1240    protected static function isSubclassOf($className, $type)
1241    {
1242        return is_subclass_of($className, $type);
1243    }
1244
1245    /**
1246     * Unregister a service
1247     *
1248     * Called when $allowOverride is true and we detect that a service being
1249     * added to the instance already exists. This will remove the duplicate
1250     * entry, and also any shared flags previously registered.
1251     *
1252     * @param  string $canonical
1253     * @return void
1254     */
1255    protected function unregisterService($canonical)
1256    {
1257        $types = array('invokableClasses', 'factories', 'aliases');
1258        foreach ($types as $type) {
1259            if (isset($this->{$type}[$canonical])) {
1260                unset($this->{$type}[$canonical]);
1261                break;
1262            }
1263        }
1264
1265        if (isset($this->instances[$canonical])) {
1266            unset($this->instances[$canonical]);
1267        }
1268
1269        if (isset($this->shared[$canonical])) {
1270            unset($this->shared[$canonical]);
1271        }
1272    }
1273}
1274