From 34e5cc769888c562ed0b83b0e4e4b9a1f80f2d98 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sun, 26 Feb 2017 18:31:03 +0100 Subject: [PATCH] [DI] Simplify AutowirePass and other master-only cleanups --- .../Console/Descriptor/JsonDescriptor.php | 8 +- .../Console/Descriptor/TextDescriptor.php | 4 +- .../Console/Descriptor/XmlDescriptor.php | 4 +- .../Compiler/AnalyzeServiceReferencesPass.php | 2 +- .../Compiler/AutowirePass.php | 130 ++++++------------ .../Compiler/ResolveInvalidReferencesPass.php | 6 +- .../ResolveReferencesToAliasesPass.php | 2 +- .../DependencyInjection/ContainerBuilder.php | 3 + .../DependencyInjection/Dumper/PhpDumper.php | 5 + .../LazyProxy/InheritanceProxyHelper.php | 42 +++--- .../Loader/YamlFileLoader.php | 1 + .../Tests/Compiler/AutowirePassTest.php | 1 - .../Tests/ContainerBuilderTest.php | 2 +- 13 files changed, 89 insertions(+), 121 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php index aa0b8af9b3f7d..c9a42d1b6e905 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php @@ -399,14 +399,14 @@ private function describeValue($value, $omitTags, $showArguments) ); } - if ($value instanceof Definition) { - return $this->getContainerDefinitionData($value, $omitTags, $showArguments); - } - if ($value instanceof ArgumentInterface) { return $this->describeValue($value->getValues(), $omitTags, $showArguments); } + if ($value instanceof Definition) { + return $this->getContainerDefinitionData($value, $omitTags, $showArguments); + } + return $value; } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php index 64afc5762934d..fd1fd533a50b7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -330,8 +330,6 @@ protected function describeContainerDefinition(Definition $definition, array $op foreach ($arguments as $argument) { if ($argument instanceof Reference) { $argumentsInformation[] = sprintf('Service(%s)', (string) $argument); - } elseif ($argument instanceof Definition) { - $argumentsInformation[] = 'Inlined Service'; } elseif ($argument instanceof IteratorArgument) { $argumentsInformation[] = sprintf('Iterator (%d element(s))', count($argument->getValues())); } elseif ($argument instanceof ServiceLocatorArgument) { @@ -339,6 +337,8 @@ protected function describeContainerDefinition(Definition $definition, array $op } elseif ($argument instanceof ClosureProxyArgument) { list($reference, $method) = $argument->getValues(); $argumentsInformation[] = sprintf('ClosureProxy(Service(%s)::%s())', $reference, $method); + } elseif ($argument instanceof Definition) { + $argumentsInformation[] = 'Inlined Service'; } else { $argumentsInformation[] = is_array($argument) ? sprintf('Array (%d element(s))', count($argument)) : $argument; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php index e15f88cf83c32..7e0547d4320df 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php @@ -440,8 +440,6 @@ private function getArgumentNodes(array $arguments, \DOMDocument $dom) if ($argument instanceof Reference) { $argumentXML->setAttribute('type', 'service'); $argumentXML->setAttribute('id', (string) $argument); - } elseif ($argument instanceof Definition) { - $argumentXML->appendChild($dom->importNode($this->getContainerDefinitionDocument($argument, null, false, true)->childNodes->item(0), true)); } elseif ($argument instanceof IteratorArgument) { $argumentXML->setAttribute('type', 'iterator'); @@ -459,6 +457,8 @@ private function getArgumentNodes(array $arguments, \DOMDocument $dom) $argumentXML->setAttribute('type', 'closure-proxy'); $argumentXML->setAttribute('id', (string) $reference); $argumentXML->setAttribute('method', $method); + } elseif ($argument instanceof Definition) { + $argumentXML->appendChild($dom->importNode($this->getContainerDefinitionDocument($argument, null, false, true)->childNodes->item(0), true)); } elseif (is_array($argument)) { $argumentXML->setAttribute('type', 'collection'); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php index b949d80874b57..af2160ff989cf 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php @@ -74,7 +74,7 @@ protected function processValue($value, $isRoot = false) if ($value instanceof ArgumentInterface) { $this->lazy = true; - parent::processValue($value); + parent::processValue($value->getValues()); $this->lazy = $lazy; return $value; diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index ec378d13183d9..a0d7d6213c3e5 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -15,6 +15,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\LazyProxy\InheritanceProxyHelper; use Symfony\Component\DependencyInjection\Reference; /** @@ -252,13 +253,7 @@ private function autowireMethod(\ReflectionMethod $reflectionMethod, array $argu continue; } - if (method_exists($parameter, 'getType')) { - if ($typeName = $parameter->getType()) { - $typeName = $typeName->isBuiltin() ? null : ($typeName instanceof \ReflectionNamedType ? $typeName->getName() : $typeName->__toString()); - } - } elseif (preg_match('/^(?:[^ ]++ ){4}([a-zA-Z_\x7F-\xFF][^ ]++)/', $parameter, $typeName)) { - $typeName = 'callable' === $typeName[1] || 'array' === $typeName[1] ? null : $typeName[1]; - } + $typeName = InheritanceProxyHelper::getTypeHint($reflectionMethod, $parameter, true); if (!$typeName) { // no default value? Then fail @@ -278,52 +273,23 @@ private function autowireMethod(\ReflectionMethod $reflectionMethod, array $argu continue; } - if ($this->container->has($typeName) && !$this->container->findDefinition($typeName)->isAbstract()) { - $arguments[$index] = new Reference($typeName); - $didAutowire = true; - - continue; - } - - if (null === $this->types) { - $this->populateAvailableTypes(); - } - - if (isset($this->types[$typeName])) { - $value = new Reference($this->types[$typeName]); + if ($value = $this->getAutowiredReference($typeName)) { $didAutowire = true; $this->usedTypes[$typeName] = $this->currentId; - } elseif ($typeHint = $this->container->getReflectionClass($typeName, true)) { - try { - $value = $this->createAutowiredDefinition($typeHint); - $didAutowire = true; - $this->usedTypes[$typeName] = $this->currentId; - } catch (RuntimeException $e) { - if ($parameter->allowsNull()) { - $value = null; - } elseif ($parameter->isDefaultValueAvailable()) { - $value = $parameter->getDefaultValue(); - } else { - // The exception code is set to 1 if the exception must be thrown even if it's an optional setter - if (1 === $e->getCode() || self::MODE_REQUIRED === $mode) { - throw $e; - } - - return array(); - } - } - } else { - // Typehint against a non-existing class - - if (!$parameter->isDefaultValueAvailable()) { - if (self::MODE_REQUIRED === $mode) { - throw new RuntimeException(sprintf('Cannot autowire argument $%s of method %s::%s() for service "%s": Class %s does not exist.', $parameter->name, $reflectionMethod->class, $reflectionMethod->name, $this->currentId, $typeName)); - } - - return array(); + } elseif ($parameter->isDefaultValueAvailable()) { + $value = $parameter->getDefaultValue(); + } elseif ($parameter->allowsNull()) { + $value = null; + } elseif (self::MODE_REQUIRED === $mode) { + if ($classOrInterface = class_exists($typeName, false) ? 'class' : (interface_exists($typeName, false) ? 'interface' : null)) { + $message = sprintf('Unable to autowire argument of type "%s" for the service "%s". No services were found matching this %s and it cannot be auto-registered.', $typeName, $this->currentId, $classOrInterface); + } else { + $message = sprintf('Cannot autowire argument $%s of method %s::%s() for service "%s": Class %s does not exist.', $parameter->name, $reflectionMethod->class, $reflectionMethod->name, $this->currentId, $typeName); } - $value = $parameter->getDefaultValue(); + throw new RuntimeException($message); + } else { + return array(); } $arguments[$index] = $value; @@ -356,42 +322,39 @@ private function autowireOverridenGetters(array $overridenGetters, array $autowi || 0 !== $reflectionMethod->getNumberOfParameters() || $reflectionMethod->isFinal() || $reflectionMethod->returnsReference() - || !$returnType = $reflectionMethod->getReturnType() + || !($typeName = InheritanceProxyHelper::getTypeHint($reflectionMethod, null, true)) + || !($typeRef = $this->getAutowiredReference($typeName)) ) { continue; } - $typeName = $returnType instanceof \ReflectionNamedType ? $returnType->getName() : $returnType->__toString(); - if ($this->container->has($typeName) && !$this->container->findDefinition($typeName)->isAbstract()) { - $overridenGetters[$lcMethod] = new Reference($typeName); - continue; - } + $overridenGetters[$lcMethod] = $typeRef; + $this->usedTypes[$typeName] = $this->currentId; + } - if (null === $this->types) { - $this->populateAvailableTypes(); - } + return $overridenGetters; + } - if (isset($this->types[$typeName])) { - $value = new Reference($this->types[$typeName]); - } elseif ($returnType = $this->container->getReflectionClass($typeName, true)) { - try { - $value = $this->createAutowiredDefinition($returnType); - } catch (RuntimeException $e) { - if (1 === $e->getCode()) { - throw $e; - } + /** + * @return Reference|null A reference to the service matching the given type, if any + */ + private function getAutowiredReference($typeName, $autoRegister = true) + { + if ($this->container->has($typeName) && !$this->container->findDefinition($typeName)->isAbstract()) { + return new Reference($typeName); + } - continue; - } - } else { - continue; - } + if (null === $this->types) { + $this->populateAvailableTypes(); + } - $overridenGetters[$lcMethod] = $value; - $this->usedTypes[$typeName] = $this->currentId; + if (isset($this->types[$typeName])) { + return new Reference($this->types[$typeName]); } - return $overridenGetters; + if ($autoRegister && $class = $this->container->getReflectionClass($typeName, true)) { + return $this->createAutowiredDefinition($class); + } } /** @@ -477,7 +440,7 @@ private function set($type, $id) * * @param \ReflectionClass $typeHint * - * @return Reference A reference to the registered definition + * @return Reference|null A reference to the registered definition * * @throws RuntimeException */ @@ -487,12 +450,11 @@ private function createAutowiredDefinition(\ReflectionClass $typeHint) $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class'; $matchingServices = implode(', ', $this->ambiguousServiceTypes[$typeHint->name]); - throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s". Multiple services exist for this %s (%s).', $typeHint->name, $this->currentId, $classOrInterface, $matchingServices), 1); + throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s". Multiple services exist for this %s (%s).', $typeHint->name, $this->currentId, $classOrInterface, $matchingServices)); } if (!$typeHint->isInstantiable()) { - $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class'; - throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s". No services were found matching this %s and it cannot be auto-registered.', $typeHint->name, $this->currentId, $classOrInterface)); + return; } $currentId = $this->currentId; @@ -504,14 +466,8 @@ private function createAutowiredDefinition(\ReflectionClass $typeHint) $this->populateAvailableType($argumentId, $argumentDefinition); - try { - $this->processValue($argumentDefinition); - $this->currentId = $currentId; - } catch (RuntimeException $e) { - $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class'; - $message = sprintf('Unable to autowire argument of type "%s" for the service "%s". No services were found matching this %s and it cannot be auto-registered.', $typeHint->name, $this->currentId, $classOrInterface); - throw new RuntimeException($message, 0, $e); - } + $this->processValue($argumentDefinition); + $this->currentId = $currentId; return new Reference($argumentId); } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInvalidReferencesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInvalidReferencesPass.php index abab663a0a684..d705e3aaf3797 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInvalidReferencesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInvalidReferencesPass.php @@ -53,7 +53,9 @@ public function process(ContainerBuilder $container) */ private function processValue($value, $rootLevel = 0, $level = 0) { - if ($value instanceof Definition) { + if ($value instanceof ArgumentInterface) { + $value->setValues($this->processValue($value->getValues(), $rootLevel, 1 + $level)); + } elseif ($value instanceof Definition) { if ($value->isSynthetic() || $value->isAbstract()) { return $value; } @@ -87,8 +89,6 @@ private function processValue($value, $rootLevel = 0, $level = 0) if (false !== $i) { $value = array_values($value); } - } elseif ($value instanceof ArgumentInterface) { - $value->setValues($this->processValue($value->getValues(), $rootLevel, 1 + $level)); } elseif ($value instanceof Reference) { $id = (string) $value; diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveReferencesToAliasesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveReferencesToAliasesPass.php index 9fc4b7a92d325..19e6d2482ec8d 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveReferencesToAliasesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveReferencesToAliasesPass.php @@ -11,8 +11,8 @@ namespace Symfony\Component\DependencyInjection\Compiler; -use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ContainerBuilder; diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 084198df319ac..45b2a5a5d811f 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -1127,6 +1127,9 @@ public function resolveServices($value) $parameterBag = $this->getParameterBag(); $services = array(); foreach ($value->getValues() as $k => $v) { + if ($v->getInvalidBehavior() === ContainerInterface::IGNORE_ON_INVALID_REFERENCE && !$this->has((string) $v)) { + continue; + } $services[$k] = function () use ($v, $parameterBag) { return $this->resolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($v))); }; diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index f831695c5c9db..865b980e5f2b9 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection\Dumper; +use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; @@ -1289,6 +1290,8 @@ private function exportParameters(array $parameters, $path = '', $indent = 12) foreach ($parameters as $key => $value) { if (is_array($value)) { $value = $this->exportParameters($value, $path.'/'.$key, $indent + 4); + } elseif ($value instanceof ArgumentInterface) { + throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain special arguments. "%s" found in "%s".', get_class($value), $path.'/'.$key)); } elseif ($value instanceof Variable) { throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain variable references. Variable "%s" found in "%s".', $value, $path.'/'.$key)); } elseif ($value instanceof Definition) { @@ -1461,6 +1464,8 @@ private function getDefinitionsFromArguments(array $arguments) foreach ($arguments as $argument) { if (is_array($argument)) { $definitions = array_merge($definitions, $this->getDefinitionsFromArguments($argument)); + } elseif ($argument instanceof ArgumentInterface) { + $definitions = array_merge($definitions, $this->getDefinitionsFromArguments($argument->getValues())); } elseif ($argument instanceof Definition) { $definitions = array_merge( $definitions, diff --git a/src/Symfony/Component/DependencyInjection/LazyProxy/InheritanceProxyHelper.php b/src/Symfony/Component/DependencyInjection/LazyProxy/InheritanceProxyHelper.php index b37e0b820fffc..78a37f622f1f6 100644 --- a/src/Symfony/Component/DependencyInjection/LazyProxy/InheritanceProxyHelper.php +++ b/src/Symfony/Component/DependencyInjection/LazyProxy/InheritanceProxyHelper.php @@ -63,12 +63,7 @@ public static function getSignature(\ReflectionFunctionAbstract $r, &$call = nul if ($p->isPassedByReference()) { $k = '&'.$k; } - if (method_exists($p, 'getType')) { - $type = $p->getType(); - } elseif (preg_match('/^(?:[^ ]++ ){4}([a-zA-Z_\x7F-\xFF][^ ]++)/', $p, $type)) { - $type = $type[1]; - } - if ($type && $type = self::getTypeHint($type, $r)) { + if ($type = self::getTypeHint($r, $p)) { $k = $type.' '.$k; } if ($type && $p->allowsNull()) { @@ -91,46 +86,55 @@ public static function getSignature(\ReflectionFunctionAbstract $r, &$call = nul } $call = ($r->isClosure() ? '' : $r->name).'('.implode(', ', $call).')'; - if ($type = method_exists($r, 'getReturnType') ? $r->getReturnType() : null) { - $type = ': '.($type->allowsNull() ? '?' : '').self::getTypeHint($type, $r); + if ($type = self::getTypeHint($r)) { + $type = ': '.($r->getReturnType()->allowsNull() ? '?' : '').$type; } return ($r->returnsReference() ? '&' : '').($r->isClosure() ? '' : $r->name).'('.implode(', ', $signature).')'.$type; } /** - * @param $type \ReflectionType|string $type As returned by ReflectionParameter::getType() - or string on PHP 5 - * * @return string|null The FQCN or builtin name of the type hint, or null when the type hint references an invalid self|parent context */ - public static function getTypeHint($type, \ReflectionFunctionAbstract $r) + public static function getTypeHint(\ReflectionFunctionAbstract $r, \ReflectionParameter $p = null, $noBuiltin = false) { - if (is_string($type)) { - $name = $type; + if ($p instanceof \ReflectionParameter) { + if (method_exists($p, 'getType')) { + $type = $p->getType(); + } elseif (preg_match('/^(?:[^ ]++ ){4}([a-zA-Z_\x7F-\xFF][^ ]++)/', $p, $type)) { + $name = $type = $type[1]; - if ('callable' === $name || 'array' === $name) { - return $name; + if ('callable' === $name || 'array' === $name) { + return $noBuiltin ? null : $name; + } } } else { + $type = method_exists($r, 'getReturnType') ? $r->getReturnType() : null; + } + if (!$type) { + return; + } + if (!is_string($type)) { $name = $type instanceof \ReflectionNamedType ? $type->getName() : $type->__toString(); if ($type->isBuiltin()) { - return $name; + return $noBuiltin ? null : $name; } } $lcName = strtolower($name); + $prefix = $noBuiltin ? '' : '\\'; if ('self' !== $lcName && 'parent' !== $lcName) { - return '\\'.$name; + return $prefix.$name; } if (!$r instanceof \ReflectionMethod) { return; } if ('self' === $lcName) { - return '\\'.$r->getDeclaringClass()->name; + return $prefix.$r->getDeclaringClass()->name; } if ($parent = $r->getDeclaringClass()->getParentClass()) { - return '\\'.$parent->name; + return $prefix.$parent->name; } } diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index 6ad27499e7b23..15e78fcb3f0fd 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Loader; use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index 563796e4866ca..f7263ed4185de 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -638,7 +638,6 @@ public function testIgnoreServiceWithClassNotExisting() /** * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException * @expectedExceptionMessage Unable to autowire argument of type "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface" for the service "setter_injection_collision". Multiple services exist for this interface (c1, c2). - * @expectedExceptionCode 1 */ public function testSetterInjectionCollisionThrowsException() { diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index b85cf691f4545..8452f3a032852 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -454,7 +454,7 @@ public function testCreateServiceWithServiceLocatorArgument() $this->assertInstanceOf(ServiceLocator::class, $locator); $this->assertInstanceOf('stdClass', $locator->get('bar')); - $this->assertNull($locator->get('invalid')); + $this->assertFalse($locator->has('invalid')); $this->assertSame($locator->get('bar'), $locator('bar'), '->get() should be used when invoking ServiceLocator'); }