Skip to content

Commit 6a6c5b3

Browse files
authored
Merge pull request #307 from phpDocumentor/feature/expressions-in-arguments
Started reworking expression strings into Expression objects
2 parents 19faf01 + 88113ca commit 6a6c5b3

30 files changed

+971
-122
lines changed

docs/expressions.rst

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
Expressions
2+
===========
3+
4+
Starting with version 5.4, we now support parsing expressions and extracting types and references to elements from them.
5+
6+
.. info::
7+
8+
An expression is, for example, the default value for a property or argument, the definition of an enum case or a
9+
constant value. These are called expressions and can contain more complex combinations of operators and values.
10+
11+
As this library revolves around reflecting Static information, most parts of an expression are considered irrelevant;
12+
except for type information -such as type hints- and references to other elements, such as constants. As such, whenever
13+
an expression is interpreted, it will result in a string containing placeholders and an array containing the reflected
14+
parts -such as FQSENs-.
15+
16+
This means that the getters like ``getDefault()`` will return a string or when you provide the optional argument
17+
$isString as being false, it will return an Expression object; which, when cast to string, will provide the same result.
18+
19+
.. warning::
20+
21+
Deprecation: In version 6, we will remove the optional argument and always return an Expression object. When the
22+
result was used as a string nothing will change, but code that checks if the output is a string will no longer
23+
function starting from that version.
24+
25+
This will allow consumers to be able to extract types and links to elements from expressions. This allows consumers to,
26+
for example, interpret the default value for a constructor promoted properties when it directly instantiates an object.
27+
28+
Creating expressions
29+
--------------------
30+
31+
.. hint::
32+
33+
The description below is only for internal usage and to understand how expressions work, this library deals with
34+
this by default.
35+
36+
In this library, we use the ExpressionPrinter to convert a PHP-Parser node -or expression- into an expression
37+
like this::
38+
39+
$printer = new ExpressionPrinter();
40+
$expressionTemplate = $printer->prettyPrintExpr($phpParserNode);
41+
$expression = new Expression($expressionTemplate, $printer->getParts());
42+
43+
In the example above we assume that there is a PHP-Parser node representing an expression; this node is passed to the
44+
ExpressionPrinter -which is an adapted PrettyPrinter from PHP-Parser- which will render the expression as a readable
45+
template string containing placeholders, and a list of parts that can slot into the placeholders.
46+
47+
Consuming expressions
48+
---------------------
49+
50+
When using this library, you can consume these expression objects either by
51+
52+
1. Directly casting them to a string - this will replace all placeholders with the stringified version of the parts
53+
2. Use the render function - this will do the same as the previous methods but you can specify one or more overrides
54+
for the placeholders in the expression
55+
56+
The second method can be used to create your own string values from the given parts and render, for example, links in
57+
these locations.
58+
59+
Another way to use these expressions is to interpret the parts array, and through that way know which elements and
60+
types are referred to in that expression.

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,5 @@ are however several advantages to using this library:
2525

2626
getting-started
2727
reflection-structure
28+
expressions
2829
extending/index

phpstan.neon

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ parameters:
66
ignoreErrors:
77

88
- '#Method phpDocumentor\\Reflection\\File\\LocalFile::md5\(\) should return string but returns string\|false\.#'
9-
- '#Else branch is unreachable because ternary operator condition is always true\.#'
109
#
1110
# all these $fqsen errors indicate the need for a decorator class around PhpParser\Node to hold the public $fqsen that Reflection is giving it)
1211
#

