Skip to content

Commit 1b159ea

Browse files
committed
Make TYPO3 Context aspects configurable
Add a mapping to be able to add custom Context API aspects to the ContextDynamicReturnTypeExtension. Resolves #34
1 parent cadf0d8 commit 1b159ea

File tree

4 files changed

+115
-22
lines changed

4 files changed

+115
-22
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,14 @@ includes:
2525
```
2626
</details>
2727

28+
### Custom Context API Aspects
29+
30+
If you use custom aspects for the TYPO3 Context API you can now add a mapping so PHPStan knows
31+
what type of aspect class is returned by the context API
32+
33+
```
34+
parameters:
35+
typo3:
36+
contextApiGetAspectMapping:
37+
myCustomAspect: \FlowdGmbh\MyProject\Context\MyCustomAspect
38+
```

extension.neon

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,28 @@ services:
2121
- phpstan.broker.dynamicMethodReturnTypeExtension
2222
-
2323
class: SaschaEgerer\PhpstanTypo3\Type\ContextDynamicReturnTypeExtension
24+
arguments:
25+
contextApiGetAspectMapping: %typo3.contextApiGetAspectMapping%
2426
tags:
2527
- phpstan.broker.dynamicMethodReturnTypeExtension
28+
-
29+
class: SaschaEgerer\PhpstanTypo3\Rule\ContextAspectValidationRule
30+
arguments:
31+
contextApiGetAspectMapping: %typo3.contextApiGetAspectMapping%
32+
tags:
33+
- phpstan.rules.rule
2634
parameters:
2735
bootstrapFiles:
2836
- phpstan.bootstrap.php
37+
typo3:
38+
contextApiGetAspectMapping:
39+
date: \TYPO3\CMS\Core\Context\DateTimeAspect
40+
visibility: \TYPO3\CMS\Core\Context\VisibilityAspect
41+
backend.user: \TYPO3\CMS\Core\Context\UserAspect
42+
frontend.user: \TYPO3\CMS\Core\Context\UserAspect
43+
workspace: \TYPO3\CMS\Core\Context\WorkspaceAspect
44+
language: \TYPO3\CMS\Core\Context\LanguageAspect
45+
typoscript: \TYPO3\CMS\Core\Context\TypoScriptAspect
2946
stubFiles:
3047
- stubs/GeneralUtility.stub
3148
- stubs/ObjectStorage.stub
@@ -71,3 +88,7 @@ parameters:
7188
- pageErrorHandler
7289
TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList:
7390
- pageErrorHandler
91+
parametersSchema:
92+
typo3: structure([
93+
contextApiGetAspectMapping: arrayOf(string())
94+
])
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace SaschaEgerer\PhpstanTypo3\Rule;
4+
5+
use PhpParser\Node;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Rules\RuleErrorBuilder;
8+
9+
/**
10+
* @implements \PHPStan\Rules\Rule<\PhpParser\Node\Expr\MethodCall>
11+
*/
12+
class ContextAspectValidationRule implements \PHPStan\Rules\Rule
13+
{
14+
15+
/** @var array<string, string> */
16+
private $contextApiGetAspectMapping;
17+
18+
/**
19+
* @param array<string, string> $contextApiGetAspectMapping
20+
*/
21+
public function __construct(array $contextApiGetAspectMapping)
22+
{
23+
$this->contextApiGetAspectMapping = $contextApiGetAspectMapping;
24+
}
25+
26+
public function getNodeType(): string
27+
{
28+
return Node\Expr\MethodCall::class;
29+
}
30+
31+
public function processNode(Node $node, Scope $scope): array
32+
{
33+
if (!$node->name instanceof Node\Identifier) {
34+
return [];
35+
}
36+
37+
$methodReflection = $scope->getMethodReflection($scope->getType($node->var), $node->name->toString());
38+
if ($methodReflection === null || $methodReflection->getName() !== 'getAspect') {
39+
return [];
40+
}
41+
42+
$declaringClass = $methodReflection->getDeclaringClass();
43+
44+
if ($declaringClass->getName() !== \TYPO3\CMS\Core\Context\Context::class) {
45+
return [];
46+
}
47+
48+
$argument = $node->getArgs()[0] ?? null;
49+
50+
if (!($argument instanceof Node\Arg) || !($argument->value instanceof Node\Scalar\String_)) {
51+
return [];
52+
}
53+
54+
if (isset($this->contextApiGetAspectMapping[$argument->value->value])) {
55+
return [];
56+
}
57+
58+
$ruleError = RuleErrorBuilder::message(sprintf(
59+
'There is no aspect "%s" configured so we can\'t figure out the exact type to return when calling %s::%s',
60+
$argument->value->value,
61+
$declaringClass->getDisplayName(),
62+
$methodReflection->getName()
63+
))->tip('You should add custom aspects to the typo3.contextApiGetAspectMapping setting.')->build();
64+
65+
return [$ruleError];
66+
}
67+
68+
}

src/Type/ContextDynamicReturnTypeExtension.php

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@
1313
class ContextDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
1414
{
1515

16+
/** @var array<string, string> */
17+
private $contextApiGetAspectMapping;
18+
19+
/**
20+
* @param array<string, string> $contextApiGetAspectMapping
21+
*/
22+
public function __construct(array $contextApiGetAspectMapping)
23+
{
24+
$this->contextApiGetAspectMapping = $contextApiGetAspectMapping;
25+
}
26+
1627
public function getClass(): string
1728
{
1829
return \TYPO3\CMS\Core\Context\Context::class;
@@ -33,33 +44,15 @@ public function getTypeFromMethodCall(
3344
{
3445
$argument = $methodCall->getArgs()[0] ?? null;
3546

36-
if ($argument === null) {
47+
if ($argument === null || !($argument->value instanceof \PhpParser\Node\Scalar\String_)) {
3748
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
3849
}
3950

40-
$argumentValue = $argument->value;
41-
42-
if (!($argumentValue instanceof \PhpParser\Node\Scalar\String_)) {
43-
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
51+
if (isset($this->contextApiGetAspectMapping[$argument->value->value])) {
52+
return new ObjectType($this->contextApiGetAspectMapping[$argument->value->value]);
4453
}
4554

46-
switch ($argumentValue->value) {
47-
case 'date':
48-
return new ObjectType(\TYPO3\CMS\Core\Context\DateTimeAspect::class);
49-
case 'visibility':
50-
return new ObjectType(\TYPO3\CMS\Core\Context\VisibilityAspect::class);
51-
case 'backend.user':
52-
case 'frontend.user':
53-
return new ObjectType(\TYPO3\CMS\Core\Context\UserAspect::class);
54-
case 'workspace':
55-
return new ObjectType(\TYPO3\CMS\Core\Context\WorkspaceAspect::class);
56-
case 'language':
57-
return new ObjectType(\TYPO3\CMS\Core\Context\LanguageAspect::class);
58-
case 'typoscript':
59-
return new ObjectType(\TYPO3\CMS\Core\Context\TypoScriptAspect::class);
60-
default:
61-
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
62-
}
55+
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
6356
}
6457

6558
}

0 commit comments

Comments
 (0)