Skip to content

Commit ee53f82

Browse files
[FEATURE] Add ContextGetAspectDynamicReturnTypeExtension
1 parent d7c49b7 commit ee53f82

3 files changed

+198
-0
lines changed

extension.neon

+4
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,7 @@ services:
33
class: FriendsOfTYPO3\PHPStan\TYPO3\Type\GeneralUtilityDynamicReturnTypeExtension
44
tags:
55
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
6+
-
7+
class: FriendsOfTYPO3\PHPStan\TYPO3\Type\ContextGetAspectDynamicReturnTypeExtension
8+
tags:
9+
- phpstan.broker.dynamicMethodReturnTypeExtension
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace FriendsOfTYPO3\PHPStan\TYPO3\Type;
6+
7+
use PhpParser\Node\Arg;
8+
use PhpParser\Node\Expr\MethodCall;
9+
use PhpParser\Node\Scalar\String_;
10+
use PHPStan\Analyser\Scope;
11+
use PHPStan\Reflection\MethodReflection;
12+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
13+
use PHPStan\Type\ObjectType;
14+
use PHPStan\Type\Type;
15+
use TYPO3\CMS\Core\Context\AspectInterface;
16+
use TYPO3\CMS\Core\Context\Context;
17+
use TYPO3\CMS\Core\Context\DateTimeAspect;
18+
use TYPO3\CMS\Core\Context\LanguageAspect;
19+
use TYPO3\CMS\Core\Context\TypoScriptAspect;
20+
use TYPO3\CMS\Core\Context\UserAspect;
21+
use TYPO3\CMS\Core\Context\VisibilityAspect;
22+
use TYPO3\CMS\Core\Context\WorkspaceAspect;
23+
24+
class ContextGetAspectDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
25+
{
26+
public function getClass(): string
27+
{
28+
return Context::class;
29+
}
30+
31+
public function isMethodSupported(MethodReflection $methodReflection): bool
32+
{
33+
return $methodReflection->getName() === 'getAspect';
34+
}
35+
36+
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
37+
{
38+
$defaultObjectType = new ObjectType(AspectInterface::class);
39+
40+
if (!($argument = $methodCall->args[0] ?? null) instanceof Arg) {
41+
return $defaultObjectType;
42+
}
43+
/** @var Arg $argument */
44+
45+
if (!($string = $argument->value ?? null) instanceof String_) {
46+
return $defaultObjectType;
47+
}
48+
/** @var String_ $string */
49+
50+
switch ($string->value) {
51+
case 'date':
52+
$type = new ObjectType(DateTimeAspect::class);
53+
break;
54+
case 'visibility':
55+
$type = new ObjectType(VisibilityAspect::class);
56+
break;
57+
case 'frontend.user':
58+
case 'backend.user':
59+
$type = new ObjectType(UserAspect::class);
60+
break;
61+
case 'workspace':
62+
$type = new ObjectType(WorkspaceAspect::class);
63+
break;
64+
case 'language':
65+
$type = new ObjectType(LanguageAspect::class);
66+
break;
67+
case 'typoscript':
68+
$type = new ObjectType(TypoScriptAspect::class);
69+
break;
70+
default:
71+
$type = $defaultObjectType;
72+
break;
73+
}
74+
75+
return $type;
76+
}
77+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace FriendsOfTYPO3\PHPStan\TYPO3\Tests\Unit\Type;
6+
7+
use FriendsOfTYPO3\PHPStan\TYPO3\Type\ContextGetAspectDynamicReturnTypeExtension;
8+
use PhpParser\Node\Arg;
9+
use PhpParser\Node\Expr;
10+
use PhpParser\Node\Scalar\String_;
11+
use PHPStan\Analyser\Scope;
12+
use PHPStan\Reflection\MethodReflection;
13+
use PHPStan\Type\ObjectType;
14+
use PHPUnit\Framework\TestCase;
15+
use TYPO3\CMS\Core\Context\AspectInterface;
16+
use TYPO3\CMS\Core\Context\Context;
17+
use TYPO3\CMS\Core\Context\DateTimeAspect;
18+
use TYPO3\CMS\Core\Context\LanguageAspect;
19+
use TYPO3\CMS\Core\Context\TypoScriptAspect;
20+
use TYPO3\CMS\Core\Context\UserAspect;
21+
use TYPO3\CMS\Core\Context\VisibilityAspect;
22+
use TYPO3\CMS\Core\Context\WorkspaceAspect;
23+
24+
class ContextGetAspectDynamicReturnTypeExtensionTest extends TestCase
25+
{
26+
/**
27+
* @var ContextGetAspectDynamicReturnTypeExtension
28+
*/
29+
private $extension;
30+
31+
protected function setUp(): void
32+
{
33+
$this->extension = new ContextGetAspectDynamicReturnTypeExtension();
34+
}
35+
36+
public function testGetClass(): void
37+
{
38+
static::assertSame(Context::class, $this->extension->getClass());
39+
}
40+
41+
/**
42+
* @return \Generator<int,array<int, string|bool>>
43+
*/
44+
public function dataIsMethodSupported(): \Generator
45+
{
46+
yield ['getAspect', true];
47+
yield ['foo', false];
48+
}
49+
50+
/**
51+
* @dataProvider dataIsMethodSupported
52+
* @param string $method
53+
* @param bool $expectedResult
54+
*/
55+
public function testIsMethodSupported(string $method, bool $expectedResult): void
56+
{
57+
$methodReflection = $this->prophesize(MethodReflection::class);
58+
$methodReflection->getName()->willReturn($method);
59+
60+
self::assertSame($expectedResult, $this->extension->isMethodSupported($methodReflection->reveal()));
61+
}
62+
63+
public function testGetTypeFromMethodCallReturnsAspectInterfaceAsDefault(): void
64+
{
65+
$scope = $this->prophesize(Scope::class)->reveal();
66+
$methodCall = $this->prophesize(Expr\MethodCall::class)->reveal();
67+
$methodReflection = $this->prophesize(MethodReflection::class)->reveal();
68+
69+
/** @var ObjectType $type */
70+
$type = $this->extension->getTypeFromMethodCall($methodReflection, $methodCall, $scope);
71+
72+
static::assertInstanceOf(ObjectType::class, $type);
73+
static::assertSame(AspectInterface::class, $type->getClassName());
74+
}
75+
76+
/**
77+
* @return \Generator<int,array<int, string>>
78+
*/
79+
public function getTypeFromMethodCallDataProvider(): \Generator
80+
{
81+
yield ['date', DateTimeAspect::class];
82+
yield ['visibility', VisibilityAspect::class];
83+
yield ['frontend.user', UserAspect::class];
84+
yield ['backend.user', UserAspect::class];
85+
yield ['workspace', WorkspaceAspect::class];
86+
yield ['language', LanguageAspect::class];
87+
yield ['typoscript', TypoScriptAspect::class];
88+
yield ['unknown', AspectInterface::class];
89+
}
90+
91+
/**
92+
* @dataProvider getTypeFromMethodCallDataProvider
93+
* @param string $argumentValue
94+
* @param string $expectedClassName
95+
*/
96+
public function testGetTypeFromMethodCall(string $argumentValue, string $expectedClassName): void
97+
{
98+
$string = $this->prophesize(String_::class)->reveal();
99+
$string->value = $argumentValue;
100+
101+
$arg = $this->prophesize(Arg::class)->reveal();
102+
$arg->value = $string;
103+
104+
$methodCall = $this->prophesize(Expr\MethodCall::class)->reveal();
105+
$methodCall->args = [$arg];
106+
107+
/** @var ObjectType $type */
108+
$type = $this->extension->getTypeFromMethodCall(
109+
$this->prophesize(MethodReflection::class)->reveal(),
110+
$methodCall,
111+
$this->prophesize(Scope::class)->reveal()
112+
);
113+
114+
static::assertInstanceOf(ObjectType::class, $type);
115+
static::assertSame($expectedClassName, $type->getClassName());
116+
}
117+
}

0 commit comments

Comments
 (0)