diff --git a/UPGRADE-3.2.md b/UPGRADE-3.2.md new file mode 100644 index 0000000000..634f284cc2 --- /dev/null +++ b/UPGRADE-3.2.md @@ -0,0 +1,8 @@ +UPGRADE FROM 3.1 to 3.2 +======================= + +DependencyInjection +------------------- + + * Calling `get()` on a `ContainerBuilder` instance before compiling the + container is deprecated and will throw an exception in Symfony 4.0. diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md index 35e2471d90..1fc371a4ca 100644 --- a/UPGRADE-4.0.md +++ b/UPGRADE-4.0.md @@ -4,6 +4,9 @@ UPGRADE FROM 3.x to 4.0 DependencyInjection ------------------- + * Calling `get()` on a `ContainerBuilder` instance before compiling the + container is not supported anymore and will throw an exception. + * Using unsupported configuration keys in YAML configuration files raises an exception. diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php index a3732fde42..f4424d0cc7 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php @@ -96,11 +96,11 @@ class WebProfilerExtensionTest extends TestCase $this->assertSame($listenerInjected, $this->container->has('web_profiler.debug_toolbar')); + $this->assertSaneContainer($this->getDumpedContainer()); + if ($listenerInjected) { $this->assertSame($listenerEnabled, $this->container->get('web_profiler.debug_toolbar')->isEnabled()); } - - $this->assertSaneContainer($this->getDumpedContainer()); } public function getDebugModes() diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index aae5460ace..7a9703432b 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -94,6 +94,8 @@ class ContainerBuilder extends Container implements TaggedContainerInterface */ private $usedTags = array(); + private $compiled = false; + /** * Sets the track resources flag. * @@ -409,6 +411,10 @@ class ContainerBuilder extends Container implements TaggedContainerInterface */ public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) { + if (!$this->compiled) { + @trigger_error(sprintf('Calling %s() before compiling the container is deprecated since version 3.2 and will throw an exception in 4.0.', __METHOD__), E_USER_DEPRECATED); + } + $id = strtolower($id); if ($service = parent::get($id, ContainerInterface::NULL_ON_INVALID_REFERENCE)) { @@ -543,6 +549,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface } $compiler->compile($this); + $this->compiled = true; if ($this->trackResources) { foreach ($this->definitions as $definition) { diff --git a/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php index 376487d3ab..c569232f20 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php @@ -180,14 +180,17 @@ class GraphvizDumper extends Dumper } foreach ($container->getServiceIds() as $id) { - $service = $container->get($id); - if (array_key_exists($id, $container->getAliases())) { continue; } if (!$container->hasDefinition($id)) { - $class = ('service_container' === $id) ? get_class($this->container) : get_class($service); + if ('service_container' === $id) { + $class = get_class($this->container); + } else { + $class = get_class($container->get($id)); + } + $nodes[$id] = array('class' => str_replace('\\', '\\\\', $class), 'attributes' => $this->options['node.instance']); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutoAliasServicePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutoAliasServicePassTest.php index e3aba6d707..a8d32815e7 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutoAliasServicePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutoAliasServicePassTest.php @@ -57,7 +57,6 @@ class AutoAliasServicePassTest extends \PHPUnit_Framework_TestCase $pass->process($container); $this->assertEquals('Symfony\Component\DependencyInjection\Tests\Compiler\ServiceClassDefault', $container->getDefinition('example')->getClass()); - $this->assertInstanceOf('Symfony\Component\DependencyInjection\Tests\Compiler\ServiceClassDefault', $container->get('example')); } public function testProcessWithExistingAlias() @@ -75,7 +74,7 @@ class AutoAliasServicePassTest extends \PHPUnit_Framework_TestCase $this->assertTrue($container->hasAlias('example')); $this->assertEquals('mysql.example', $container->getAlias('example')); - $this->assertInstanceOf('Symfony\Component\DependencyInjection\Tests\Compiler\ServiceClassMysql', $container->get('example')); + $this->assertSame('Symfony\Component\DependencyInjection\Tests\Compiler\ServiceClassMysql', $container->getDefinition('mysql.example')->getClass()); } public function testProcessWithManualAlias() @@ -86,7 +85,7 @@ class AutoAliasServicePassTest extends \PHPUnit_Framework_TestCase ->addTag('auto_alias', array('format' => '%existing%.example')); $container->register('mysql.example', 'Symfony\Component\DependencyInjection\Tests\Compiler\ServiceClassMysql'); - $container->register('mariadb.example', 'Symfony\Component\DependencyInjection\Tests\Compiler\ServiceClassMariadb'); + $container->register('mariadb.example', 'Symfony\Component\DependencyInjection\Tests\Compiler\ServiceClassMariaDb'); $container->setAlias('example', 'mariadb.example'); $container->setParameter('existing', 'mysql'); @@ -95,7 +94,7 @@ class AutoAliasServicePassTest extends \PHPUnit_Framework_TestCase $this->assertTrue($container->hasAlias('example')); $this->assertEquals('mariadb.example', $container->getAlias('example')); - $this->assertInstanceOf('Symfony\Component\DependencyInjection\Tests\Compiler\ServiceClassMariaDb', $container->get('example')); + $this->assertSame('Symfony\Component\DependencyInjection\Tests\Compiler\ServiceClassMariaDb', $container->getDefinition('mariadb.example')->getClass()); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index c8820d5d59..701fe1b188 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -20,8 +20,6 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\RuntimeException; -use Symfony\Component\DependencyInjection\Exception\InactiveScopeException; -use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\Loader\ClosureLoader; use Symfony\Component\DependencyInjection\Reference; @@ -76,6 +74,9 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase $builder = new ContainerBuilder(); $builder->setDefinition('deprecated_foo', $definition); + + $builder->compile(); + $builder->get('deprecated_foo'); restore_error_handler(); @@ -102,41 +103,80 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase $this->assertTrue($builder->has('bar'), '->has() returns true if a service exists'); } - public function testGet() + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException + * @expectedExceptionMessage You have requested a non-existent service "foo". + */ + public function testGetThrowsExceptionIfServiceDoesNotExist() { $builder = new ContainerBuilder(); - try { - $builder->get('foo'); - $this->fail('->get() throws a ServiceNotFoundException if the service does not exist'); - } catch (ServiceNotFoundException $e) { - $this->assertEquals('You have requested a non-existent service "foo".', $e->getMessage(), '->get() throws a ServiceNotFoundException if the service does not exist'); - } + $builder->compile(); + $builder->get('foo'); + } + + public function testGetReturnsNullIfServiceDoesNotExistAndInvalidReferenceIsUsed() + { + $builder = new ContainerBuilder(); + $builder->compile(); $this->assertNull($builder->get('foo', ContainerInterface::NULL_ON_INVALID_REFERENCE), '->get() returns null if the service does not exist and NULL_ON_INVALID_REFERENCE is passed as a second argument'); + } - $builder->register('foo', 'stdClass'); - $this->assertInternalType('object', $builder->get('foo'), '->get() returns the service definition associated with the id'); - $builder->set('bar', $bar = new \stdClass()); - $this->assertEquals($bar, $builder->get('bar'), '->get() returns the service associated with the id'); - $builder->register('bar', 'stdClass'); - $this->assertEquals($bar, $builder->get('bar'), '->get() returns the service associated with the id even if a definition has been defined'); - + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException + */ + public function testGetThrowsCircularReferenceExceptionIfServiceHasReferenceToItself() + { + $builder = new ContainerBuilder(); $builder->register('baz', 'stdClass')->setArguments(array(new Reference('baz'))); - try { - @$builder->get('baz'); - $this->fail('->get() throws a ServiceCircularReferenceException if the service has a circular reference to itself'); - } catch (\Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException $e) { - $this->assertEquals('Circular reference detected for service "baz", path: "baz".', $e->getMessage(), '->get() throws a LogicException if the service has a circular reference to itself'); - } + $builder->compile(); + $builder->get('baz'); + } + + public function testGetReturnsSameInstanceWhenServiceIsShared() + { + $builder = new ContainerBuilder(); + $builder->register('bar', 'stdClass'); + $builder->compile(); $this->assertTrue($builder->get('bar') === $builder->get('bar'), '->get() always returns the same instance if the service is shared'); } + public function testGetCreatesServiceBasedOnDefinition() + { + $builder = new ContainerBuilder(); + $builder->register('foo', 'stdClass'); + $builder->compile(); + + $this->assertInternalType('object', $builder->get('foo'), '->get() returns the service definition associated with the id'); + } + + public function testGetReturnsRegisteredService() + { + $builder = new ContainerBuilder(); + $builder->set('bar', $bar = new \stdClass()); + $builder->compile(); + + $this->assertSame($bar, $builder->get('bar'), '->get() returns the service associated with the id'); + } + + public function testRegisterDoesNotOverrideExistingService() + { + $builder = new ContainerBuilder(); + $builder->set('bar', $bar = new \stdClass()); + $builder->register('bar', 'stdClass'); + $builder->compile(); + + $this->assertSame($bar, $builder->get('bar'), '->get() returns the service associated with the id even if a definition has been defined'); + } + public function testNonSharedServicesReturnsDifferentInstances() { $builder = new ContainerBuilder(); $builder->register('bar', 'stdClass')->setShared(false); + $builder->compile(); + $this->assertNotSame($builder->get('bar'), $builder->get('bar')); } @@ -149,6 +189,8 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase $builder = new ContainerBuilder(); $builder->register('foo', 'stdClass')->setSynthetic(true); + $builder->compile(); + // we expect a RuntimeException here as foo is synthetic try { $builder->get('foo'); @@ -177,6 +219,9 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase $this->assertFalse($builder->hasAlias('foobar'), '->hasAlias() returns false if the alias does not exist'); $this->assertEquals('foo', (string) $builder->getAlias('bar'), '->getAlias() returns the aliased service'); $this->assertTrue($builder->has('bar'), '->setAlias() defines a new service'); + + $builder->compile(); + $this->assertTrue($builder->get('bar') === $builder->get('foo'), '->setAlias() creates a service that is an alias to another one'); try { @@ -244,6 +289,9 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase $builder->set('aliased', new \stdClass()); $builder->set('alias', $foo = new \stdClass()); + + $builder->compile(); + $this->assertSame($foo, $builder->get('alias'), '->set() replaces an existing alias'); } @@ -261,9 +309,12 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase { $builder = new ContainerBuilder(); $builder->register('foo1', 'Bar\FooClass')->setFile(__DIR__.'/Fixtures/includes/foo.php'); - $this->assertInstanceOf('\Bar\FooClass', $builder->get('foo1'), '->createService() requires the file defined by the service definition'); $builder->register('foo2', 'Bar\FooClass')->setFile(__DIR__.'/Fixtures/includes/%file%.php'); $builder->setParameter('file', 'foo'); + + $builder->compile(); + + $this->assertInstanceOf('\Bar\FooClass', $builder->get('foo1'), '->createService() requires the file defined by the service definition'); $this->assertInstanceOf('\Bar\FooClass', $builder->get('foo2'), '->createService() replaces parameters in the file provided by the service definition'); } @@ -274,6 +325,8 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase $builder->register('foo1', 'Bar\FooClass')->setFile(__DIR__.'/Fixtures/includes/foo.php'); $builder->getDefinition('foo1')->setLazy(true); + $builder->compile(); + $foo1 = $builder->get('foo1'); $this->assertSame($foo1, $builder->get('foo1'), 'The same proxy is retrieved on multiple subsequent calls'); @@ -285,6 +338,9 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase $builder = new ContainerBuilder(); $builder->register('foo1', '%class%'); $builder->setParameter('class', 'stdClass'); + + $builder->compile(); + $this->assertInstanceOf('\stdClass', $builder->get('foo1'), '->createService() replaces parameters in the class provided by the service definition'); } @@ -294,6 +350,9 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase $builder->register('bar', 'stdClass'); $builder->register('foo1', 'Bar\FooClass')->addArgument(array('foo' => '%value%', '%value%' => 'foo', new Reference('bar'), '%%unescape_it%%')); $builder->setParameter('value', 'bar'); + + $builder->compile(); + $this->assertEquals(array('foo' => 'bar', 'bar' => 'foo', $builder->get('bar'), '%unescape_it%'), $builder->get('foo1')->arguments, '->createService() replaces parameters and service references in the arguments provided by the service definition'); } @@ -305,6 +364,8 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase $builder->register('bar', 'Bar\FooClass')->setFactory(array(new Definition('Bar\FooClass'), 'getInstance')); $builder->register('baz', 'Bar\FooClass')->setFactory(array(new Reference('bar'), 'getInstance')); + $builder->compile(); + $this->assertTrue($builder->get('foo')->called, '->createService() calls the factory method to create the service instance'); $this->assertTrue($builder->get('qux')->called, '->createService() calls the factory method to create the service instance'); $this->assertTrue($builder->get('bar')->called, '->createService() uses anonymous service as factory'); @@ -317,6 +378,9 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase $builder->register('bar', 'stdClass'); $builder->register('foo1', 'Bar\FooClass')->addMethodCall('setBar', array(array('%value%', new Reference('bar')))); $builder->setParameter('value', 'bar'); + + $builder->compile(); + $this->assertEquals(array('bar', $builder->get('bar')), $builder->get('foo1')->bar, '->createService() replaces the values in the method calls arguments'); } @@ -326,6 +390,9 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase $builder->register('bar', 'stdClass'); $builder->register('foo1', 'Bar\FooClass')->addMethodCall('setBar', array(array('%%unescape_it%%'))); $builder->setParameter('value', 'bar'); + + $builder->compile(); + $this->assertEquals(array('%unescape_it%'), $builder->get('foo1')->bar, '->createService() replaces the values in the method calls arguments'); } @@ -335,6 +402,9 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase $builder->register('bar', 'stdClass'); $builder->register('foo1', 'Bar\FooClass')->setProperty('bar', array('%value%', new Reference('bar'), '%%unescape_it%%')); $builder->setParameter('value', 'bar'); + + $builder->compile(); + $this->assertEquals(array('bar', $builder->get('bar'), '%unescape_it%'), $builder->get('foo1')->bar, '->createService() replaces the values in the properties'); } @@ -342,20 +412,20 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase { $builder = new ContainerBuilder(); $builder->register('foo1', 'Bar\FooClass')->setConfigurator('sc_configure'); - $this->assertTrue($builder->get('foo1')->configured, '->createService() calls the configurator'); - $builder->register('foo2', 'Bar\FooClass')->setConfigurator(array('%class%', 'configureStatic')); $builder->setParameter('class', 'BazClass'); - $this->assertTrue($builder->get('foo2')->configured, '->createService() calls the configurator'); - $builder->register('baz', 'BazClass'); $builder->register('foo3', 'Bar\FooClass')->setConfigurator(array(new Reference('baz'), 'configure')); - $this->assertTrue($builder->get('foo3')->configured, '->createService() calls the configurator'); - $builder->register('foo4', 'Bar\FooClass')->setConfigurator(array($builder->getDefinition('baz'), 'configure')); + $builder->register('foo5', 'Bar\FooClass')->setConfigurator('foo'); + + $builder->compile(); + + $this->assertTrue($builder->get('foo1')->configured, '->createService() calls the configurator'); + $this->assertTrue($builder->get('foo2')->configured, '->createService() calls the configurator'); + $this->assertTrue($builder->get('foo3')->configured, '->createService() calls the configurator'); $this->assertTrue($builder->get('foo4')->configured, '->createService() calls the configurator'); - $builder->register('foo5', 'Bar\FooClass')->setConfigurator('foo'); try { $builder->get('foo5'); $this->fail('->createService() throws an InvalidArgumentException if the configure callable is not a valid callable'); @@ -371,6 +441,9 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase { $builder = new ContainerBuilder(); $builder->register('foo', 'Bar\FooClass')->setSynthetic(true); + + $builder->compile(); + $builder->get('foo'); } @@ -380,6 +453,9 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase $builder->setParameter('bar', 'bar'); $builder->register('bar', 'BarClass'); $builder->register('foo', 'Bar\FooClass')->addArgument(array('foo' => new Expression('service("bar").foo ~ parameter("bar")'))); + + $builder->compile(); + $this->assertEquals('foobar', $builder->get('foo')->arguments['foo']); } @@ -387,6 +463,8 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase { $builder = new ContainerBuilder(); $builder->register('foo', 'Bar\FooClass'); + $builder->compile(); + $this->assertEquals($builder->get('foo'), $builder->resolveServices(new Reference('foo')), '->resolveServices() resolves service references to service instances'); $this->assertEquals(array('foo' => array('foo', $builder->get('foo'))), $builder->resolveServices(array('foo' => array('foo', new Reference('foo')))), '->resolveServices() resolves service references to service instances in nested arrays'); $this->assertEquals($builder->get('foo'), $builder->resolveServices(new Expression('service("foo")')), '->resolveServices() resolves expressions');