diff --git a/src/Resolver/NodeExpressionResolver.php b/src/Resolver/NodeExpressionResolver.php index 93df977..c98e657 100644 --- a/src/Resolver/NodeExpressionResolver.php +++ b/src/Resolver/NodeExpressionResolver.php @@ -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; diff --git a/tests/ReflectionParameterTest.php b/tests/ReflectionParameterTest.php index 0aa60ca..0651941 100644 --- a/tests/ReflectionParameterTest.php +++ b/tests/ReflectionParameterTest.php @@ -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 */ diff --git a/tests/Resolver/NodeExpressionResolverTest.php b/tests/Resolver/NodeExpressionResolverTest.php index f4969d2..d89d66c 100644 --- a/tests/Resolver/NodeExpressionResolverTest.php +++ b/tests/Resolver/NodeExpressionResolverTest.php @@ -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("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("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("process($expressionNodeTree[0]); + + $value = $expressionSolver->getValue(); + $this->assertInstanceOf(\DateTimeImmutable::class, $value); + } } diff --git a/tests/Stub/FileWithNewExpressionDefaults.php b/tests/Stub/FileWithNewExpressionDefaults.php new file mode 100644 index 0000000..62f7f3f --- /dev/null +++ b/tests/Stub/FileWithNewExpressionDefaults.php @@ -0,0 +1,29 @@ +