psalm-baseline.xml

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
</MixedArgumentTypeCoercion>
3030
</file>
3131
<file src="src/phpDocumentor/Reflection/NodeVisitor/ElementNameResolver.php">
32+
<DeprecatedClass>
33+
<code><![CDATA[PropertyProperty::class]]></code>
34+
<code><![CDATA[PropertyProperty::class]]></code>
35+
</DeprecatedClass>
3236
<ImplicitToStringCast>
3337
<code><![CDATA[$node->name]]></code>
3438
<code><![CDATA[$node->name]]></code>
@@ -53,9 +57,6 @@
5357
<PossiblyUndefinedMethod>
5458
<code><![CDATA[addConstant]]></code>
5559
</PossiblyUndefinedMethod>
56-
<RedundantCondition>
57-
<code><![CDATA[$const->getValue() !== null]]></code>
58-
</RedundantCondition>
5960
</file>
6061
<file src="src/phpDocumentor/Reflection/Php/Factory/ClassConstantIterator.php">
6162
<MixedReturnStatement>
@@ -115,11 +116,6 @@
115116
<code><![CDATA[$object->getAttribute('fqsen')]]></code>
116117
</MixedArgument>
117118
</file>
118-
<file src="src/phpDocumentor/Reflection/Php/Factory/GlobalConstant.php">
119-
<RedundantCondition>
120-
<code><![CDATA[$const->getValue() !== null]]></code>
121-
</RedundantCondition>
122-
</file>
123119
<file src="src/phpDocumentor/Reflection/Php/Factory/GlobalConstantIterator.php">
124120
<MixedReturnStatement>
125121
<code><![CDATA[$this->constant->consts[$this->index]->getAttribute('fqsen')]]></code>

psalm.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
<projectFiles>
1010
<directory name="src" />
1111
<ignoreFiles>
12+
<!--
13+
ExpressionPrinter inherits all kinds of errors from PHP-Parser that I cannot fix, until a solution is found
14+
we ignore this
15+
-->
16+
<file name="src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php"/>
1217
<directory name="vendor" />
1318
</ignoreFiles>
1419
</projectFiles>

src/phpDocumentor/Reflection/NodeVisitor/ElementNameResolver.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use phpDocumentor\Reflection\Fqsen;
1818
use PhpParser\Node;
1919
use PhpParser\Node\Const_;
20+
use PhpParser\Node\PropertyItem;
2021
use PhpParser\Node\Stmt\Class_;
2122
use PhpParser\Node\Stmt\ClassConst;
2223
use PhpParser\Node\Stmt\ClassMethod;
@@ -72,6 +73,7 @@ public function leaveNode(Node $node)
7273
case ClassMethod::class:
7374
case Trait_::class:
7475
case PropertyProperty::class:
76+
case PropertyItem::class:
7577
case Node\PropertyItem::class:
7678
case ClassConst::class:
7779
case Const_::class:

src/phpDocumentor/Reflection/Php/Argument.php

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616
use phpDocumentor\Reflection\Type;
1717
use phpDocumentor\Reflection\Types\Mixed_;
1818

19+
use function is_string;
20+
use function trigger_error;
21+
22+
use const E_USER_DEPRECATED;
23+
1924
/**
2025
* Descriptor representing a single Argument of a method or function.
2126
*
@@ -33,8 +38,8 @@ public function __construct(
3338
/** @var string name of the Argument */
3439
private readonly string $name,
3540
Type|null $type = null,
36-
/** @var string|null the default value for an argument or null if none is provided */
37-
private readonly string|null $default = null,
41+
/** @var Expression|string|null the default value for an argument or null if none is provided */
42+
private Expression|string|null $default = null,
3843
/** @var bool whether the argument passes the parameter by reference instead of by value */
3944
private readonly bool $byReference = false,
4045
/** @var bool Determines if this Argument represents a variadic argument */
@@ -44,6 +49,15 @@ public function __construct(
4449
$type = new Mixed_();
4550
}
4651

52+
if (is_string($this->default)) {
53+
trigger_error(
54+
'Default values for arguments should be of type Expression, support for strings will be '
55+
. 'removed in 7.x',
56+
E_USER_DEPRECATED,
57+
);
58+
$this->default = new Expression($this->default, []);
59+
}
60+
4761
$this->type = $type;
4862
}
4963

@@ -60,8 +74,21 @@ public function getType(): Type|null
6074
return $this->type;
6175
}
6276

