Skip to content

Commit cbd2862

Browse files
[TASK] Refactor and improve GeneralUtilityDynamicReturnTypeExtension
1 parent ee53f82 commit cbd2862

6 files changed

+120
-15
lines changed

phpstan.neon

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ parameters:
66

77
paths:
88
- %currentWorkingDirectory%/src/
9-
- %currentWorkingDirectory%/tests/
9+
- %currentWorkingDirectory%/tests/Unit/

src/Type/GeneralUtilityDynamicReturnTypeExtension.php

+68-14
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,19 @@
44

55
namespace FriendsOfTYPO3\PHPStan\TYPO3\Type;
66

7-
use PhpParser\Node\Arg;
8-
use PhpParser\Node\Expr;
7+
use PhpParser\Node\Arg as ArgumentNode;
98
use PhpParser\Node\Expr\ClassConstFetch;
109
use PhpParser\Node\Expr\StaticCall;
1110
use PhpParser\Node\Name;
11+
use PhpParser\Node\Scalar\String_ as StringNode;
1212
use PHPStan\Analyser\Scope;
13+
use PHPStan\Reflection\ClassReflection;
1314
use PHPStan\Reflection\MethodReflection;
1415
use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
1516
use PHPStan\Type\ObjectType;
1617
use PHPStan\Type\ObjectWithoutClassType;
17-
use PHPStan\Type\Type;
18+
use PHPStan\Type\StaticType;
19+
use PHPStan\Type\Type as TypeInterface;
1820
use TYPO3\CMS\Core\Utility\GeneralUtility;
1921

2022
class GeneralUtilityDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension
@@ -29,32 +31,84 @@ public function isStaticMethodSupported(MethodReflection $methodReflection): boo
2931
return $methodReflection->getName() === 'makeInstance';
3032
}
3133

32-
public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): Type
34+
public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): TypeInterface
3335
{
34-
if (empty($methodCall->args)) {
36+
try {
37+
$classNameArgument = $this->fetchClassNameArgument($methodCall);
38+
$classNameArgumentValueExpression = $classNameArgument->value;
39+
40+
switch (true) {
41+
case $classNameArgumentValueExpression instanceof StringNode:
42+
/*
43+
* Examples:
44+
*
45+
* - GeneralUtility::makeInstance('foo')
46+
* - GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\DataHandling\\DataHandler')
47+
*/
48+
return $this->createObjectTypeFromString($classNameArgumentValueExpression);
49+
case $classNameArgumentValueExpression instanceof ClassConstFetch:
50+
/*
51+
* Examples:
52+
*
53+
* - GeneralUtility::makeInstance(TYPO3\CMS\Core\DataHandling\DataHandler::class)
54+
* - GeneralUtility::makeInstance(self::class)
55+
* - GeneralUtility::makeInstance(static::class)
56+
*/
57+
return $this->createObjectTypeFromClassConstFetch($classNameArgumentValueExpression, $scope->getClassReflection());
58+
default:
59+
throw new \InvalidArgumentException(
60+
'Argument $className is neither a string nor a class constant',
61+
1584879239
62+
);
63+
}
64+
} catch (\Throwable $exception) {
3565
return new ObjectWithoutClassType();
3666
}
67+
}
3768

38-
/** @var Arg $argument */
39-
$argument = $methodCall->args[0];
69+
private function fetchClassNameArgument(StaticCall $methodCall): ArgumentNode
70+
{
71+
if (empty($methodCall->args)) {
72+
/*
73+
* This usually does not happen as calling GeneralUtility::makeInstance() without the mandatory argument
74+
* $className results in a syntax error.
75+
*/
76+
throw new \LogicException('Method makeInstance is called without arguments.', 1584878263);
77+
}
4078

41-
/** @var Expr $argumentValue */
42-
$argumentValue = $argument->value;
79+
return $methodCall->args[0];
80+
}
4381

44-
if (!$argumentValue instanceof ClassConstFetch) {
45-
return new ObjectWithoutClassType();
82+
private function createObjectTypeFromString(StringNode $string): TypeInterface
83+
{
84+
$className = $string->value;
85+
86+
if (!class_exists($className)) {
87+
throw new \LogicException('makeInstance has been called with non class name string', 1584879581);
4688
}
47-
/** @var ClassConstFetch $argumentValue */
4889

49-
$class = $argumentValue->class;
90+
return new ObjectType($className);
91+
}
5092

93+
private function createObjectTypeFromClassConstFetch(ClassConstFetch $expression, ?ClassReflection $classReflection): TypeInterface
94+
{
95+
$class = $expression->class;
5196
if (!$class instanceof Name) {
52-
return new ObjectWithoutClassType();
97+
throw new \LogicException('', 1584878823);
5398
}
5499
/** @var Name $class */
55100

56101
$className = $class->toString();
57102

103+
if ($className === 'self' && $classReflection !== null) {
104+
return new ObjectType($classReflection->getName());
105+
}
106+
107+
if ($className === 'static' && $classReflection !== null) {
108+
$callingClass = $classReflection->getName();
109+
return new StaticType($callingClass);
110+
}
111+
58112
return new ObjectType($className);
59113
}
60114
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace FriendsOfTYPO3\PHPStan\TYPO3\Tests\Fixtures;
6+
7+
use TYPO3\CMS\Core\DataHandling\DataHandler;
8+
use TYPO3\CMS\Core\Utility\GeneralUtility;
9+
10+
$object = GeneralUtility::makeInstance(DataHandler::class);
11+
$object->storeLogMessages = true;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace FriendsOfTYPO3\PHPStan\TYPO3\Tests\Fixtures;
6+
7+
use TYPO3\CMS\Core\Utility\GeneralUtility;
8+
9+
$object = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\DataHandling\\DataHandler');
10+
$object->storeLogMessages = true;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace FriendsOfTYPO3\PHPStan\TYPO3\Tests\Fixtures;
6+
7+
use TYPO3\CMS\Core\Utility\GeneralUtility;
8+
9+
class MakeInstanceCallWithSelf
10+
{
11+
public function create(): self
12+
{
13+
return GeneralUtility::makeInstance(self::class);
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace FriendsOfTYPO3\PHPStan\TYPO3\Tests\Fixtures;
6+
7+
use TYPO3\CMS\Core\Utility\GeneralUtility;
8+
9+
class MakeInstanceCallWithStatic
10+
{
11+
public function create(): self
12+
{
13+
return GeneralUtility::makeInstance(static::class);
14+
}
15+
}

0 commit comments

Comments
 (0)