diff --git a/src/Rules/Drupal/RenderCallbackRule.php b/src/Rules/Drupal/RenderCallbackRule.php index e06487bd..434f84b2 100644 --- a/src/Rules/Drupal/RenderCallbackRule.php +++ b/src/Rules/Drupal/RenderCallbackRule.php @@ -153,10 +153,18 @@ private function doProcessNode(Node\Expr $node, Scope $scope, string $keyChecked )->line($errorLine) ->tip('Change record: https://www.drupal.org/node/2966725.') ->build(); - } elseif (!$trustedCallbackType->isSuperTypeOf($type)->yes()) { - $errors[] = RuleErrorBuilder::message( - sprintf("%s callback class %s at key '%s' does not implement Drupal\Core\Security\TrustedCallbackInterface.", $keyChecked, $constantStringType->describe(VerbosityLevel::value()), $pos) - )->line($errorLine)->tip('Change record: https://www.drupal.org/node/2966725.')->build(); + } else { + // @see \PHPStan\Type\Constant\ConstantStringType::isCallable + 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#', $constantStringType->getValue(), $matches); + if (count($matches) === 0) { + $errors[] = RuleErrorBuilder::message( + sprintf("%s callback %s at key '%s' is not callable.", $keyChecked, $constantStringType->describe(VerbosityLevel::value()), $pos) + )->line($errorLine)->build(); + } elseif (!$trustedCallbackType->isSuperTypeOf(new ObjectType($matches[1]))->yes()) { + $errors[] = RuleErrorBuilder::message( + sprintf("%s callback class %s at key '%s' does not implement Drupal\Core\Security\TrustedCallbackInterface.", $keyChecked, $constantStringType->describe(VerbosityLevel::value()), $pos) + )->line($errorLine)->tip('Change record: https://www.drupal.org/node/2966725.')->build(); + } } } diff --git a/tests/src/Rules/RenderCallbackRuleTest.php b/tests/src/Rules/RenderCallbackRuleTest.php index 9259a26d..14b966ad 100644 --- a/tests/src/Rules/RenderCallbackRuleTest.php +++ b/tests/src/Rules/RenderCallbackRuleTest.php @@ -158,6 +158,11 @@ public static function fileData(): \Generator ], ]; } + + yield [ + __DIR__ . '/data/bug-543.php', + [] + ]; } diff --git a/tests/src/Rules/data/bug-543.php b/tests/src/Rules/data/bug-543.php new file mode 100644 index 00000000..2eda8414 --- /dev/null +++ b/tests/src/Rules/data/bug-543.php @@ -0,0 +1,105 @@ + TestAccessClass::class . '::' . $method, + ]; + } + + public function bug543AccessResultAllowed(): void { + $build = [ + '#access_callback' => TestAccessClass::class . '::accessResultAllowed', + ]; + } + + public function bug543AccessResultForbidden(): void { + $build = [ + '#access_callback' => TestAccessClass::class . '::accessResultForbidden', + ]; + } + + public function bug543AccessFalse(): void { + $build = [ + '#access_callback' => TestAccessClass::class . '::accessFalse', + ]; + } + + public function bug543AccessTrue(): void { + $build = [ + '#access_callback' => TestAccessClass::class . '::accessTrue', + ]; + } +} + +class TestAccessClass implements TrustedCallbackInterface { + + public static function accessTrue() { + return TRUE; + } + + public static function accessFalse() { + return FALSE; + } + + public static function accessResultAllowed() { + return AccessResult::allowed(); + } + + public static function accessResultForbidden() { + return AccessResult::forbidden(); + } + + /** + * {@inheritdoc} + */ + public static function trustedCallbacks() { + return ['accessTrue', 'accessFalse', 'accessResultAllowed', 'accessResultForbidden']; + } + +}