function RenderCallbackRule::doProcessNode
Return value
(string|\PHPStan\Rules\RuleError)[] errors
1 call to RenderCallbackRule::doProcessNode()
- RenderCallbackRule::processNode in vendor/
mglaman/ phpstan-drupal/ src/ Rules/ Drupal/ RenderCallbackRule.php
File
-
vendor/
mglaman/ phpstan-drupal/ src/ Rules/ Drupal/ RenderCallbackRule.php, line 138
Class
- RenderCallbackRule
- @implements Rule<Node\Expr\ArrayItem>
Namespace
mglaman\PHPStanDrupal\Rules\DrupalCode
private function doProcessNode(Node\Expr $node, Scope $scope, string $keyChecked, int $pos) : array {
$checkIsCallable = true;
$trustedCallbackType = new UnionType([
new ObjectType(TrustedCallbackInterface::class),
new ObjectType(RenderCallbackInterface::class),
]);
$errors = [];
$errorLine = $node->getStartLine();
$type = $this->getType($node, $scope);
foreach ($type->getConstantStrings() as $constantStringType) {
if (!$constantStringType->isCallable()
->yes()) {
$errors[] = RuleErrorBuilder::message(sprintf("%s callback %s at key '%s' is not callable.", $keyChecked, $constantStringType->describe(VerbosityLevel::value()), $pos))
->line($errorLine)
->build();
}
elseif ($this->reflectionProvider
->hasFunction(new Name($constantStringType->getValue()), null)) {
// We can determine if the callback is callable through the type system. However, we cannot determine
// if it is just a function or a static class call (MyClass::staticFunc).
$errors[] = RuleErrorBuilder::message(sprintf("%s callback %s at key '%s' is not trusted.", $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();
}
}
}
foreach ($type->getConstantArrays() as $constantArrayType) {
if (!$constantArrayType->isCallable()
->yes()) {
// If the right-hand side of the array is a variable, we cannot
// determine if it is callable. Bail now.
$itemType = $constantArrayType->getItemType();
if ($itemType instanceof UnionType) {
$unionConstantStrings = array_merge(...array_map(static function (Type $type) {
return $type->getConstantStrings();
}, $itemType->getTypes()));
if (count($unionConstantStrings) === 0) {
// Right-hand side of UnionType is not a constant string. We cannot determine if the dynamic
// value is callable or not.
$checkIsCallable = false;
break;
}
}
$errors[] = RuleErrorBuilder::message(sprintf("%s callback %s at key '%s' is not callable.", $keyChecked, $constantArrayType->describe(VerbosityLevel::value()), $pos))
->line($errorLine)
->build();
continue;
}
$typeAndMethodNames = $constantArrayType->findTypeAndMethodNames();
if ($typeAndMethodNames === []) {
continue;
}
foreach ($typeAndMethodNames as $typeAndMethodName) {
$isTrustedCallbackAttribute = TrinaryLogic::createNo()->lazyOr($typeAndMethodName->getType()
->getObjectClassReflections(), function (ClassReflection $reflection) use ($typeAndMethodName) {
if (!class_exists(TrustedCallback::class)) {
return TrinaryLogic::createNo();
}
$hasAttribute = $reflection->getNativeReflection()
->getMethod($typeAndMethodName->getMethod())
->getAttributes(TrustedCallback::class);
return TrinaryLogic::createFromBoolean(count($hasAttribute) > 0);
});
$isTrustedCallbackInterfaceType = $trustedCallbackType->isSuperTypeOf($typeAndMethodName->getType())
->yes();
if (!$isTrustedCallbackInterfaceType && !$isTrustedCallbackAttribute->yes()) {
if (class_exists(TrustedCallback::class)) {
$errors[] = RuleErrorBuilder::message(sprintf("%s callback method '%s' at key '%s' does not implement attribute \\Drupal\\Core\\Security\\Attribute\\TrustedCallback.", $keyChecked, $constantArrayType->describe(VerbosityLevel::value()), $pos))
->line($errorLine)
->tip('Change record: https://www.drupal.org/node/3349470')
->build();
}
else {
$errors[] = RuleErrorBuilder::message(sprintf("%s callback class '%s' at key '%s' does not implement Drupal\\Core\\Security\\TrustedCallbackInterface.", $keyChecked, $typeAndMethodName->getType()
->describe(VerbosityLevel::value()), $pos))
->line($errorLine)
->tip('Change record: https://www.drupal.org/node/2966725.')
->build();
}
}
}
}
// @todo move to its own rule for 1.2.0, FormClosureSerializationRule.
if ($type instanceof ClosureType && $scope->isInClass()) {
$classReflection = $scope->getClassReflection();
$classType = new ObjectType($classReflection->getName());
$formType = new ObjectType('\\Drupal\\Core\\Form\\FormInterface');
if ($formType->isSuperTypeOf($classType)
->yes()) {
$errors[] = RuleErrorBuilder::message(sprintf("%s may not contain a closure at key '%s' as forms may be serialized and serialization of closures is not allowed.", $keyChecked, $pos))->line($errorLine)
->build();
}
}
if (count($errors) === 0 && ($checkIsCallable && !$type->isCallable()
->yes())) {
$errors[] = RuleErrorBuilder::message(sprintf("%s value '%s' at key '%s' is invalid.", $keyChecked, $type->describe(VerbosityLevel::value()), $pos))
->line($errorLine)
->build();
}
return $errors;
}