Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions src/Resolver/NodeExpressionResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,49 @@ protected function resolveExprFuncCall(Expr\FuncCall $node): mixed
return $reflectedFunction->invoke(...$resolvedArgs);
}

/**
* Resolves new expression by instantiating the class with constructor arguments
*
* @throws \Throwable In case of any errors during class instantiation
*/
protected function resolveExprNew(Expr\New_ $node): object
{
$classToInstantiateNode = $node->class;

// Resolve class name - it can be a Name node or an expression
if ($classToInstantiateNode instanceof Node\Name) {
// Unwrap resolved class name if we have it inside attributes
if ($classToInstantiateNode->hasAttribute('resolvedName')) {
$classToInstantiateNode = $classToInstantiateNode->getAttribute('resolvedName');
}
$className = $classToInstantiateNode->toString();
} else {
// It's an expression, resolve it to get class name
$className = $this->resolve($classToInstantiateNode);
if (!is_string($className)) {
throw new ReflectionException("Unable to resolve class name for instantiation.");
}
}

// Resolve constructor arguments
$resolvedArgs = [];
foreach ($node->args as $argumentNode) {
$value = $this->resolve($argumentNode->value);
// if constructor uses named arguments, then unpack argument name first
if (isset($argumentNode->name)) {
$name = $this->resolve($argumentNode->name);
$resolvedArgs[$name] = $value;
} else {
// otherwise simply add argument to the list
$resolvedArgs[] = $value;
}
}

// Use ReflectionClass to safely instantiate the class
$reflectionClass = new \ReflectionClass($className);
return $reflectionClass->newInstance(...$resolvedArgs);
}

protected function resolveScalarFloat(Float_ $node): float
{
return $node->value;
Expand Down
38 changes: 38 additions & 0 deletions tests/ReflectionParameterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,44 @@ public static function parametersDataProvider(): \Generator
}
}

/**
* Test that parameters with new expression default values work correctly
*/
public function testParametersWithNewExpressionDefaults(): void
{
$fileName = __DIR__ . '/Stub/FileWithNewExpressionDefaults.php';
$reflectionFile = new ReflectionFile($fileName);
$parsedFileNamespace = $reflectionFile->getFileNamespace('Go\ParserReflection\Stub');

// Test the method from the reported issue
$parsedClass = $parsedFileNamespace->getClass('Go\ParserReflection\Stub\TestClassWithNewExpressionDefaults');
$parsedMethod = $parsedClass->getMethod('deactivateSeries');
$parsedParameter = $parsedMethod->getParameters()[0];

$this->assertTrue($parsedParameter->isDefaultValueAvailable());
$this->assertFalse($parsedParameter->isDefaultValueConstant());

$defaultValue = $parsedParameter->getDefaultValue();
$this->assertInstanceOf(\DateTimeImmutable::class, $defaultValue);

// Test DateTime default
$parsedMethod2 = $parsedClass->getMethod('withDateTime');
$parsedParameter2 = $parsedMethod2->getParameters()[0];

$this->assertTrue($parsedParameter2->isDefaultValueAvailable());
$defaultValue2 = $parsedParameter2->getDefaultValue();
$this->assertInstanceOf(\DateTime::class, $defaultValue2);
$this->assertSame('2023-01-01', $defaultValue2->format('Y-m-d'));

// Test stdClass default
$parsedMethod3 = $parsedClass->getMethod('withStdClass');
$parsedParameter3 = $parsedMethod3->getParameters()[0];

$this->assertTrue($parsedParameter3->isDefaultValueAvailable());
$defaultValue3 = $parsedParameter3->getDefaultValue();
$this->assertInstanceOf(\stdClass::class, $defaultValue3);
}

/**
* @inheritDoc
*/
Expand Down
40 changes: 40 additions & 0 deletions tests/Resolver/NodeExpressionResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,44 @@ public function testResolveConstFetchFromNonExprAsClass(): void
$expressionSolver = new NodeExpressionResolver(NULL);
$expressionSolver->process($expressionNodeTree[0]);
}

/**
* Testing resolving new expression with a simple instantiation
*/
public function testResolveNewExpression(): void
{
$expressionNodeTree = $this->parser->parse("<?php new \\DateTime('2023-01-01');");
$expressionSolver = new NodeExpressionResolver(NULL);
$expressionSolver->process($expressionNodeTree[0]);

$value = $expressionSolver->getValue();
$this->assertInstanceOf(\DateTime::class, $value);
$this->assertSame('2023-01-01', $value->format('Y-m-d'));
}

/**
* Testing resolving new expression without constructor arguments
*/
public function testResolveNewExpressionWithoutArguments(): void
{
$expressionNodeTree = $this->parser->parse("<?php new \\stdClass();");
$expressionSolver = new NodeExpressionResolver(NULL);
$expressionSolver->process($expressionNodeTree[0]);

$value = $expressionSolver->getValue();
$this->assertInstanceOf(\stdClass::class, $value);
}

/**
* Testing resolving new expression with DateTimeImmutable as in the issue
*/
public function testResolveNewExpressionDateTimeImmutable(): void
{
$expressionNodeTree = $this->parser->parse("<?php new \\DateTimeImmutable('today');");
$expressionSolver = new NodeExpressionResolver(NULL);
$expressionSolver->process($expressionNodeTree[0]);

$value = $expressionSolver->getValue();
$this->assertInstanceOf(\DateTimeImmutable::class, $value);
}
}
29 changes: 29 additions & 0 deletions tests/Stub/FileWithNewExpressionDefaults.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace Go\ParserReflection\Stub {

class TestClassWithNewExpressionDefaults
{
/**
* Method with DateTimeImmutable default value as in the reported issue
*/
public function deactivateSeries(\DateTimeImmutable $today = new \DateTimeImmutable('today')): bool
{
return true;
}

/**
* Method with DateTime default value
*/
public function withDateTime(\DateTime $date = new \DateTime('2023-01-01')): void
{
}

/**
* Method with stdClass default value (no constructor args)
*/
public function withStdClass(\stdClass $obj = new \stdClass()): void
{
}
}
}
Loading