From 18894cf91ed3cfa3ecd4344112c4eb6a32f279ab Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 8 Aug 2024 11:07:26 +0200 Subject: [PATCH] Add the possibility to deprecate attributes and nodes on Node --- CHANGELOG | 1 + src/Node/NameDeprecation.php | 46 ++++++++++++++++++++++++ src/Node/Node.php | 54 ++++++++++++++++++++++++++++ tests/Node/NodeTest.php | 68 ++++++++++++++++++++++++++++++++++++ 4 files changed, 169 insertions(+) create mode 100644 src/Node/NameDeprecation.php diff --git a/CHANGELOG b/CHANGELOG index fd552fe3563..3922d3f140d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ # 3.11.0 (2024-XX-XX) + * Add the possibility to deprecate attributes and nodes on `Node` * Add the possibility to add a package and a version to the `deprecated` tag * Add the possibility to add a package for filter/function/test deprecations * Mark `ConstantExpression` as being `@final` diff --git a/src/Node/NameDeprecation.php b/src/Node/NameDeprecation.php new file mode 100644 index 00000000000..63ab285761a --- /dev/null +++ b/src/Node/NameDeprecation.php @@ -0,0 +1,46 @@ + + */ +class NameDeprecation +{ + private $package; + private $version; + private $newName; + + public function __construct(string $package = '', string $version = '', string $newName = '') + { + $this->package = $package; + $this->version = $version; + $this->newName = $newName; + } + + public function getPackage(): string + { + return $this->package; + } + + public function getVersion(): string + { + return $this->version; + } + + public function getNewName(): string + { + return $this->newName; + } +} diff --git a/src/Node/Node.php b/src/Node/Node.php index e0e473e8cc4..5ef661f5e37 100644 --- a/src/Node/Node.php +++ b/src/Node/Node.php @@ -30,6 +30,10 @@ class Node implements \Countable, \IteratorAggregate protected $tag; private $sourceContext; + /** @var array */ + private $nodeNameDeprecations = []; + /** @var array */ + private $attributeNameDeprecations = []; /** * @param array $nodes An array of named nodes @@ -109,14 +113,39 @@ public function getAttribute(string $name) throw new \LogicException(\sprintf('Attribute "%s" does not exist for Node "%s".', $name, static::class)); } + $triggerDeprecation = \func_num_args() > 1 ? func_get_arg(1) : true; + if ($triggerDeprecation && isset($this->attributeNameDeprecations[$name])) { + $dep = $this->attributeNameDeprecations[$name]; + if ($dep->getNewName()) { + trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting attribute "%s" on a "%s" class is deprecated, get the "%s" attribute instead.', $name, static::class, $dep->getNewName()); + } else { + trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting attribute "%s" on a "%s" class is deprecated.', $name, static::class); + } + } + return $this->attributes[$name]; } public function setAttribute(string $name, $value): void { + $triggerDeprecation = \func_num_args() > 2 ? func_get_arg(2) : true; + if ($triggerDeprecation && isset($this->attributeNameDeprecations[$name])) { + $dep = $this->attributeNameDeprecations[$name]; + if ($dep->getNewName()) { + trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting attribute "%s" on a "%s" class is deprecated, set the "%s" attribute instead.', $name, static::class, $dep->getNewName()); + } else { + trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting attribute "%s" on a "%s" class is deprecated.', $name, static::class); + } + } + $this->attributes[$name] = $value; } + public function deprecateAttribute(string $name, NameDeprecation $dep): void + { + $this->attributeNameDeprecations[$name] = $dep; + } + public function removeAttribute(string $name): void { unset($this->attributes[$name]); @@ -133,11 +162,31 @@ public function getNode(string $name): self throw new \LogicException(\sprintf('Node "%s" does not exist for Node "%s".', $name, static::class)); } + $triggerDeprecation = \func_num_args() > 1 ? func_get_arg(1) : true; + if ($triggerDeprecation && isset($this->nodeNameDeprecations[$name])) { + $dep = $this->nodeNameDeprecations[$name]; + if ($dep->getNewName()) { + trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting node "%s" on a "%s" class is deprecated, get the "%s" node instead.', $name, static::class, $dep->getNewName()); + } else { + trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting node "%s" on a "%s" class is deprecated.', $name, static::class); + } + } + return $this->nodes[$name]; } public function setNode(string $name, self $node): void { + $triggerDeprecation = \func_num_args() > 2 ? func_get_arg(2) : true; + if ($triggerDeprecation && isset($this->nodeNameDeprecations[$name])) { + $dep = $this->nodeNameDeprecations[$name]; + if ($dep->getNewName()) { + trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting node "%s" on a "%s" class is deprecated, set the "%s" node instead.', $name, static::class, $dep->getNewName()); + } else { + trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting node "%s" on a "%s" class is deprecated.', $name, static::class); + } + } + if (null !== $this->sourceContext) { $node->setSourceContext($this->sourceContext); } @@ -149,6 +198,11 @@ public function removeNode(string $name): void unset($this->nodes[$name]); } + public function deprecateNode(string $name, NameDeprecation $dep): void + { + $this->nodeNameDeprecations[$name] = $dep; + } + /** * @return int */ diff --git a/tests/Node/NodeTest.php b/tests/Node/NodeTest.php index 8e82af5b9e9..b4f8eee02aa 100644 --- a/tests/Node/NodeTest.php +++ b/tests/Node/NodeTest.php @@ -12,10 +12,14 @@ */ use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; +use Twig\Node\NameDeprecation; use Twig\Node\Node; class NodeTest extends TestCase { + use ExpectDeprecationTrait; + public function testToString() { // callable is not a supported type for a Node attribute, but Drupal uses some apparently @@ -23,4 +27,68 @@ public function testToString() $this->assertEquals('Twig\Node\Node(value: \Closure)', (string) $node); } + + public function testAttributeDeprecationIgnore() + { + $node = new Node([], ['foo' => false]); + $node->deprecateAttribute('foo', new NameDeprecation('foo/bar', '2.0', 'bar')); + + $this->assertSame(false, $node->getAttribute('foo', false)); + } + + /** + * @group legacy + */ + public function testAttributeDeprecationWithoutAlternative() + { + $node = new Node([], ['foo' => false]); + $node->deprecateAttribute('foo', new NameDeprecation('foo/bar', '2.0')); + + $this->expectDeprecation('Since foo/bar 2.0: Getting attribute "foo" on a "Twig\Node\Node" class is deprecated.'); + $this->assertSame(false, $node->getAttribute('foo')); + } + + /** + * @group legacy + */ + public function testAttributeDeprecationWithAlternative() + { + $node = new Node([], ['foo' => false]); + $node->deprecateAttribute('foo', new NameDeprecation('foo/bar', '2.0', 'bar')); + + $this->expectDeprecation('Since foo/bar 2.0: Getting attribute "foo" on a "Twig\Node\Node" class is deprecated, get the "bar" attribute instead.'); + $this->assertSame(false, $node->getAttribute('foo')); + } + + public function testNodeDeprecationIgnore() + { + $node = new Node(['foo' => $foo = new Node()], []); + $node->deprecateNode('foo', new NameDeprecation('foo/bar', '2.0')); + + $this->assertSame($foo, $node->getNode('foo', false)); + } + + /** + * @group legacy + */ + public function testNodeDeprecationWithoutAlternative() + { + $node = new Node(['foo' => $foo = new Node()], []); + $node->deprecateNode('foo', new NameDeprecation('foo/bar', '2.0')); + + $this->expectDeprecation('Since foo/bar 2.0: Getting node "foo" on a "Twig\Node\Node" class is deprecated.'); + $this->assertSame($foo, $node->getNode('foo')); + } + + /** + * @group legacy + */ + public function testNodeAttributeDeprecationWithAlternative() + { + $node = new Node(['foo' => $foo = new Node()], []); + $node->deprecateNode('foo', new NameDeprecation('foo/bar', '2.0', 'bar')); + + $this->expectDeprecation('Since foo/bar 2.0: Getting node "foo" on a "Twig\Node\Node" class is deprecated, get the "bar" node instead.'); + $this->assertSame($foo, $node->getNode('foo')); + } }