From aed118528b384c60549844b293968a2fcf22dceb Mon Sep 17 00:00:00 2001 From: Jeff Flitton Date: Wed, 24 Jul 2013 10:26:32 -0600 Subject: [PATCH 1/2] Added Container->inject function to inject into an existing instance (i.e. for unit testing a controller). --- src/DI/Container.php | 47 +++++++++++++++++++++++++++++++++++++ src/DI/Factory.php | 28 ++++++++++++++++++++++ src/DI/FactoryInterface.php | 2 +- 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/src/DI/Container.php b/src/DI/Container.php index b949492c3..7db670f6a 100644 --- a/src/DI/Container.php +++ b/src/DI/Container.php @@ -140,6 +140,28 @@ public function get($name, $useProxy = false) throw new NotFoundException("No entry or class found for '$name'"); } + /** + * Performs injection on an existing instance + * + * @param object $instance Object to perform injection upon + * @throws InvalidArgumentException + * @throws DependencyException + * @return object $instance + */ + public function inject($instance) + { + $className = get_class($instance); + $definition = $this->definitionManager->getDefinition($className); + + // It's a class + if ($definition instanceof ClassDefinition) { + $instance = $this->injectExistingInstance($definition, $instance); + return $instance; + } + else + return $instance; + } + /** * Define an object or a value in the container * @@ -314,6 +336,31 @@ private function getNewInstance(ClassDefinition $classDefinition) return $instance; } + /** + * @param ClassDefinition $classDefinition + * @param object $instance + * @return object $instance + */ + private function injectExistingInstance(ClassDefinition $classDefinition, $instance) + { + $classname = $classDefinition->getClassName(); + + if (isset($this->classesBeingInstantiated[$classname])) { + throw new DependencyException("Circular dependency detected while trying to instantiate class '$classname'"); + } + $this->classesBeingInstantiated[$classname] = true; + + try { + $instance = $this->factory->injectInstance($classDefinition, $instance); + } catch (Exception $exception) { + unset($this->classesBeingInstantiated[$classname]); + throw $exception; + } + + unset($this->classesBeingInstantiated[$classname]); + return $instance; + } + /** * Returns a proxy instance * diff --git a/src/DI/Factory.php b/src/DI/Factory.php index 36ac90fb5..40ca0a848 100644 --- a/src/DI/Factory.php +++ b/src/DI/Factory.php @@ -75,6 +75,34 @@ public function createInstance(ClassDefinition $classDefinition) return $instance; } + /** + * {@inheritdoc} + * @throws DependencyException + * @throws \DI\Definition\Exception\DefinitionException + */ + public function injectInstance(ClassDefinition $classDefinition, $instance) + { + try { + // Property injections + foreach ($classDefinition->getPropertyInjections() as $propertyInjection) { + $this->injectProperty($instance, $propertyInjection); + } + + // Method injections + foreach ($classDefinition->getMethodInjections() as $methodInjection) { + $this->injectMethod($instance, $methodInjection); + } + } catch (DependencyException $e) { + throw $e; + } catch (DefinitionException $e) { + throw $e; + } catch (NotFoundException $e) { + throw new DependencyException("Error while injecting dependencies into " . $classDefinition->getClassName() . ": " . $e->getMessage(), 0, $e); + } + + return $instance; + } + /** * Creates an instance and inject dependencies through the constructor * diff --git a/src/DI/FactoryInterface.php b/src/DI/FactoryInterface.php index 8621a0454..9e7bfc144 100644 --- a/src/DI/FactoryInterface.php +++ b/src/DI/FactoryInterface.php @@ -27,5 +27,5 @@ interface FactoryInterface * @return object The instance */ function createInstance(ClassDefinition $classDefinition); - + function injectInstance(ClassDefinition $classDefinition, $instance); } From dcf94414717413bd982a69973fbdfd13e6b17647 Mon Sep 17 00:00:00 2001 From: Jeff Flitton Date: Wed, 24 Jul 2013 14:11:39 -0600 Subject: [PATCH 2/2] Adding tests for performing injection on existing objects. --- src/DI/Container.php | 34 +------------ tests/IntegrationTests/DI/InjectionTest.php | 56 +++++++++++++++++++++ tests/UnitTests/DI/ContainerTest.php | 23 +++++++++ 3 files changed, 81 insertions(+), 32 deletions(-) diff --git a/src/DI/Container.php b/src/DI/Container.php index 7db670f6a..53b4a6765 100644 --- a/src/DI/Container.php +++ b/src/DI/Container.php @@ -153,13 +153,8 @@ public function inject($instance) $className = get_class($instance); $definition = $this->definitionManager->getDefinition($className); - // It's a class - if ($definition instanceof ClassDefinition) { - $instance = $this->injectExistingInstance($definition, $instance); - return $instance; - } - else - return $instance; + $instance = $this->factory->injectInstance($definition, $instance); + return $instance; } /** @@ -336,31 +331,6 @@ private function getNewInstance(ClassDefinition $classDefinition) return $instance; } - /** - * @param ClassDefinition $classDefinition - * @param object $instance - * @return object $instance - */ - private function injectExistingInstance(ClassDefinition $classDefinition, $instance) - { - $classname = $classDefinition->getClassName(); - - if (isset($this->classesBeingInstantiated[$classname])) { - throw new DependencyException("Circular dependency detected while trying to instantiate class '$classname'"); - } - $this->classesBeingInstantiated[$classname] = true; - - try { - $instance = $this->factory->injectInstance($classDefinition, $instance); - } catch (Exception $exception) { - unset($this->classesBeingInstantiated[$classname]); - throw $exception; - } - - unset($this->classesBeingInstantiated[$classname]); - return $instance; - } - /** * Returns a proxy instance * diff --git a/tests/IntegrationTests/DI/InjectionTest.php b/tests/IntegrationTests/DI/InjectionTest.php index b410b5a59..6ecb02e74 100644 --- a/tests/IntegrationTests/DI/InjectionTest.php +++ b/tests/IntegrationTests/DI/InjectionTest.php @@ -14,6 +14,8 @@ use DI\Scope; use DI\Container; use IntegrationTests\DI\Fixtures\Class1; +use IntegrationTests\DI\Fixtures\Class2; +use IntegrationTests\DI\Fixtures\Implementation1; use IntegrationTests\DI\Fixtures\LazyDependency; /** @@ -167,6 +169,33 @@ public function testPropertyInjection($type, Container $container) $this->assertTrue($proxy->isProxyInitialized()); } + /** + * @dataProvider containerProvider + */ + public function testPropertyInjectionExistingObject($type, Container $container) + { + // Only constructor injection with reflection + if ($type == self::DEFINITION_REFLECTION) { + return; + } + /** @var $class1 Class1 */ + $class1 = new Class1(new Class2(), new Implementation1()); + $container->inject($class1); + $this->assertInstanceOf('IntegrationTests\DI\Fixtures\Class2', $class1->property1); + $this->assertInstanceOf('IntegrationTests\DI\Fixtures\Implementation1', $class1->property2); + $this->assertInstanceOf('IntegrationTests\DI\Fixtures\Class2', $class1->property3); + $this->assertEquals('bar', $class1->property4); + // Lazy injection + /** @var LazyDependency|\ProxyManager\Proxy\LazyLoadingInterface $proxy */ + $proxy = $class1->property5; + $this->assertInstanceOf('IntegrationTests\DI\Fixtures\LazyDependency', $proxy); + $this->assertInstanceOf('ProxyManager\Proxy\LazyLoadingInterface', $proxy); + $this->assertFalse($proxy->isProxyInitialized()); + // Correct proxy resolution + $this->assertTrue($proxy->getValue()); + $this->assertTrue($proxy->isProxyInitialized()); + } + /** * @dataProvider containerProvider */ @@ -193,6 +222,33 @@ public function testMethodInjection($type, Container $container) $this->assertTrue($proxy->isProxyInitialized()); } + /** + * @dataProvider containerProvider + */ + public function testMethodInjectionExistingObject($type, Container $container) + { + // Only constructor injection with reflection + if ($type == self::DEFINITION_REFLECTION) { + return; + } + /** @var $class1 Class1 */ + $class1 = new Class1(new Class2(), new Implementation1()); + $container->inject($class1); + $this->assertInstanceOf('IntegrationTests\DI\Fixtures\Class2', $class1->method1Param1); + $this->assertInstanceOf('IntegrationTests\DI\Fixtures\Implementation1', $class1->method2Param1); + $this->assertInstanceOf('IntegrationTests\DI\Fixtures\Class2', $class1->method3Param1); + $this->assertEquals('bar', $class1->method3Param2); + $this->assertInstanceOf('IntegrationTests\DI\Fixtures\LazyDependency', $class1->method4Param1); + $this->assertInstanceOf('ProxyManager\Proxy\LazyLoadingInterface', $class1->method4Param1); + // Lazy injection + /** @var LazyDependency|\ProxyManager\Proxy\LazyLoadingInterface $proxy */ + $proxy = $class1->method4Param1; + $this->assertFalse($proxy->isProxyInitialized()); + // Correct proxy resolution + $this->assertTrue($proxy->getValue()); + $this->assertTrue($proxy->isProxyInitialized()); + } + /** * @dataProvider containerProvider */ diff --git a/tests/UnitTests/DI/ContainerTest.php b/tests/UnitTests/DI/ContainerTest.php index 6227f9c90..1c3b22f87 100644 --- a/tests/UnitTests/DI/ContainerTest.php +++ b/tests/UnitTests/DI/ContainerTest.php @@ -152,6 +152,29 @@ public function testGetNonStringParameter() $container->get(new stdClass()); } + /** + * Test that injecting an existing object returns the same reference to that object + */ + public function testInjectMaintainsReferentialEquality() + { + $container = new Container(); + $instance = new stdClass(); + $result = $container->inject($instance); + + $this->assertSame($instance, $result); + } + + /** + * Test that injection on null yields null + */ + public function testInjectNull() + { + $container = new Container(); + $result = $container->inject(null); + + $this->assertEquals($result, null); + } + /** * We should be able to set a null value * @see https://github.com/mnapoli/PHP-DI/issues/79