Permalink
Browse files

feature #29968 [DI] Added support for deprecating aliases (j92, Renan)

This PR was merged into the 4.3-dev branch.

Discussion
----------

[DI] Added support for deprecating aliases

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? |no
| Tests pass?   | yes
| Fixed tickets | #24507
| License       | MIT
| Doc PR        | TBD

This PR is a continuity of #24707

Commits
-------

6c571ad Added support for deprecating aliases (runtime+dumper)
0eb071b Added support for deprecating an alias
  • Loading branch information...
nicolas-grekas committed Jan 25, 2019
2 parents 0901bbe + 6c571ad commit edc4a0f572cb82b7b322bd2db473b8702f76b2a7
@@ -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,46 @@ public function isPrivate()
return $this->private;
}
/**
* Whether this alias is deprecated, that means it should not be referenced
* anymore.
*
* @param bool $status Whether this alias is deprecated, 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;
}
public function isDeprecated(): bool
{
return $this->deprecated;
}
public function getDeprecationMessage(string $id): string
{
return str_replace('%service_id%', $id, $this->deprecationTemplate ?: self::$defaultDeprecationTemplate);
}
/**
* Returns the Id of this alias.
*
@@ -31,6 +31,7 @@ public function process(ContainerBuilder $container)
foreach ($container->getAliases() as $id => $alias) {
$aliasId = (string) $alias;
if ($aliasId !== $defId = $this->getDefinitionId($aliasId, $container)) {
$container->setAlias($id, $defId)->setPublic($alias->isPublic())->setPrivate($alias->isPrivate());
}
@@ -60,8 +61,15 @@ private function getDefinitionId(string $id, ContainerBuilder $container): strin
if (isset($seen[$id])) {
throw new ServiceCircularReferenceException($id, array_merge(array_keys($seen), [$id]));
}
$seen[$id] = true;
$id = (string) $container->getAlias($id);
$alias = $container->getAlias($id);
if ($alias->isDeprecated()) {
@trigger_error($alias->getDeprecationMessage($id), E_USER_DEPRECATED);
}
$id = (string) $alias;
}
return $id;
@@ -579,7 +579,13 @@ private function doGet($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_
}
if (!isset($this->definitions[$id]) && isset($this->aliasDefinitions[$id])) {
return $this->doGet((string) $this->aliasDefinitions[$id], $invalidBehavior, $inlineServices, $isConstructorArgument);
$alias = $this->aliasDefinitions[$id];
if ($alias->isDeprecated()) {
@trigger_error($alias->getDeprecationMessage($id), E_USER_DEPRECATED);
}
return $this->doGet((string) $alias, $invalidBehavior, $inlineServices, $isConstructorArgument);
}
try {
@@ -218,6 +218,7 @@ public function dump(array $options = [])
$code =
$this->startClass($options['class'], $baseClass, $baseClassWithNamespace).
$this->addServices($services).
$this->addDeprecatedAliases().
$this->addDefaultParametersMethod()
;
@@ -1115,6 +1116,15 @@ private function addMethodMap(): string
}
}
$aliases = $this->container->getAliases();
foreach ($aliases as $alias => $id) {
if (!$id->isDeprecated()) {
continue;
}
$id = (string) $id;
$code .= ' '.$this->doExport($alias).' => '.$this->doExport($this->generateMethodName($alias)).",\n";
}
return $code ? " \$this->methodMap = [\n{$code} ];\n" : '';
}
@@ -1141,6 +1151,10 @@ private function addAliases(): string
$code = " \$this->aliases = [\n";
ksort($aliases);
foreach ($aliases as $alias => $id) {
if ($id->isDeprecated()) {
continue;
}
$id = (string) $id;
while (isset($aliases[$id])) {
$id = (string) $aliases[$id];
@@ -1151,6 +1165,39 @@ private function addAliases(): string
return $code." ];\n";
}
private function addDeprecatedAliases(): string
{
$code = '';
$aliases = $this->container->getAliases();
foreach ($aliases as $alias => $definition) {
if (!$definition->isDeprecated()) {
continue;
}
$public = $definition->isPublic() ? 'public' : 'private';
$id = (string) $definition;
$methodNameAlias = $this->generateMethodName($alias);
$idExported = $this->export($id);
$messageExported = $this->export($definition->getDeprecationMessage($alias));
$code = <<<EOF
/*{$this->docStar}
* Gets the $public '$alias' alias.
*
* @return object The "$id" service.
*/
protected function {$methodNameAlias}()
{
@trigger_error($messageExported, E_USER_DEPRECATED);
return \$this->get($idExported);
}
EOF;
}
return $code;
}
private function addInlineRequires(): string
{
if (!$this->hotPathTag || !$this->inlineRequires) {
@@ -220,6 +220,10 @@ private function parseDefinition(\DOMElement $service, $file, array $defaults)
$alias->setPublic($defaults['public']);
}
if ($deprecated = $this->getChildren($service, 'deprecated')) {
$alias->setDeprecated(true, $deprecated[0]->nodeValue ?: null);
}
return;
}
@@ -668,7 +672,10 @@ private function validateAlias(\DOMElement $alias, $file)
}
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, ['deprecated'], true)) {
throw new InvalidArgumentException(sprintf('Invalid child element "%s" defined for alias "%s" in "%s".', $child->localName, $alias->getAttribute('id'), $file));
}
}
@@ -349,9 +349,13 @@ private function parseDefinition($id, $service, $file, array $defaults)
}
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));
}
if ('deprecated' === $key) {
$alias->setDeprecated(true, $value);
}
}
return;
@@ -0,0 +1,110 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 [
"With \rs" => ["invalid \r message %service_id%"],
"With \ns" => ["invalid \n message %service_id%"],
'With */s' => ['invalid */ message %service_id%'],
'message not containing required %service_id% variable' => ['this is deprecated'],
];
}
}
@@ -83,6 +83,48 @@ public function testResolveFactory()
$this->assertSame('Factory', (string) $resolvedBarFactory[0]);
}
/**
* @group legacy
* @expectedDeprecation The "deprecated_foo_alias" service alias is deprecated. You should stop using it, as it will soon be removed.
*/
public function testDeprecationNoticeWhenReferencedByAlias()
{
$container = new ContainerBuilder();
$container->register('foo', 'stdClass');
$aliasDeprecated = new Alias('foo');
$aliasDeprecated->setDeprecated(true);
$container->setAlias('deprecated_foo_alias', $aliasDeprecated);
$alias = new Alias('deprecated_foo_alias');
$container->setAlias('alias', $alias);
$this->process($container);
}
/**
* @group legacy
* @expectedDeprecation The "foo_aliased" service alias is deprecated. You should stop using it, as it will soon be removed.
*/
public function testDeprecationNoticeWhenReferencedByDefinition()
{
$container = new ContainerBuilder();
$container->register('foo', 'stdClass');
$aliasDeprecated = new Alias('foo');
$aliasDeprecated->setDeprecated(true);
$container->setAlias('foo_aliased', $aliasDeprecated);
$container
->register('definition')
->setArguments([new Reference('foo_aliased')])
;
$this->process($container);
}
protected function process(ContainerBuilder $container)
{
$pass = new ResolveReferencesToAliasesPass();
Oops, something went wrong.

0 comments on commit edc4a0f

Please sign in to comment.