Skip to content

Commit 0d524d3

Browse files
committed
feature #4182 Add the possibility to deprecate attributes and nodes on Node (fabpot)
This PR was merged into the 3.x branch. Discussion ---------- Add the possibility to deprecate attributes and nodes on Node Commits ------- 18894cf Add the possibility to deprecate attributes and nodes on Node
2 parents 90fbb9a + 18894cf commit 0d524d3

File tree

4 files changed

+169
-0
lines changed

4 files changed

+169
-0
lines changed

CHANGELOG

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# 3.11.0 (2024-XX-XX)
22

3+
* Add the possibility to deprecate attributes and nodes on `Node`
34
* Add the possibility to add a package and a version to the `deprecated` tag
45
* Add the possibility to add a package for filter/function/test deprecations
56
* Mark `ConstantExpression` as being `@final`

src/Node/NameDeprecation.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
/*
4+
* This file is part of Twig.
5+
*
6+
* (c) Fabien Potencier
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Twig\Node;
13+
14+
/**
15+
* Represents a deprecation for a named node or attribute on a Node.
16+
*
17+
* @author Fabien Potencier <fabien@symfony.com>
18+
*/
19+
class NameDeprecation
20+
{
21+
private $package;
22+
private $version;
23+
private $newName;
24+
25+
public function __construct(string $package = '', string $version = '', string $newName = '')
26+
{
27+
$this->package = $package;
28+
$this->version = $version;
29+
$this->newName = $newName;
30+
}
31+
32+
public function getPackage(): string
33+
{
34+
return $this->package;
35+
}
36+
37+
public function getVersion(): string
38+
{
39+
return $this->version;
40+
}
41+
42+
public function getNewName(): string
43+
{
44+
return $this->newName;
45+
}
46+
}

src/Node/Node.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ class Node implements \Countable, \IteratorAggregate
3030
protected $tag;
3131

3232
private $sourceContext;
33+
/** @var array<string, NameDeprecation> */
34+
private $nodeNameDeprecations = [];
35+
/** @var array<string, NameDeprecation> */
36+
private $attributeNameDeprecations = [];
3337

3438
/**
3539
* @param array $nodes An array of named nodes
@@ -109,14 +113,39 @@ public function getAttribute(string $name)
109113
throw new \LogicException(\sprintf('Attribute "%s" does not exist for Node "%s".', $name, static::class));
110114
}
111115

116+
$triggerDeprecation = \func_num_args() > 1 ? func_get_arg(1) : true;
117+
if ($triggerDeprecation && isset($this->attributeNameDeprecations[$name])) {
118+
$dep = $this->attributeNameDeprecations[$name];
119+
if ($dep->getNewName()) {
120+
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());
121+
} else {
122+
trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting attribute "%s" on a "%s" class is deprecated.', $name, static::class);
123+
}
124+
}
125+
112126
return $this->attributes[$name];
113127
}
114128

115129
public function setAttribute(string $name, $value): void
116130
{
131+
$triggerDeprecation = \func_num_args() > 2 ? func_get_arg(2) : true;
132+
if ($triggerDeprecation && isset($this->attributeNameDeprecations[$name])) {
133+
$dep = $this->attributeNameDeprecations[$name];
134+
if ($dep->getNewName()) {
135+
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());
136+
} else {
137+
trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting attribute "%s" on a "%s" class is deprecated.', $name, static::class);
138+
}
139+
}
140+
117141
$this->attributes[$name] = $value;
118142
}
119143

144+
public function deprecateAttribute(string $name, NameDeprecation $dep): void
145+
{
146+
$this->attributeNameDeprecations[$name] = $dep;
147+
}
148+
120149
public function removeAttribute(string $name): void
121150
{
122151
unset($this->attributes[$name]);
@@ -133,11 +162,31 @@ public function getNode(string $name): self
133162
throw new \LogicException(\sprintf('Node "%s" does not exist for Node "%s".', $name, static::class));
134163
}
135164

165+
$triggerDeprecation = \func_num_args() > 1 ? func_get_arg(1) : true;
166+
if ($triggerDeprecation && isset($this->nodeNameDeprecations[$name])) {
167+
$dep = $this->nodeNameDeprecations[$name];
168+
if ($dep->getNewName()) {
169+
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());
170+
} else {
171+
trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting node "%s" on a "%s" class is deprecated.', $name, static::class);
172+
}
173+
}
174+
136175
return $this->nodes[$name];
137176
}
138177

139178
public function setNode(string $name, self $node): void
140179
{
180+
$triggerDeprecation = \func_num_args() > 2 ? func_get_arg(2) : true;
181+
if ($triggerDeprecation && isset($this->nodeNameDeprecations[$name])) {
182+
$dep = $this->nodeNameDeprecations[$name];
183+
if ($dep->getNewName()) {
184+
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());
185+
} else {
186+
trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting node "%s" on a "%s" class is deprecated.', $name, static::class);
187+
}
188+
}
189+
141190
if (null !== $this->sourceContext) {
142191
$node->setSourceContext($this->sourceContext);
143192
}
@@ -149,6 +198,11 @@ public function removeNode(string $name): void
149198
unset($this->nodes[$name]);
150199
}
151200

201+
public function deprecateNode(string $name, NameDeprecation $dep): void
202+
{
203+
$this->nodeNameDeprecations[$name] = $dep;
204+
}
205+
152206
/**
153207
* @return int
154208
*/

