Skip to content

Commit dcd0363

Browse files
authored
Fix: Add support for new expressions in parameter default values (#154)
* Add resolveExprNew method and tests for new expression handling
1 parent a443152 commit dcd0363

File tree

4 files changed

+150
-0
lines changed

4 files changed

+150
-0
lines changed

src/Resolver/NodeExpressionResolver.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,49 @@ protected function resolveExprFuncCall(Expr\FuncCall $node): mixed
231231
return $reflectedFunction->invoke(...$resolvedArgs);
232232
}
233233

234+
/**
235+
* Resolves new expression by instantiating the class with constructor arguments
236+
*
237+
* @throws \Throwable In case of any errors during class instantiation
238+
*/
239+
protected function resolveExprNew(Expr\New_ $node): object
240+
{
241+
$classToInstantiateNode = $node->class;
242+
243+
// Resolve class name - it can be a Name node or an expression
244+
if ($classToInstantiateNode instanceof Node\Name) {
245+
// Unwrap resolved class name if we have it inside attributes
246+
if ($classToInstantiateNode->hasAttribute('resolvedName')) {
247+
$classToInstantiateNode = $classToInstantiateNode->getAttribute('resolvedName');
248+
}
249+
$className = $classToInstantiateNode->toString();
250+
} else {
251+
// It's an expression, resolve it to get class name
252+
$className = $this->resolve($classToInstantiateNode);
253+
if (!is_string($className)) {
254+
throw new ReflectionException("Unable to resolve class name for instantiation.");
255+
}
256+
}
257+
258+
// Resolve constructor arguments
259+
$resolvedArgs = [];
260+
foreach ($node->args as $argumentNode) {
261+
$value = $this->resolve($argumentNode->value);
262+
// if constructor uses named arguments, then unpack argument name first
263+
if (isset($argumentNode->name)) {
264+
$name = $this->resolve($argumentNode->name);
265+
$resolvedArgs[$name] = $value;
266+
} else {
267+
// otherwise simply add argument to the list
268+
$resolvedArgs[] = $value;
269+
}
270+
}
271+
272+
// Use ReflectionClass to safely instantiate the class
273+
$reflectionClass = new \ReflectionClass($className);
274+
return $reflectionClass->newInstance(...$resolvedArgs);
275+
}
276+
234277
protected function resolveScalarFloat(Float_ $node): float
235278
{
236279
return $node->value;

tests/ReflectionParameterTest.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,44 @@ public static function parametersDataProvider(): \Generator
215215
}
216216
}
217217

218+
/**
219+
* Test that parameters with new expression default values work correctly
220+
*/
221+
public function testParametersWithNewExpressionDefaults(): void
222+
{
223+
$fileName = __DIR__ . '/Stub/FileWithNewExpressionDefaults.php';
224+
$reflectionFile = new ReflectionFile($fileName);
225+
$parsedFileNamespace = $reflectionFile->getFileNamespace('Go\ParserReflection\Stub');
226+
227+
// Test the method from the reported issue
228+
$parsedClass = $parsedFileNamespace->getClass('Go\ParserReflection\Stub\TestClassWithNewExpressionDefaults');
229+
$parsedMethod = $parsedClass->getMethod('deactivateSeries');
230+
$parsedParameter = $parsedMethod->getParameters()[0];
231+
232+
$this->assertTrue($parsedParameter->isDefaultValueAvailable());
233+
$this->assertFalse($parsedParameter->isDefaultValueConstant());
234+
235+
$defaultValue = $parsedParameter->getDefaultValue();
236+
$this->assertInstanceOf(\DateTimeImmutable::class, $defaultValue);
237+
238+
// Test DateTime default
239+
$parsedMethod2 = $parsedClass->getMethod('withDateTime');
240+
$parsedParameter2 = $parsedMethod2->getParameters()[0];
241+
242+
$this->assertTrue($parsedParameter2->isDefaultValueAvailable());
243+
$defaultValue2 = $parsedParameter2->getDefaultValue();
244+
$this->assertInstanceOf(\DateTime::class, $defaultValue2);
245+
$this->assertSame('2023-01-01', $defaultValue2->format('Y-m-d'));
246+
247+
// Test stdClass default
248+
$parsedMethod3 = $parsedClass->getMethod('withStdClass');
249+
$parsedParameter3 = $parsedMethod3->getParameters()[0];
250+
251+
$this->assertTrue($parsedParameter3->isDefaultValueAvailable());
252+
$defaultValue3 = $parsedParameter3->getDefaultValue();
253+
$this->assertInstanceOf(\stdClass::class, $defaultValue3);
254+
}
255+
218256
/**
219257
* @inheritDoc
220258
*/

tests/Resolver/NodeExpressionResolverTest.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,44 @@ public function testResolveConstFetchFromNonExprAsClass(): void
6565
$expressionSolver = new NodeExpressionResolver(NULL);
6666
$expressionSolver->process($expressionNodeTree[0]);
6767
}
68+
69+
/**
70+
* Testing resolving new expression with a simple instantiation
71+
*/
72+
public function testResolveNewExpression(): void
73+
{
74+
$expressionNodeTree = $this->parser->parse("<?php new \\DateTime('2023-01-01');");
75+
$expressionSolver = new NodeExpressionResolver(NULL);
76+
$expressionSolver->process($expressionNodeTree[0]);
77+
78+
$value = $expressionSolver->getValue();
79+
$this->assertInstanceOf(\DateTime::class, $value);
80+
$this->assertSame('2023-01-01', $value->format('Y-m-d'));
81+
}
82+
83+
/**
84+
* Testing resolving new expression without constructor arguments
85+
*/
86+
public function testResolveNewExpressionWithoutArguments(): void
87+
{
88+
$expressionNodeTree = $this->parser->parse("<?php new \\stdClass();");
89+
$expressionSolver = new NodeExpressionResolver(NULL);
90+
$expressionSolver->process($expressionNodeTree[0]);
91+
92+
$value = $expressionSolver->getValue();
93+
$this->assertInstanceOf(\stdClass::class, $value);
94+
}
95+
96+
/**
97+
* Testing resolving new expression with DateTimeImmutable as in the issue
98+
*/
99+
public function testResolveNewExpressionDateTimeImmutable(): void
100+
{
101+
$expressionNodeTree = $this->parser->parse("<?php new \\DateTimeImmutable('today');");
102+
$expressionSolver = new NodeExpressionResolver(NULL);
103+
$expressionSolver->process($expressionNodeTree[0]);
104+
105+
$value = $expressionSolver->getValue();
106+
$this->assertInstanceOf(\DateTimeImmutable::class, $value);
107+
}
68108
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Go\ParserReflection\Stub {
4+
5+
class TestClassWithNewExpressionDefaults
6+
{
7+
/**
8+
* Method with DateTimeImmutable default value as in the reported issue
9+
*/
10+
public function deactivateSeries(\DateTimeImmutable $today = new \DateTimeImmutable('today')): bool
11+
{
12+
return true;
13+
}
14+
15+
/**
16+
* Method with DateTime default value
17+
*/
18+
public function withDateTime(\DateTime $date = new \DateTime('2023-01-01')): void
19+
{
20+
}
21+
22+
/**
23+
* Method with stdClass default value (no constructor args)
24+
*/
25+
public function withStdClass(\stdClass $obj = new \stdClass()): void
26+
{
27+
}
28+
}
29+
}

0 commit comments

Comments
 (0)