diff --git a/src/Symfony/Component/DependencyInjection/Alias.php b/src/Symfony/Component/DependencyInjection/Alias.php index 24484de167..2b52986be8 100644 --- a/src/Symfony/Component/DependencyInjection/Alias.php +++ b/src/Symfony/Component/DependencyInjection/Alias.php @@ -11,17 +11,24 @@ namespace Symfony\Component\DependencyInjection; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; + class Alias { private $id; private $public; private $private; + private $deprecated; + private $deprecationTemplate; + + private static $defaultDeprecationTemplate = 'The "%service_id%" service alias is deprecated. You should stop using it, as it will soon be removed.'; public function __construct(string $id, bool $public = true) { $this->id = $id; $this->public = $public; $this->private = 2 > \func_num_args(); + $this->deprecated = false; } /** @@ -78,6 +85,58 @@ class Alias return $this->private; } + /** + * Whether this alias is deprecated, that means it should not be called + * anymore. + * + * @param bool $status Defaults to true + * @param string $template Optional template message to use if the alias is deprecated + * + * @return $this + * + * @throws InvalidArgumentException when the message template is invalid + */ + public function setDeprecated($status = true, $template = null) + { + if (null !== $template) { + if (preg_match('#[\r\n]|\*/#', $template)) { + throw new InvalidArgumentException('Invalid characters found in deprecation template.'); + } + + if (false === strpos($template, '%service_id%')) { + throw new InvalidArgumentException('The deprecation template must contain the "%service_id%" placeholder.'); + } + + $this->deprecationTemplate = $template; + } + + $this->deprecated = (bool) $status; + + return $this; + } + + /** + * Returns whether this alias is deprecated. + * + * @return bool + */ + public function isDeprecated() + { + return $this->deprecated; + } + + /** + * Message to use if this alias is deprecated. + * + * @param string $id Service id relying on this alias + * + * @return string + */ + public function getDeprecationMessage($id) + { + return str_replace('%service_id%', $id, $this->deprecationTemplate ?: self::$defaultDeprecationTemplate); + } + /** * Returns the Id of this alias. * diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 58a4bfc484..999410fb2d 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -579,7 +579,12 @@ class ContainerBuilder extends Container implements TaggedContainerInterface } if (!isset($this->definitions[$id]) && isset($this->aliasDefinitions[$id])) { - return $this->doGet((string) $this->aliasDefinitions[$id], $invalidBehavior, $inlineServices, $isConstructorArgument); + $aliasDefinition = $this->aliasDefinitions[$id]; + if ($aliasDefinition->isDeprecated()) { + @trigger_error($aliasDefinition->getDeprecationMessage($id), E_USER_DEPRECATED); + } + + return $this->doGet((string) $aliasDefinition, $invalidBehavior, $inlineServices, $isConstructorArgument); } try { diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index c5292ecf68..61b30a22e4 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -220,6 +220,10 @@ class XmlFileLoader extends FileLoader $alias->setPublic($defaults['public']); } + if ($deprecated = $this->getChildren($service, 'deprecated')) { + $alias->setDeprecated(true, $deprecated[0]->nodeValue ?: null); + } + return; } @@ -667,8 +671,12 @@ EOF } } + $allowedTags = array('deprecated'); foreach ($alias->childNodes as $child) { - if ($child instanceof \DOMElement && self::NS === $child->namespaceURI) { + if (!$child instanceof \DOMElement && self::NS !== $child->namespaceURI) { + continue; + } + if (!in_array($child->localName, $allowedTags, true)) { throw new InvalidArgumentException(sprintf('Invalid child element "%s" defined for alias "%s" in "%s".', $child->localName, $alias->getAttribute('id'), $file)); } } diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index 05fbe86363..13d6746c10 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -349,8 +349,13 @@ class YamlFileLoader extends FileLoader } foreach ($service as $key => $value) { - if (!\in_array($key, ['alias', 'public'])) { + if (!\in_array($key, ['alias', 'public', 'deprecated'])) { throw new InvalidArgumentException(sprintf('The configuration key "%s" is unsupported for the service "%s" which is defined as an alias in "%s". Allowed configuration keys for service aliases are "alias" and "public".', $key, $id, $file)); + continue; + } + + if ('deprecated' === $key) { + $alias->setDeprecated(true, $value); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/AliasTest.php b/src/Symfony/Component/DependencyInjection/Tests/AliasTest.php new file mode 100644 index 0000000000..eb61a9df7d --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/AliasTest.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Alias; + +class AliasTest extends TestCase +{ + public function testConstructor() + { + $alias = new Alias('foo'); + + $this->assertEquals('foo', (string) $alias); + $this->assertTrue($alias->isPublic()); + } + + public function testCanConstructANonPublicAlias() + { + $alias = new Alias('foo', false); + + $this->assertEquals('foo', (string) $alias); + $this->assertFalse($alias->isPublic()); + } + + public function testCanConstructAPrivateAlias() + { + $alias = new Alias('foo', false, false); + + $this->assertEquals('foo', (string) $alias); + $this->assertFalse($alias->isPublic()); + $this->assertFalse($alias->isPrivate()); + } + + public function testCanSetPublic() + { + $alias = new Alias('foo', false); + $alias->setPublic(true); + + $this->assertTrue($alias->isPublic()); + } + + public function testCanDeprecateAnAlias() + { + $alias = new Alias('foo', false); + $alias->setDeprecated(true, 'The %service_id% service is deprecated.'); + + $this->assertTrue($alias->isDeprecated()); + } + + public function testItHasADefaultDeprecationMessage() + { + $alias = new Alias('foo', false); + $alias->setDeprecated(); + + $expectedMessage = 'The "foo" service alias is deprecated. You should stop using it, as it will soon be removed.'; + $this->assertEquals($expectedMessage, $alias->getDeprecationMessage('foo')); + } + + public function testReturnsCorrectDeprecationMessage() + { + $alias = new Alias('foo', false); + $alias->setDeprecated(true, 'The "%service_id%" is deprecated.'); + + $expectedMessage = 'The "foo" is deprecated.'; + $this->assertEquals($expectedMessage, $alias->getDeprecationMessage('foo')); + } + + public function testCanOverrideDeprecation() + { + $alias = new Alias('foo', false); + $alias->setDeprecated(); + + $initial = $alias->isDeprecated(); + $alias->setDeprecated(false); + $final = $alias->isDeprecated(); + + $this->assertTrue($initial); + $this->assertFalse($final); + } + + /** + * @dataProvider invalidDeprecationMessageProvider + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + */ + public function testCannotDeprecateWithAnInvalidTemplate($message) + { + $def = new Alias('foo'); + $def->setDeprecated(true, $message); + } + + public function invalidDeprecationMessageProvider() + { + return array( + "With \rs" => array("invalid \r message %service_id%"), + "With \ns" => array("invalid \n message %service_id%"), + 'With */s' => array('invalid */ message %service_id%'), + 'message not containing required %service_id% variable' => array('this is deprecated'), + ); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index 83c0458e5c..cec7ea4d18 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -259,6 +259,22 @@ class ContainerBuilderTest extends TestCase } } + /** + * @group legacy + * @expectedDeprecation The "foobar" service alias is deprecated. You should stop using it, as it will soon be removed. + */ + public function testDeprecatedAlias() + { + $builder = new ContainerBuilder(); + $builder->register('foo', 'stdClass'); + + $alias = new Alias('foo'); + $alias->setDeprecated(); + $builder->setAlias('foobar', $alias); + + $builder->get('foobar'); + } + public function testGetAliases() { $builder = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/deprecated_alias_definitions.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/deprecated_alias_definitions.xml new file mode 100644 index 0000000000..83ceeefa9c --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/deprecated_alias_definitions.xml @@ -0,0 +1,13 @@ + + + + + + + + + + The "%service_id%" service alias is deprecated. + + + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/deprecated_alias_definitions.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/deprecated_alias_definitions.yml new file mode 100644 index 0000000000..2223597815 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/deprecated_alias_definitions.yml @@ -0,0 +1,4 @@ +services: + alias_for_foobar: + alias: foobar + deprecated: The "%service_id%" service alias is deprecated. diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php index 6d9634c66c..e5037d809b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -351,6 +351,21 @@ class XmlFileLoaderTest extends TestCase $this->assertSame($message, $container->getDefinition('bar')->getDeprecationMessage('bar')); } + public function testDeprecatedAliases() + { + $container = new ContainerBuilder(); + $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml')); + $loader->load('deprecated_alias_definitions.xml'); + + $this->assertTrue($container->getAlias('alias_for_foo')->isDeprecated()); + $message = 'The "alias_for_foo" service alias is deprecated. You should stop using it, as it will soon be removed.'; + $this->assertSame($message, $container->getAlias('alias_for_foo')->getDeprecationMessage('alias_for_foo')); + + $this->assertTrue($container->getAlias('alias_for_foobar')->isDeprecated()); + $message = 'The "alias_for_foobar" service alias is deprecated.'; + $this->assertSame($message, $container->getAlias('alias_for_foobar')->getDeprecationMessage('alias_for_foobar')); + } + public function testConvertDomElementToArray() { $doc = new \DOMDocument('1.0'); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php index 37e6054f55..8c9ccaf06f 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php @@ -175,6 +175,17 @@ class YamlFileLoaderTest extends TestCase $this->assertEquals(['decorated', 'decorated.pif-pouf', 5], $services['decorator_service_with_name_and_priority']->getDecoratedService()); } + public function testDeprecatedAliases() + { + $container = new ContainerBuilder(); + $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml')); + $loader->load('deprecated_alias_definitions.yml'); + + $this->assertTrue($container->getAlias('alias_for_foobar')->isDeprecated()); + $message = 'The "alias_for_foobar" service alias is deprecated.'; + $this->assertSame($message, $container->getAlias('alias_for_foobar')->getDeprecationMessage('alias_for_foobar')); + } + public function testLoadFactoryShortSyntax() { $container = new ContainerBuilder();