tests/Node/NodeTest.php

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,83 @@
1212
*/
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
16+
use Twig\Node\NameDeprecation;
1517
use Twig\Node\Node;
1618

1719
class NodeTest extends TestCase
1820
{
21+
use ExpectDeprecationTrait;
22+
1923
public function testToString()
2024
{
2125
// callable is not a supported type for a Node attribute, but Drupal uses some apparently
2226
$node = new Node([], ['value' => function () { return '1'; }], 1);
2327

2428
$this->assertEquals('Twig\Node\Node(value: \Closure)', (string) $node);
2529
}
30+
31+
public function testAttributeDeprecationIgnore()
32+
{
33+
$node = new Node([], ['foo' => false]);
34+
$node->deprecateAttribute('foo', new NameDeprecation('foo/bar', '2.0', 'bar'));
35+
36+
$this->assertSame(false, $node->getAttribute('foo', false));
37+
}
38+
39+
/**
40+
* @group legacy
41+
*/
42+
public function testAttributeDeprecationWithoutAlternative()
43+
{
44+
$node = new Node([], ['foo' => false]);
45+
$node->deprecateAttribute('foo', new NameDeprecation('foo/bar', '2.0'));
46+
47+
$this->expectDeprecation('Since foo/bar 2.0: Getting attribute "foo" on a "Twig\Node\Node" class is deprecated.');
48+
$this->assertSame(false, $node->getAttribute('foo'));
49+
}
50+
51+
/**
52+
* @group legacy
53+
*/
54+
public function testAttributeDeprecationWithAlternative()
55+
{
56+
$node = new Node([], ['foo' => false]);
57+
$node->deprecateAttribute('foo', new NameDeprecation('foo/bar', '2.0', 'bar'));
58+
59+
$this->expectDeprecation('Since foo/bar 2.0: Getting attribute "foo" on a "Twig\Node\Node" class is deprecated, get the "bar" attribute instead.');
60+
$this->assertSame(false, $node->getAttribute('foo'));
61+
}
62+
63+
public function testNodeDeprecationIgnore()
64+
{
65+
$node = new Node(['foo' => $foo = new Node()], []);
66+
$node->deprecateNode('foo', new NameDeprecation('foo/bar', '2.0'));
67+
68+
$this->assertSame($foo, $node->getNode('foo', false));
69+
}
70+
71+
/**
72+
* @group legacy
73+
*/
74+
public function testNodeDeprecationWithoutAlternative()
75+
{
76+
$node = new Node(['foo' => $foo = new Node()], []);
77+
$node->deprecateNode('foo', new NameDeprecation('foo/bar', '2.0'));
78+
79+
$this->expectDeprecation('Since foo/bar 2.0: Getting node "foo" on a "Twig\Node\Node" class is deprecated.');
80+
$this->assertSame($foo, $node->getNode('foo'));
81+
}
82+
83+
/**
84+
* @group legacy
85+
*/
86+
public function testNodeAttributeDeprecationWithAlternative()
87+
{
88+
$node = new Node(['foo' => $foo = new Node()], []);
89+
$node->deprecateNode('foo', new NameDeprecation('foo/bar', '2.0', 'bar'));
90+
91+
$this->expectDeprecation('Since foo/bar 2.0: Getting node "foo" on a "Twig\Node\Node" class is deprecated, get the "bar" node instead.');
92+
$this->assertSame($foo, $node->getNode('foo'));
93+
}
2694
}

0 commit comments

Comments
 (0)