Skip to content

Commit 4b9b4b5

Browse files
committed
Expand docs and fix linting and analysis issues
1 parent 52e5c99 commit 4b9b4b5

File tree

13 files changed

+170
-45
lines changed

13 files changed

+170
-45
lines changed

docs/expressions.rst

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,37 @@ $isString as being false, it will return an Expression object; which, when cast
2424

2525
This will allow consumers to be able to extract types and links to elements from expressions. This allows consumers to,
2626
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.

phpstan.neon

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,6 @@ parameters:
1717
- '#Parameter \#1 \$fqsen of class phpDocumentor\\Reflection\\Php\\(.*) constructor expects phpDocumentor\\Reflection\\Fqsen, mixed given\.#'
1818
- '#Parameter \#1 \$fqsen of method phpDocumentor\\Reflection\\Php\\File::addNamespace\(\) expects phpDocumentor\\Reflection\\Fqsen, mixed given\.#'
1919
#
20-
# there is one test case that prevents changing PropertyIterator::getDefault() to just return Expr (this is set in PhpParser)
21-
# src/phpDocumentor/Reflection/Php/Factory/Property.php
22-
- '#Parameter \#1 \$node of method PhpParser\\PrettyPrinterAbstract::prettyPrintExpr\(\) expects PhpParser\\Node\\Expr, PhpParser\\Node\\Expr\|string given\.#'
23-
#
2420
# Type hint in php-parser is incorrect.
2521
- '#Cannot cast PhpParser\\Node\\Expr\|string to string.#'
2622

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/Php/Constant.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ final class Constant implements Element, MetaDataContainerInterface
4848

