Skip to content

Commit 8fcebb8

Browse files
authored
Merge pull request #302 from mglaman/gh296
Handle controller notation for callbacks
2 parents 4be5dda + 2787403 commit 8fcebb8

File tree

4 files changed

+51
-20
lines changed

4 files changed

+51
-20
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ jobs:
6161
command: ./vendor/bin/phpcs src
6262
- run:
6363
name: PHPStan Analyze
64-
command: ./vendor/bin/phpstan analyze src
64+
command: php -dmemory_limit=-1 vendor/bin/phpstan.phar
6565
- run:
6666
name: PHPUnit
6767
command: ./vendor/bin/phpunit

src/Rules/Drupal/RenderCallbackRule.php

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,18 @@
22

33
namespace mglaman\PHPStanDrupal\Rules\Drupal;
44

5+
use mglaman\PHPStanDrupal\Drupal\ServiceMap;
56
use PhpParser\Node;
67
use PHPStan\Analyser\Scope;
7-
use PHPStan\Broker\Broker;
8-
use PHPStan\Reflection\Php\PhpFunctionReflection;
98
use PHPStan\Reflection\ReflectionProvider;
109
use PHPStan\Rules\Rule;
1110
use PHPStan\Rules\RuleErrorBuilder;
12-
use PHPStan\TrinaryLogic;
1311
use PHPStan\Type\ClosureType;
1412
use PHPStan\Type\Constant\ConstantArrayType;
15-
use PHPStan\Type\Constant\ConstantArrayTypeAndMethod;
1613
use PHPStan\Type\Constant\ConstantIntegerType;
1714
use PHPStan\Type\Constant\ConstantStringType;
1815
use PHPStan\Type\ObjectType;
16+
use PHPStan\Type\Type;
1917
use PHPStan\Type\UnionType;
2018
use PHPStan\Type\VerbosityLevel;
2119

@@ -24,9 +22,12 @@ final class RenderCallbackRule implements Rule
2422

2523
protected ReflectionProvider $reflectionProvider;
2624

27-
public function __construct(ReflectionProvider $reflectionProvider)
25+
protected ServiceMap $serviceMap;
26+
27+
public function __construct(ReflectionProvider $reflectionProvider, ServiceMap $serviceMap)
2828
{
2929
$this->reflectionProvider = $reflectionProvider;
30+
$this->serviceMap = $serviceMap;
3031
}
3132

3233
public function getNodeType(): string
@@ -70,7 +71,7 @@ public function processNode(Node $node, Scope $scope): array
7071
continue;
7172
}
7273
$errorLine = $item->value->getLine();
73-
$type = $scope->getType($item->value);
74+
$type = $this->getType($item->value, $scope);
7475

7576
if ($type instanceof ConstantStringType) {
7677
if (!$type->isCallable()->yes()) {
@@ -87,17 +88,6 @@ public function processNode(Node $node, Scope $scope): array
8788
)->line($errorLine)
8889
->tip('Change record: https://www.drupal.org/node/2966725.')
8990
->build();
90-
continue;
91-
}
92-
// @see \PHPStan\Type\Constant\ConstantStringType::isCallable
93-
preg_match('#^([a-zA-Z_\\x7f-\\xff\\\\][a-zA-Z0-9_\\x7f-\\xff\\\\]*)::([a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*)\\z#', $type->getValue(), $matches);
94-
if ($matches === null) {
95-
throw new \PHPStan\ShouldNotHappenException('Unable to get class name from ConstantStringType value: ' . $type->describe(VerbosityLevel::value()));
96-
}
97-
if (!$trustedCallbackType->isSuperTypeOf(new ObjectType($matches[1]))->yes()) {
98-
$errors[] = RuleErrorBuilder::message(
99-
sprintf("%s callback class '%s' at key '%s' does not implement Drupal\Core\Security\TrustedCallbackInterface.", $keyChecked, (new ObjectType($matches[1]))->describe(VerbosityLevel::value()), $pos)
100-
)->line($errorLine)->tip('Change record: https://www.drupal.org/node/2966725.')->build();
10191
}
10292
} elseif ($type instanceof ConstantArrayType) {
10393
if (!$type->isCallable()->yes()) {
@@ -139,4 +129,42 @@ public function processNode(Node $node, Scope $scope): array
139129

140130
return $errors;
141131
}
132+
133+
private function getType(Node\Expr $node, Scope $scope): Type
134+
{
135+
$type = $scope->getType($node);
136+
if ($type instanceof ConstantStringType) {
137+
if ($type->isClassString()) {
138+
return $type;
139+
}
140+
// Covers \Drupal\Core\Controller\ControllerResolver::createController.
141+
if (substr_count($type->getValue(), ':') === 1) {
142+
[$class_or_service, $method] = explode(':', $type->getValue(), 2);
143+
144+
$serviceDefinition = $this->serviceMap->getService($class_or_service);
145+
if ($serviceDefinition === null || $serviceDefinition->getClass() === null) {
146+
return $type;
147+
}
148+
return new ConstantArrayType(
149+
[new ConstantIntegerType(0), new ConstantIntegerType(1)],
150+
[
151+
new ConstantStringType($serviceDefinition->getClass(), true),
152+
new ConstantStringType($method)
153+
]
154+
);
155+
}
156+
// @see \PHPStan\Type\Constant\ConstantStringType::isCallable
157+
preg_match('#^([a-zA-Z_\\x7f-\\xff\\\\][a-zA-Z0-9_\\x7f-\\xff\\\\]*)::([a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*)\\z#', $type->getValue(), $matches);
158+
if ($matches !== null && count($matches) > 0) {
159+
return new ConstantArrayType(
160+
[new ConstantIntegerType(0), new ConstantIntegerType(1)],
161+
[
162+
new ConstantStringType($matches[1], true),
163+
new ConstantStringType($matches[2])
164+
]
165+
);
166+
}
167+
}
168+
return $type;
169+
}
142170
}

tests/fixtures/drupal/modules/pre_render_callback_rule/pre_render_callback_rule.module

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ function pre_render_callback_rule_alter_some_stuff() {
2727
'sample_pre_render_callback',
2828
'\Drupal\pre_render_callback_rule\RenderArrayWithPreRenderCallback::preRenderCallback',
2929
'\Drupal\pre_render_callback_rule\NotTrustedCallback::unsafeCallback',
30-
['\Drupal\pre_render_callback_rule\NotTrustedCallback', 'unsafeCallback']
30+
['\Drupal\pre_render_callback_rule\NotTrustedCallback', 'unsafeCallback'],
31+
'user.toolbar_link_builder:renderToolbarLinks'
3132
],
3233
];
3334

tests/src/Rules/PreRenderCallbackRuleTest.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace mglaman\PHPStanDrupal\Tests\Rules;
44

55

6+
use mglaman\PHPStanDrupal\Drupal\ServiceMap;
67
use mglaman\PHPStanDrupal\Tests\DrupalRuleTestCase;
78
use mglaman\PHPStanDrupal\Rules\Drupal\RenderCallbackRule;
89

@@ -11,7 +12,8 @@ final class PreRenderCallbackRuleTest extends DrupalRuleTestCase {
1112
protected function getRule(): \PHPStan\Rules\Rule
1213
{
1314
return new RenderCallbackRule(
14-
$this->createReflectionProvider()
15+
$this->createReflectionProvider(),
16+
self::getContainer()->getByType(ServiceMap::class)
1517
);
1618
}
1719

0 commit comments

Comments
 (0)