63-
public function getDefault(): string|null
77+
public function getDefault(bool $asString = true): Expression|string|null
6478
{
79+
if ($this->default === null) {
80+
return null;
81+
}
82+
83+
if ($asString) {
84+
trigger_error(
85+
'The Default value will become of type Expression by default',
86+
E_USER_DEPRECATED,
87+
);
88+
89+
return (string) $this->default;
90+
}
91+
6592
return $this->default;
6693
}
6794

src/phpDocumentor/Reflection/Php/Constant.php

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
use phpDocumentor\Reflection\Location;
2121
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
2222

23+
use function is_string;
24+
use function trigger_error;
25+
26+
use const E_USER_DEPRECATED;
27+
2328
/**
2429
* Descriptor representing a constant
2530
*
@@ -42,7 +47,7 @@ final class Constant implements Element, MetaDataContainerInterface, AttributeCo
4247
public function __construct(
4348
private readonly Fqsen $fqsen,
4449
private readonly DocBlock|null $docBlock = null,
45-
private readonly string|null $value = null,
50+
private Expression|string|null $value = null,
4651
Location|null $location = null,
4752
Location|null $endLocation = null,
4853
Visibility|null $visibility = null,
@@ -51,13 +56,37 @@ public function __construct(
5156
$this->location = $location ?: new Location(-1);
5257
$this->endLocation = $endLocation ?: new Location(-1);
5358
$this->visibility = $visibility ?: new Visibility(Visibility::PUBLIC_);
59+
60+
if (!is_string($this->value)) {
61+
return;
62+
}
63+
64+
trigger_error(
65+
'Constant values should be of type Expression, support for strings will be '
66+
. 'removed in 6.x',
67+
E_USER_DEPRECATED,
68+
);
69+
$this->value = new Expression($this->value, []);
5470
}
5571

5672
/**
57-
* Returns the value of this constant.
73+
* Returns the expression value for this constant.
5874
*/
59-
public function getValue(): string|null
75+
public function getValue(bool $asString = true): Expression|string|null
6076
{
77+
if ($this->value === null) {
78+
return null;
79+
}
80+
81+
if ($asString) {
82+
trigger_error(
83+
'The expression value will become of type Expression by default',
84+
E_USER_DEPRECATED,
85+
);
86+
87+
return (string) $this->value;
88+
}
89+
6190
return $this->value;
6291
}
6392

src/phpDocumentor/Reflection/Php/EnumCase.php

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
use phpDocumentor\Reflection\Location;
1212
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
1313

14+
use function is_string;
15+
use function trigger_error;
16+
17+
use const E_USER_DEPRECATED;
18+
1419
/**
1520
* Represents a case in an Enum.
1621
*
@@ -30,7 +35,7 @@ public function __construct(
3035
private readonly DocBlock|null $docBlock,
3136
Location|null $location = null,
3237
Location|null $endLocation = null,
33-
private readonly string|null $value = null,
38+
private Expression|string|null $value = null,
3439
) {
3540
if ($location === null) {
3641
$location = new Location(-1);
@@ -42,6 +47,16 @@ public function __construct(
4247

4348
$this->location = $location;
4449
$this->endLocation = $endLocation;
50+
if (!is_string($this->value)) {
51+
return;
52+
}
53+
54+
trigger_error(
55+
'Expression values for enum cases should be of type Expression, support for strings will be '
56+
. 'removed in 7.x',
57+
E_USER_DEPRECATED,
58+
);
59+
$this->value = new Expression($this->value, []);
4560
}
4661

4762
#[Override]
@@ -71,8 +86,24 @@ public function getEndLocation(): Location
7186
return $this->endLocation;
7287
}
7388

74-
public function getValue(): string|null
89+
/**
90+
* Returns the value for this enum case.
91+
*/
92+
public function getValue(bool $asString = true): Expression|string|null
7593
{
94+
if ($this->value === null) {
95+
return null;
96+
}
97+
98+
if ($asString) {
99+
trigger_error(
100+
'The enum case value will become of type Expression by default',
101+
E_USER_DEPRECATED,
102+
);
103+
104+
return (string) $this->value;
105+
}
106+
76107
return $this->value;
77108
}
78109
}

0 commit comments

Comments
 (0)