4949
/**
5050
* Initializes the object.
51+
*
52+
* @param Expression|string|null $value
5153
*/
5254
public function __construct(
5355
Fqsen $fqsen,

src/phpDocumentor/Reflection/Php/Expression.php

Lines changed: 102 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,48 +5,143 @@
55
namespace phpDocumentor\Reflection\Php;
66

77
use phpDocumentor\Reflection\Fqsen;
8+
use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter;
89
use phpDocumentor\Reflection\Type;
10+
use Webmozart\Assert\Assert;
911

1012
use function array_keys;
11-
use function array_map;
13+
use function md5;
1214
use function str_replace;
1315

16+
/**
17+
* Represents an expression with a define statement, constant, property, enum case and any other location.
18+
*
19+
* Certain expressions contain useful references to other elements or types. Examples of these are:
20+
*
21+
* - Define statements that use an expression to refer to a class or function
22+
* - Properties whose default value refers to a constant
23+
* - Arguments whose default value initialize an object
24+
* - Enum Cases that refer to a function or constant
25+
*
26+
* This class represents every location where an expression is used and contains 2 pieces of information:
27+
*
28+
* - The expression string containing placeholders linking to useful information
29+
* - An array of 'parts' whose keys equal the placeholders in the expression string and whose values is the extracted
30+
* information, such as an {@see FQSEN} or {@see Type}.
31+
*
32+
* In a way, the expression string is similar in nature to a URI Template (see links) where you have a string containing
33+
* variables that can be replaced. These variables are delimited by `{{` and `}}`, and are build up of the prefix PHPDOC
34+
* and then an MD5 hash of the name of the extracted information.
35+
*
36+
* It is not necessary for a consumer to interpret the information when they do not need it, a {@see self::__toString()}
37+
* magic method is provided that will replace the placeholders with the `toString()` output of each part.
38+
*
39+
* @link https://github.com/php/php-langspec/blob/master/spec/10-expressions.md
40+
* for the definition of expressions in PHP.
41+
* @link https://www.rfc-editor.org/rfc/rfc6570 for more information on URI Templates.
42+
* @see ExpressionPrinter how an expression coming from PHP-Parser is transformed into an expression.
43+
*/
1444
final class Expression
1545
{
46+
/** @var string The expression string containing placeholders for any extracted Types or FQSENs. */
1647
private string $expression;
1748

18-
/** @var array<string, Fqsen|Type> */
49+
/**
50+
* The collection of placeholders with the value that their holding.
51+
*
52+
* In the expression string there can be several placeholders, this array contains a placeholder => value pair
53+
* that can be used by consumers to map the data to another formatting, adding links for example, and then render
54+
* the expression.
55+
*
56+
* @var array<string, Fqsen|Type>
57+
*/
1958
private array $parts;
2059

60+
/**
61+
* Returns the recommended placeholder string format given a name.
62+
*
63+
* Consumers can use their own formats when needed, the placeholders are all keys in the {@see self::$parts} array
64+
* and not interpreted by this class. However, to prevent collisions it is recommended to use this method to
65+
* generate a placeholder.
66+
*
67+
* @param string $name a string identifying the element for which the placeholder is generated.
68+
*/
69+
public static function generatePlaceholder(string $name): string
70+
{
71+
Assert::notEmpty($name);
72+
73+
return '{{ PHPDOC' . md5($name) . ' }}';
74+
}
75+
2176
/**
2277
* @param array<string, Fqsen|Type> $parts
2378
*/
2479
public function __construct(string $expression, array $parts)
2580
{
81+
Assert::notEmpty($expression);
82+
Assert::allIsInstanceOfAny($parts, [Fqsen::class, Type::class]);
83+
2684
$this->expression = $expression;
2785
$this->parts = $parts;
2886
}
2987

88+
/**
89+
* The raw expression string containing placeholders for any extracted Types or FQSENs.
90+
*
91+
* @see self::render() to render a human-readable expression and to replace some parts with custom values.
92+
* @see self::__toString() to render a human-readable expression with the previously extracted parts.
93+
*/
3094
public function getExpression(): string
3195
{
3296
return $this->expression;
3397
}
3498

3599
/**
100+
* A list of extracted parts for which placeholders exist in the expression string.
101+
*
102+
* The returned array will have the placeholders of the expression string as keys, and the related FQSEN or Type as
103+
* value. This can be used as a basis for doing your own transformations to {@see self::render()} the expression
104+
* in a custom way; or to extract type information from an expression and use that elsewhere in your application.
105+
*
106+
* @see ExpressionPrinter to transform a PHP-Parser expression into an expression string and list of parts.
107+
*
36108
* @return array<string, Fqsen|Type>
37109
*/
38110
public function getParts(): array
39111
{
40112
return $this->parts;
41113
}
42114

43-
public function __toString(): string
115+
/**
116+
* Renders the expression as a string and replaces all placeholders with either a provided value, or the
117+
* stringified value from the parts in this expression.
118+
*
119+
* The keys of the replacement parts should match those of {@see self::getParts()}, any unrecognized key is not
120+
* handled.
121+
*
122+
* @param array<string, string> $replacementParts
123+
*/
124+
public function render(array $replacementParts = []): string
44125
{
45-
$valuesAsStrings = array_map(
46-
static fn (object $part): string => (string) $part,
47-
$this->parts
48-
);
126+
Assert::allStringNotEmpty($replacementParts);
127+
128+
$valuesAsStrings = [];
129+
foreach ($this->parts as $placeholder => $part) {
130+
$valuesAsStrings[$placeholder] = $replacementParts[$placeholder] ?? (string) $part;
131+
}
49132

50133
return str_replace(array_keys($this->parts), $valuesAsStrings, $this->expression);
51134
}
135+
136+
/**
137+
* Returns a rendered version of the expression string where all placeholders are replaced by the stringified
138+
* versions of the included parts.
139+
*
140+
* @see self::$parts for the list of parts used in rendering
141+
* @see self::render() to influence rendering of the expression.
142+
*/
143+
public function __toString(): string
144+
{
145+
return $this->render();
146+
}
52147
}

src/phpDocumentor/Reflection/Php/Expression/ExpressionPrinter.php

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
namespace phpDocumentor\Reflection\Php\Expression;
66

77
use phpDocumentor\Reflection\Fqsen;
8+
use phpDocumentor\Reflection\Php\Expression;
9+
use phpDocumentor\Reflection\Type;
810
use PhpParser\Node\Name;
911
use PhpParser\PrettyPrinter\Standard;
1012

11-
use function md5;
12-
1313
final class ExpressionPrinter extends Standard
1414
{
15-
/** @var array<Fqsen> */
15+
/** @var array<string, Fqsen|Type> */
1616
private array $parts = [];
1717

1818
protected function resetState(): void
@@ -25,16 +25,24 @@ protected function resetState(): void
2525
protected function pName(Name $node): string
2626
{
2727
$renderedName = parent::pName($node);
28-
$code = md5($renderedName);
28+
$placeholder = Expression::generatePlaceholder($renderedName);
29+
$this->parts[$placeholder] = new Fqsen('\\' . $renderedName);
2930

30-
$placeholder = '{{ PHPDOC' . $code . ' }}';
31-
$this->parts[$placeholder] = new Fqsen('\\' . $node);
31+
return $placeholder;
32+
}
33+
34+
// phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
35+
protected function pName_FullyQualified(Name\FullyQualified $node): string
36+
{
37+
$renderedName = parent::pName_FullyQualified($node);
38+
$placeholder = Expression::generatePlaceholder($renderedName);
39+
$this->parts[$placeholder] = new Fqsen($renderedName);
3240

3341
return $placeholder;
3442
}
3543

3644
/**
37-
* @return array<Fqsen>
45+
* @return array<string, Fqsen|Type>
3846
*/
3947
public function getParts(): array
4048
{

src/phpDocumentor/Reflection/Php/Factory/ClassConstant.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,9 @@ protected function doCreate(
9292
}
9393
}
9494

95-
private function determineValue(ClassConstantIterator $value): ?Expression
95+
private function determineValue(ClassConstantIterator $value): Expression
9696
{
97-
$expression = $value->getValue() !== null ? $this->valueConverter->prettyPrintExpr($value->getValue()) : null;
98-
if ($expression === null) {
99-
return null;
100-
}
101-
97+
$expression = $this->valueConverter->prettyPrintExpr($value->getValue());
10298
if ($this->valueConverter instanceof ExpressionPrinter) {
10399
$expression = new Expression($expression, $this->valueConverter->getParts());
104100
}

src/phpDocumentor/Reflection/Php/Factory/Define.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,7 @@ private function determineValue(?Arg $value): ?ValueExpression
128128
return null;
129129
}
130130

131-
$expression = $value->value !== null ? $this->valueConverter->prettyPrintExpr($value->value) : null;
132-
if ($expression === null) {
133-
return null;
134-
}
135-
131+
$expression = $this->valueConverter->prettyPrintExpr($value->value);
136132
if ($this->valueConverter instanceof ExpressionPrinter) {
137133
$expression = new ValueExpression($expression, $this->valueConverter->getParts());
138134
}

src/phpDocumentor/Reflection/Php/Factory/EnumCase.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use phpDocumentor\Reflection\Location;
99
use phpDocumentor\Reflection\Php\Enum_ as EnumElement;
1010
use phpDocumentor\Reflection\Php\EnumCase as EnumCaseElement;
11-
use phpDocumentor\Reflection\Php\Expression;
1211
use phpDocumentor\Reflection\Php\Expression as ValueExpression;
1312
use phpDocumentor\Reflection\Php\Expression\ExpressionPrinter;
1413
use phpDocumentor\Reflection\Php\StrategyContainer;

src/phpDocumentor/Reflection/Php/Factory/GlobalConstant.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,9 @@ protected function doCreate(
8282
}
8383
}
8484

85-
private function determineValue(GlobalConstantIterator $value): ?Expression
85+
private function determineValue(GlobalConstantIterator $value): Expression
8686
{
87-
$expression = $value->getValue() !== null ? $this->valueConverter->prettyPrintExpr($value->getValue()) : null;
88-
if ($expression === null) {
89-
return null;
90-
}
91-
87+
$expression = $this->valueConverter->prettyPrintExpr($value->getValue());
9288
if ($this->valueConverter instanceof ExpressionPrinter) {
9389
$expression = new Expression($expression, $this->valueConverter->getParts());
9490
}

0 commit comments

Comments
 (0)