Skip to content

Commit 5d8c68c

Browse files
committed
Add support for PHP 8.3 typed class constants
The Constant class now accepts and exposes a Type parameter, following the same pattern used by Property. The ClassConstantIterator reads the type from the AST node, and the ClassConstant factory converts it via the Type helper before passing it to the Constant constructor.
1 parent 4af448a commit 5d8c68c

File tree

8 files changed

+142
-0
lines changed

8 files changed

+142
-0
lines changed

src/phpDocumentor/Reflection/Php/Constant.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use phpDocumentor\Reflection\Fqsen;
2020
use phpDocumentor\Reflection\Location;
2121
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
22+
use phpDocumentor\Reflection\Type;
2223

2324
use function is_string;
2425
use function trigger_error;
@@ -52,6 +53,7 @@ public function __construct(
5253
Location|null $endLocation = null,
5354
Visibility|null $visibility = null,
5455
private readonly bool $final = false,
56+
private readonly Type|null $type = null,
5557
) {
5658
$this->location = $location ?: new Location(-1);
5759
$this->endLocation = $endLocation ?: new Location(-1);
@@ -135,4 +137,9 @@ public function isFinal(): bool
135137
{
136138
return $this->final;
137139
}
140+
141+
public function getType(): Type|null
142+
{
143+
return $this->type;
144+
}
138145
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ protected function doCreate(
9393
new Location($const->getEndLine()),
9494
$this->buildVisibility($const),
9595
$const->isFinal(),
96+
(new Type())->fromPhpParser($const->getType(), $context->getTypeContext()),
9697
);
9798

9899
foreach ($this->reducers as $reducer) {

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717
use Override;
1818
use phpDocumentor\Reflection\Fqsen;
1919
use PhpParser\Comment\Doc;
20+
use PhpParser\Node\ComplexType;
2021
use PhpParser\Node\Expr;
22+
use PhpParser\Node\Identifier;
23+
use PhpParser\Node\Name;
2124
use PhpParser\Node\Stmt\ClassConst;
2225

2326
/**
@@ -123,6 +126,14 @@ public function isFinal(): bool
123126
return $this->classConstants->isFinal();
124127
}
125128

129+
/**
130+
* Gets the type of the constant.
131+
*/
132+
public function getType(): Identifier|Name|ComplexType|null
133+
{
134+
return $this->classConstants->type;
135+
}
136+
126137
/** @link http://php.net/manual/en/iterator.current.php */
127138
#[Override]
128139
public function current(): self
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace integration;
6+
7+
use phpDocumentor\Reflection\File\LocalFile;
8+
use phpDocumentor\Reflection\Php\ProjectFactory;
9+
use phpDocumentor\Reflection\Types\Compound;
10+
use phpDocumentor\Reflection\Types\Integer;
11+
use phpDocumentor\Reflection\Types\Nullable;
12+
use phpDocumentor\Reflection\Types\String_;
13+
use PHPUnit\Framework\Attributes\CoversNothing;
14+
use PHPUnit\Framework\TestCase;
15+
16+
#[CoversNothing]
17+
final class TypedConstantTest extends TestCase
18+
{
19+
public function testTypedClassConstantsHaveType(): void
20+
{
21+
$file = __DIR__ . '/data/PHP83/TypedConstants.php';
22+
$projectFactory = ProjectFactory::createInstance();
23+
$project = $projectFactory->create('My project', [new LocalFile($file)]);
24+
25+
$class = $project->getFiles()[$file]->getClasses()['\PHP83\TypedConstants'];
26+
$constants = $class->getConstants();
27+
28+
$this->assertEquals(new String_(), $constants['\PHP83\TypedConstants::NAME']->getType());
29+
$this->assertEquals(new Integer(), $constants['\PHP83\TypedConstants::COUNT']->getType());
30+
$this->assertEquals(new Compound([new String_(), new Integer()]), $constants['\PHP83\TypedConstants::UNION']->getType());
31+
$this->assertEquals(new Nullable(new String_()), $constants['\PHP83\TypedConstants::NULLABLE']->getType());
32+
$this->assertNull($constants['\PHP83\TypedConstants::UNTYPED']->getType());
33+
}
34+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PHP83;
6+
7+
class TypedConstants
8+
{
9+
const string NAME = 'typed';
10+
const int COUNT = 42;
11+
const string|int UNION = 'either';
12+
const ?string NULLABLE = null;
13+
const UNTYPED = 'no type';
14+
}

tests/unit/phpDocumentor/Reflection/Php/ConstantTest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use phpDocumentor\Reflection\Fqsen;
1818
use phpDocumentor\Reflection\Location;
1919
use phpDocumentor\Reflection\Metadata\MetaDataContainer as MetaDataContainerInterface;
20+
use phpDocumentor\Reflection\Types\String_;
2021
use PHPUnit\Framework\Attributes\CoversClass;
2122
use PHPUnit\Framework\Attributes\UsesClass;
2223

@@ -76,6 +77,28 @@ public function testGetVisibility(): void
7677
self::assertEquals(new Visibility(Visibility::PUBLIC_), $this->fixture->getVisibility());
7778
}
7879

80+
public function testGetTypeReturnsNullByDefault(): void
81+
{
82+
self::assertNull($this->fixture->getType());
83+
}
84+
85+
public function testGetTypeReturnsTypeWhenProvided(): void
86+
{
87+
$type = new String_();
88+
$fixture = new Constant(
89+
$this->fqsen,
90+
$this->docBlock,
91+
new Expression($this->value),
92+
null,
93+
null,
94+
null,
95+
false,
96+
$type,
97+
);
98+
99+
self::assertSame($type, $fixture->getType());
100+
}
101+
79102
public function testLineAndColumnNumberIsReturnedWhenALocationIsProvided(): void
80103
{
81104
$fixture = new Constant($this->fqsen, $this->docBlock, null, new Location(100, 20), new Location(101, 20));

tests/unit/phpDocumentor/Reflection/Php/Factory/ClassConstantIteratorTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
use PhpParser\Comment\Doc;
2020
use PhpParser\Node\Const_;
2121
use PhpParser\Node\Expr\Variable;
22+
use PhpParser\Node\Identifier;
23+
use PhpParser\Node\Scalar\String_;
2224
use PhpParser\Node\Stmt\ClassConst;
2325
use PHPUnit\Framework\Attributes\CoversClass;
2426

@@ -78,6 +80,31 @@ public function testGetDocCommentPropFirst(): void
7880
$this->assertEquals('test', $fixture->getDocComment()->getText());
7981
}
8082

83+
public function testGetTypeReturnsNullWhenUntyped(): void
84+
{
85+
$const = new Const_('\Space\MyClass::MY_CONST1', new String_('a'));
86+
$const->setAttribute('fqsen', new Fqsen((string) $const->name));
87+
88+
$classConstantNode = new ClassConst([$const]);
89+
90+
$fixture = new ClassConstantIterator($classConstantNode);
91+
92+
$this->assertNull($fixture->getType());
93+
}
94+
95+
public function testGetTypeReturnsTypeWhenTyped(): void
96+
{
97+
$const = new Const_('\Space\MyClass::MY_CONST1', new String_('a'));
98+
$const->setAttribute('fqsen', new Fqsen((string) $const->name));
99+
100+
$classConstantNode = new ClassConst([$const], 0, [], [], new Identifier('string'));
101+
102+
$fixture = new ClassConstantIterator($classConstantNode);
103+
104+
$this->assertInstanceOf(Identifier::class, $fixture->getType());
105+
$this->assertEquals('string', $fixture->getType()->toString());
106+
}
107+
81108
public function testGetDocComment(): void
82109
{
83110
$const = m::mock(Const_::class);

tests/unit/phpDocumentor/Reflection/Php/Factory/ClassConstantTest.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@
2424
use phpDocumentor\Reflection\Php\ProjectFactoryStrategies;
2525
use phpDocumentor\Reflection\Php\Trait_ as TraitElement;
2626
use phpDocumentor\Reflection\Types\Null_;
27+
use phpDocumentor\Reflection\Types\String_ as StringType;
2728
use PhpParser\Comment\Doc;
2829
use PhpParser\Node\Const_;
30+
use PhpParser\Node\Identifier;
2931
use PhpParser\Node\Scalar\String_;
3032
use PhpParser\Node\Stmt\Class_ as ClassNode;
3133
use PhpParser\Node\Stmt\ClassConst;
@@ -138,6 +140,29 @@ public function testCreateForEnum(): void
138140
self::assertInstanceOf(ConstantDescriptor::class, current($result->getConstants()));
139141
}
140142

143+
public function testCreateWithTypedConstant(): void
144+
{
145+
$const = new Const_('\Space\MyClass::MY_CONST1', new String_('a'));
146+
$const->setAttribute('fqsen', new Fqsen((string) $const->name));
147+
$constantStub = new ClassConst([$const], ClassNode::MODIFIER_PUBLIC, [], [], new Identifier('string'));
148+
149+
$class = $this->performCreate($constantStub);
150+
151+
$constant = current($class->getConstants());
152+
$this->assertInstanceOf(ConstantDescriptor::class, $constant);
153+
$this->assertEquals(new StringType(), $constant->getType());
154+
}
155+
156+
public function testCreateWithUntypedConstantHasNullType(): void
157+
{
158+
$constantStub = $this->buildConstantIteratorStub();
159+
160+
$class = $this->performCreate($constantStub);
161+
162+
$constant = current($class->getConstants());
163+
$this->assertNull($constant->getType());
164+
}
165+
141166
public function testCreateWithDocBlock(): void
142167
{
143168
$doc = new Doc('text');

0 commit comments

Comments
 (0)