Skip to content

Commit a515864

Browse files
[FEATURE] Add ObjectManagerGetDynamicReturnTypeExtension
1 parent ed74ecc commit a515864

7 files changed

+204
-0
lines changed

extension.neon

+8
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,11 @@ services:
77
class: FriendsOfTYPO3\PHPStan\TYPO3\Type\ContextGetAspectDynamicReturnTypeExtension
88
tags:
99
- phpstan.broker.dynamicMethodReturnTypeExtension
10+
-
11+
class: FriendsOfTYPO3\PHPStan\TYPO3\Type\ObjectManagerGetDynamicReturnTypeExtension
12+
tags:
13+
- phpstan.broker.dynamicMethodReturnTypeExtension
14+
-
15+
class: FriendsOfTYPO3\PHPStan\TYPO3\Type\ObjectManagerInterfaceGetDynamicReturnTypeExtension
16+
tags:
17+
- phpstan.broker.dynamicMethodReturnTypeExtension
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\Type;
6+
7+
use TYPO3\CMS\Extbase\Object\ObjectManager;
8+
9+
class ObjectManagerGetDynamicReturnTypeExtension extends ObjectManagerInterfaceGetDynamicReturnTypeExtension
10+
{
11+
public function getClass(): string
12+
{
13+
return ObjectManager::class;
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace FriendsOfTYPO3\PHPStan\TYPO3\Type;
6+
7+
use PhpParser\Node\Arg as ArgumentNode;
8+
use PhpParser\Node\Expr\ClassConstFetch;
9+
use PhpParser\Node\Expr\MethodCall;
10+
use PhpParser\Node\Name;
11+
use PhpParser\Node\Scalar\String_ as StringNode;
12+
use PHPStan\Analyser\Scope;
13+
use PHPStan\Reflection\ClassReflection;
14+
use PHPStan\Reflection\MethodReflection;
15+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
16+
use PHPStan\Type\ObjectType;
17+
use PHPStan\Type\ObjectWithoutClassType;
18+
use PHPStan\Type\StaticType;
19+
use PHPStan\Type\Type as TypeInterface;
20+
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
21+
22+
class ObjectManagerInterfaceGetDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
23+
{
24+
public function getClass(): string
25+
{
26+
return ObjectManagerInterface::class;
27+
}
28+
29+
public function isMethodSupported(MethodReflection $methodReflection): bool
30+
{
31+
return $methodReflection->getName() === 'get';
32+
}
33+
34+
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): TypeInterface
35+
{
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) {
65+
return new ObjectWithoutClassType();
66+
}
67+
}
68+
69+
private function fetchClassNameArgument(MethodCall $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.', 1584881724);
77+
}
78+
79+
return $methodCall->args[0];
80+
}
81+
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', 1584881731);
88+
}
89+
90+
return new ObjectType($className);
91+
}
92+
93+
private function createObjectTypeFromClassConstFetch(ClassConstFetch $expression, ?ClassReflection $classReflection): TypeInterface
94+
{
95+
$class = $expression->class;
96+
if (!$class instanceof Name) {
97+
throw new \LogicException('', 1584881734);
98+
}
99+
/** @var Name $class */
100+
101+
$className = $class->toString();
102+
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+
112+
return new ObjectType($className);
113+
}
114+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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+
use TYPO3\CMS\Extbase\Object\ObjectManager;
10+
11+
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
12+
$object = $objectManager->get(DataHandler::class);
13+
$object->storeLogMessages = true;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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+
use TYPO3\CMS\Extbase\Object\ObjectManager;
9+
10+
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
11+
$object = $objectManager->get('TYPO3\\CMS\\Core\\DataHandling\\DataHandler');
12+
$object->storeLogMessages = true;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace FriendsOfTYPO3\PHPStan\TYPO3\Tests\Fixtures;
6+
7+
use TYPO3\CMS\Extbase\Object\ObjectManager;
8+
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
9+
10+
class ObjectManagerGetCallWithSelf
11+
{
12+
public function foo(ObjectManagerInterface $objectManager): self
13+
{
14+
return $objectManager->get(self::class);
15+
}
16+
17+
public function bar(ObjectManager $objectManager): self
18+
{
19+
return $objectManager->get(self::class);
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace FriendsOfTYPO3\PHPStan\TYPO3\Tests\Fixtures;
6+
7+
use TYPO3\CMS\Extbase\Object\ObjectManager;
8+
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
9+
10+
class ObjectManagerGetCallWithStatic
11+
{
12+
public function foo(ObjectManagerInterface $objectManager): self
13+
{
14+
return $objectManager->get(static::class);
15+
}
16+
17+
public function bar(ObjectManager $objectManager): self
18+
{
19+
return $objectManager->get(static::class);
20+
}
21+
}

0 commit comments

Comments
 (0)