Repository: webonyx/graphql-php (language: php)

Issue:
Remove static from GraphQL\Validator\Rules\ValuesOfCorrectType
A lot of `static` methods are used in `ValuesOfCorrectType`, but when I take a closer look I can't see why. There seems to be no added benefit in having these static. Would it be possible to remove all the `static` usage from this class?

The reason I ask is because I want to extend it in order to change the way some error messages are created. This feels related to #711, but I want to do more than a simple string replacement.


Discussion / hints:
> There seems to be no added benefit in having these static.

Those methods can be called without class instantiation, this is done from tests. How would removing `static` improve extensibility?

I think the deciciding difference is in how those methods are called. `self::method()` prevents overrides, while `static::method()` works.
> Those methods can be called without class instantiation, this is done from tests.

So the only reason they are static is because of tests?

> I think the deciciding difference is in how those methods are called. self::method() prevents overrides, while static::method() works.

Then replacing `self` with `static` would also work for my case.
> So the only reason they are static is because of tests?

I just named one reason, there may be many.

Another one I can think of right now is that static methods are generally easier to reason about; one does (generally) not have to worry about state with them.

> Then replacing self with static would also work for my case.

Yes. Accepting PRs, but preferrably one that opens up all validation rules for extensibility to ensure consistency.

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c32654f76..2b4fa10ce 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,9 @@
 Changed:
 - PHP version required: 7.4+
 
+Feat:
+- Improve extendability of validator rules
+
 Removed:
 - Removed deprecated `Type::getInternalTypes()`
 - Remove deprecated `GraphQL::execute()`
diff --git a/src/Validator/Rules/CustomValidationRule.php b/src/Validator/Rules/CustomValidationRule.php
index 83101a1d2..24ae2ad61 100644
--- a/src/Validator/Rules/CustomValidationRule.php
+++ b/src/Validator/Rules/CustomValidationRule.php
@@ -4,27 +4,26 @@
 
 namespace GraphQL\Validator\Rules;
 
-use GraphQL\Error\Error;
+use GraphQL\Language\AST\Node;
+use GraphQL\Language\VisitorOperation;
 use GraphQL\Validator\ValidationContext;
 
 class CustomValidationRule extends ValidationRule
 {
     /** @var callable */
-    private $visitorFn;
+    protected $visitorFn;
 
-    public function __construct($name, callable $visitorFn)
+    /**
+     * @param callable(ValidationContext): (array<string, callable(Node): VisitorOperation|mixed|null>|array<string, array<string, callable(Node): VisitorOperation|mixed|null>>) $visitorFn
+     */
+    public function __construct(string $name, callable $visitorFn)
     {
         $this->name      = $name;
         $this->visitorFn = $visitorFn;
     }
 
-    /**
-     * @return Error[]
-     */
     public function getVisitor(ValidationContext $context)
     {
-        $fn = $this->visitorFn;
-
-        return $fn($context);
+        return ($this->visitorFn)($context);
     }
 }
diff --git a/src/Validator/Rules/DisableIntrospection.php b/src/Validator/Rules/DisableIntrospection.php
index 17758e78c..f6067d102 100644
--- a/src/Validator/Rules/DisableIntrospection.php
+++ b/src/Validator/Rules/DisableIntrospection.php
@@ -13,8 +13,7 @@ class DisableIntrospection extends QuerySecurityRule
 {
     public const ENABLED = 1;
 
-    /** @var bool */
-    private $isEnabled;
+    protected int $isEnabled;
 
     public function __construct($enabled = self::ENABLED)
     {
diff --git a/src/Validator/Rules/ExecutableDefinitions.php b/src/Validator/Rules/ExecutableDefinitions.php
index a0dbf2eaa..b4c130d42 100644
--- a/src/Validator/Rules/ExecutableDefinitions.php
+++ b/src/Validator/Rules/ExecutableDefinitions.php
@@ -34,7 +34,7 @@ public function getVisitor(ValidationContext $context)
                     }
 
                     $context->reportError(new Error(
-                        self::nonExecutableDefinitionMessage($definition->name->value),
+                        static::nonExecutableDefinitionMessage($definition->name->value),
                         [$definition->name]
                     ));
                 }
diff --git a/src/Validator/Rules/FieldsOnCorrectType.php b/src/Validator/Rules/FieldsOnCorrectType.php
index 7711c08ad..1582f74cd 100644
--- a/src/Validator/Rules/FieldsOnCorrectType.php
+++ b/src/Validator/Rules/FieldsOnCorrectType.php
@@ -79,7 +79,7 @@ public function getVisitor(ValidationContext $context)
      *
      * @return string[]
      */
-    private function getSuggestedTypeNames(Schema $schema, $type, $fieldName)
+    protected function getSuggestedTypeNames(Schema $schema, $type, $fieldName)
     {
         if (Type::isAbstractType($type)) {
             $suggestedObjectTypes = [];
@@ -128,7 +128,7 @@ private function getSuggestedTypeNames(Schema $schema, $type, $fieldName)
      *
      * @return array|string[]
      */
-    private function getSuggestedFieldNames(Schema $schema, $type, $fieldName)
+    protected function getSuggestedFieldNames(Schema $schema, $type, $fieldName)
     {
         if ($type instanceof ObjectType || $type instanceof InterfaceType) {
             $possibleFieldNames = array_keys($type->getFields());
diff --git a/src/Validator/Rules/KnownArgumentNames.php b/src/Validator/Rules/KnownArgumentNames.php
index 2b6b7d6db..e14760310 100644
--- a/src/Validator/Rules/KnownArgumentNames.php
+++ b/src/Validator/Rules/KnownArgumentNames.php
@@ -40,7 +40,7 @@ public function getVisitor(ValidationContext $context)
                 }
 
                 $context->reportError(new Error(
-                    self::unknownArgMessage(
+                    static::unknownArgMessage(
                         $node->name->value,
                         $fieldDef->name,
                         $parentType->name,
diff --git a/src/Validator/Rules/KnownArgumentNamesOnDirectives.php b/src/Validator/Rules/KnownArgumentNamesOnDirectives.php
index e2a1e6da5..a72d6328b 100644
--- a/src/Validator/Rules/KnownArgumentNamesOnDirectives.php
+++ b/src/Validator/Rules/KnownArgumentNamesOnDirectives.php
@@ -104,7 +104,7 @@ static function (InputValueDefinitionNode $arg): string {
 
                     $suggestions = Utils::suggestionList($argName, $knownArgs);
                     $context->reportError(new Error(
-                        self::unknownDirectiveArgMessage($argName, $directiveName, $suggestions),
+                        static::unknownDirectiveArgMessage($argName, $directiveName, $suggestions),
                         [$argNode]
                     ));
                 }
diff --git a/src/Validator/Rules/KnownDirectives.php b/src/Validator/Rules/KnownDirectives.php
index 23235e127..bb377cbe5 100644
--- a/src/Validator/Rules/KnownDirectives.php
+++ b/src/Validator/Rules/KnownDirectives.php
@@ -101,7 +101,7 @@ static function ($name): string {
 
                 if (! $locations) {
                     $context->reportError(new Error(
-                        self::unknownDirectiveMessage($name),
+                        static::unknownDirectiveMessage($name),
                         [$node]
                     ));
 
@@ -116,7 +116,7 @@ static function ($name): string {
 
                 $context->reportError(
                     new Error(
-                        self::misplacedDirectiveMessage($name, $candidateLocation),
+                        static::misplacedDirectiveMessage($name, $candidateLocation),
                         [$node]
                     )
                 );
@@ -134,7 +134,7 @@ public static function unknownDirectiveMessage($directiveName)
      *
      * @return string
      */
-    private function getDirectiveLocationForASTPath(array $ancestors)
+    protected function getDirectiveLocationForASTPath(array $ancestors)
     {
         $appliedTo = $ancestors[count($ancestors) - 1];
         switch (true) {
diff --git a/src/Validator/Rules/KnownFragmentNames.php b/src/Validator/Rules/KnownFragmentNames.php
index 14c57d917..69b0ed055 100644
--- a/src/Validator/Rules/KnownFragmentNames.php
+++ b/src/Validator/Rules/KnownFragmentNames.php
@@ -24,7 +24,7 @@ public function getVisitor(ValidationContext $context)
                 }
 
                 $context->reportError(new Error(
-                    self::unknownFragmentMessage($fragmentName),
+                    static::unknownFragmentMessage($fragmentName),
                     [$node->name]
                 ));
             },
diff --git a/src/Validator/Rules/KnownTypeNames.php b/src/Validator/Rules/KnownTypeNames.php
index 36832ce1f..d476d28be 100644
--- a/src/Validator/Rules/KnownTypeNames.php
+++ b/src/Validator/Rules/KnownTypeNames.php
@@ -47,7 +47,7 @@ public function getVisitor(ValidationContext $context)
                 }
 
                 $context->reportError(new Error(
-                    self::unknownTypeMessage(
+                    static::unknownTypeMessage(
                         $typeName,
                         Utils::suggestionList($typeName, array_keys($schema->getTypeMap()))
                     ),
diff --git a/src/Validator/Rules/LoneAnonymousOperation.php b/src/Validator/Rules/LoneAnonymousOperation.php
index 5c00fc611..7b0600625 100644
--- a/src/Validator/Rules/LoneAnonymousOperation.php
+++ b/src/Validator/Rules/LoneAnonymousOperation.php
@@ -46,7 +46,7 @@ static function (Node $definition): bool {
                 }
 
                 $context->reportError(
-                    new Error(self::anonOperationNotAloneMessage(), [$node])
+                    new Error(static::anonOperationNotAloneMessage(), [$node])
                 );
             },
         ];
diff --git a/src/Validator/Rules/LoneSchemaDefinition.php b/src/Validator/Rules/LoneSchemaDefinition.php
index ed35ec1c3..de361e53d 100644
--- a/src/Validator/Rules/LoneSchemaDefinition.php
+++ b/src/Validator/Rules/LoneSchemaDefinition.php
@@ -43,13 +43,13 @@ public function getSDLVisitor(SDLValidationContext $context)
         return [
             NodeKind::SCHEMA_DEFINITION => static function (SchemaDefinitionNode $node) use ($alreadyDefined, $context, &$schemaDefinitionsCount): void {
                 if ($alreadyDefined !== false) {
-                    $context->reportError(new Error(self::canNotDefineSchemaWithinExtensionMessage(), $node));
+                    $context->reportError(new Error(static::canNotDefineSchemaWithinExtensionMessage(), $node));
 
                     return;
                 }
 
                 if ($schemaDefinitionsCount > 0) {
-                    $context->reportError(new Error(self::schemaDefinitionNotAloneMessage(), $node));
+                    $context->reportError(new Error(static::schemaDefinitionNotAloneMessage(), $node));
                 }
 
                 ++$schemaDefinitionsCount;
diff --git a/src/Validator/Rules/NoFragmentCycles.php b/src/Validator/Rules/NoFragmentCycles.php
index e075b02c2..76db32f7f 100644
--- a/src/Validator/Rules/NoFragmentCycles.php
+++ b/src/Validator/Rules/NoFragmentCycles.php
@@ -22,13 +22,13 @@
 class NoFragmentCycles extends ValidationRule
 {
     /** @var bool[] */
-    public $visitedFrags;
+    protected array $visitedFrags;
 
     /** @var FragmentSpreadNode[] */
-    public $spreadPath;
+    protected array $spreadPath;
 
     /** @var (int|null)[] */
-    public $spreadPathIndexByName;
+    protected array $spreadPathIndexByName;
 
     public function getVisitor(ValidationContext $context)
     {
@@ -54,7 +54,7 @@ public function getVisitor(ValidationContext $context)
         ];
     }
 
-    private function detectCycleRecursive(FragmentDefinitionNode $fragment, ValidationContext $context)
+    protected function detectCycleRecursive(FragmentDefinitionNode $fragment, ValidationContext $context)
     {
         if (isset($this->visitedFrags[$fragment->name->value])) {
             return;
@@ -89,7 +89,7 @@ private function detectCycleRecursive(FragmentDefinitionNode $fragment, Validati
                 });
 
                 $context->reportError(new Error(
-                    self::cycleErrorMessage($spreadName, $fragmentNames),
+                    static::cycleErrorMessage($spreadName, $fragmentNames),
                     $cyclePath
                 ));
             }
diff --git a/src/Validator/Rules/NoUndefinedVariables.php b/src/Validator/Rules/NoUndefinedVariables.php
index a11848bcf..7209294f6 100644
--- a/src/Validator/Rules/NoUndefinedVariables.php
+++ b/src/Validator/Rules/NoUndefinedVariables.php
@@ -39,7 +39,7 @@ public function getVisitor(ValidationContext $context)
                         }
 
                         $context->reportError(new Error(
-                            self::undefinedVarMessage(
+                            static::undefinedVarMessage(
                                 $varName,
                                 $operation->name !== null
                                     ? $operation->name->value
diff --git a/src/Validator/Rules/NoUnusedFragments.php b/src/Validator/Rules/NoUnusedFragments.php
index 55f4cd5ac..b4d68ffaf 100644
--- a/src/Validator/Rules/NoUnusedFragments.php
+++ b/src/Validator/Rules/NoUnusedFragments.php
@@ -17,10 +17,10 @@
 class NoUnusedFragments extends ValidationRule
 {
     /** @var OperationDefinitionNode[] */
-    public $operationDefs;
+    protected array $operationDefs;
 
     /** @var FragmentDefinitionNode[] */
-    public $fragmentDefs;
+    protected array $fragmentDefs;
 
     public function getVisitor(ValidationContext $context)
     {
@@ -55,7 +55,7 @@ public function getVisitor(ValidationContext $context)
                         }
 
                         $context->reportError(new Error(
-                            self::unusedFragMessage($fragName),
+                            static::unusedFragMessage($fragName),
                             [$fragmentDef]
                         ));
                     }
diff --git a/src/Validator/Rules/NoUnusedVariables.php b/src/Validator/Rules/NoUnusedVariables.php
index d1875eac2..7615b15ca 100644
--- a/src/Validator/Rules/NoUnusedVariables.php
+++ b/src/Validator/Rules/NoUnusedVariables.php
@@ -15,7 +15,7 @@
 class NoUnusedVariables extends ValidationRule
 {
     /** @var VariableDefinitionNode[] */
-    public $variableDefs;
+    protected array $variableDefs;
 
     public function getVisitor(ValidationContext $context)
     {
@@ -46,7 +46,7 @@ public function getVisitor(ValidationContext $context)
                         }
 
                         $context->reportError(new Error(
-                            self::unusedVariableMessage($variableName, $opName),
+                            static::unusedVariableMessage($variableName, $opName),
                             [$variableDef]
                         ));
                     }
diff --git a/src/Validator/Rules/OverlappingFieldsCanBeMerged.php b/src/Validator/Rules/OverlappingFieldsCanBeMerged.php
index a69f2b5fa..1445a956d 100644
--- a/src/Validator/Rules/OverlappingFieldsCanBeMerged.php
+++ b/src/Validator/Rules/OverlappingFieldsCanBeMerged.php
@@ -41,19 +41,15 @@ class OverlappingFieldsCanBeMerged extends ValidationRule
      * A memoization for when two fragments are compared "between" each other for
      * conflicts. Two fragments may be compared many times, so memoizing this can
      * dramatically improve the performance of this validator.
-     *
-     * @var PairSet
      */
-    private $comparedFragmentPairs;
+    protected PairSet $comparedFragmentPairs;
 
     /**
      * A cache for the "field map" and list of fragment names found in any given
      * selection set. Selection sets may be asked for this information multiple
      * times, so this improves the performance of this validator.
-     *
-     * @var SplObjectStorage
      */
-    private $cachedFieldsAndFragmentNames;
+    protected SplObjectStorage $cachedFieldsAndFragmentNames;
 
     public function getVisitor(ValidationContext $context)
     {
@@ -72,7 +68,7 @@ public function getVisitor(ValidationContext $context)
                     [[$responseName, $reason], $fields1, $fields2] = $conflict;
 
                     $context->reportError(new Error(
-                        self::fieldsConflictMessage($responseName, $reason),
+                        static::fieldsConflictMessage($responseName, $reason),
                         array_merge($fields1, $fields2)
                     ));
                 }
@@ -89,7 +85,7 @@ public function getVisitor(ValidationContext $context)
      *
      * @return mixed[]
      */
-    private function findConflictsWithinSelectionSet(
+    protected function findConflictsWithinSelectionSet(
         ValidationContext $context,
         $parentType,
         SelectionSetNode $selectionSet
@@ -152,7 +148,7 @@ private function findConflictsWithinSelectionSet(
      *
      * @return mixed[]|SplObjectStorage
      */
-    private function getFieldsAndFragmentNames(
+    protected function getFieldsAndFragmentNames(
         ValidationContext $context,
         $parentType,
         SelectionSetNode $selectionSet
@@ -239,7 +235,7 @@ private function getFieldsAndFragmentNames(
      * @param mixed[][][]   $astAndDefs
      * @param bool[]        $fragmentNames
      */
-    private function internalCollectFieldsAndFragmentNames(
+    protected function internalCollectFieldsAndFragmentNames(
         ValidationContext $context,
         $parentType,
         SelectionSetNode $selectionSet,
@@ -296,7 +292,7 @@ private function internalCollectFieldsAndFragmentNames(
      * @param mixed[][] $conflicts
      * @param mixed[][] $fieldMap
      */
-    private function collectConflictsWithin(
+    protected function collectConflictsWithin(
         ValidationContext $context,
         array &$conflicts,
         array $fieldMap
@@ -344,7 +340,7 @@ private function collectConflictsWithin(
      *
      * @return mixed[]|null
      */
-    private function findConflict(
+    protected function findConflict(
         ValidationContext $context,
         $parentFieldsAreMutuallyExclusive,
         $responseName,
@@ -433,7 +429,7 @@ private function findConflict(
      * @param NodeList<ArgumentNode> $arguments1 keep
      * @param NodeList<ArgumentNode> $arguments2 keep
      */
-    private function sameArguments(NodeList $arguments1, NodeList $arguments2): bool
+    protected function sameArguments(NodeList $arguments1, NodeList $arguments2): bool
     {
         if (count($arguments1) !== count($arguments2)) {
             return false;
@@ -463,7 +459,7 @@ private function sameArguments(NodeList $arguments1, NodeList $arguments2): bool
     /**
      * @return bool
      */
-    private function sameValue(Node $value1, Node $value2)
+    protected function sameValue(Node $value1, Node $value2)
     {
         return (! $value1 && ! $value2) || (Printer::doPrint($value1) === Printer::doPrint($value2));
     }
@@ -473,7 +469,7 @@ private function sameValue(Node $value1, Node $value2)
      * Composite types are ignored as their individual field types will be compared
      * later recursively. However List and Non-Null types must match.
      */
-    private function doTypesConflict(Type $type1, Type $type2): bool
+    protected function doTypesConflict(Type $type1, Type $type2): bool
     {
         if ($type1 instanceof ListOfType) {
             return $type2 instanceof ListOfType
@@ -517,7 +513,7 @@ private function doTypesConflict(Type $type1, Type $type2): bool
      *
      * @return mixed[][]
      */
-    private function findConflictsBetweenSubSelectionSets(
+    protected function findConflictsBetweenSubSelectionSets(
         ValidationContext $context,
         $areMutuallyExclusive,
         $parentType1,
@@ -611,7 +607,7 @@ private function findConflictsBetweenSubSelectionSets(
      * @param mixed[]   $fieldMap1
      * @param mixed[]   $fieldMap2
      */
-    private function collectConflictsBetween(
+    protected function collectConflictsBetween(
         ValidationContext $context,
         array &$conflicts,
         $parentFieldsAreMutuallyExclusive,
@@ -660,7 +656,7 @@ private function collectConflictsBetween(
      * @param mixed[][] $fieldMap
      * @param string    $fragmentName
      */
-    private function collectConflictsBetweenFieldsAndFragment(
+    protected function collectConflictsBetweenFieldsAndFragment(
         ValidationContext $context,
         array &$conflicts,
         array &$comparedFragments,
@@ -719,7 +715,7 @@ private function collectConflictsBetweenFieldsAndFragment(
      *
      * @return mixed[]|SplObjectStorage
      */
-    private function getReferencedFieldsAndFragmentNames(
+    protected function getReferencedFieldsAndFragmentNames(
         ValidationContext $context,
         FragmentDefinitionNode $fragment
     ) {
@@ -746,7 +742,7 @@ private function getReferencedFieldsAndFragmentNames(
      * @param string    $fragmentName1
      * @param string    $fragmentName2
      */
-    private function collectConflictsBetweenFragments(
+    protected function collectConflictsBetweenFragments(
         ValidationContext $context,
         array &$conflicts,
         $areMutuallyExclusive,
@@ -836,7 +832,7 @@ private function collectConflictsBetweenFragments(
      *
      * @return mixed[]|null
      */
-    private function subfieldConflicts(
+    protected function subfieldConflicts(
         array $conflicts,
         $responseName,
         FieldNode $ast1,
@@ -879,7 +875,7 @@ static function ($allFields, $conflict): array {
      */
     public static function fieldsConflictMessage($responseName, $reason)
     {
-        $reasonMessage = self::reasonMessage($reason);
+        $reasonMessage = static::reasonMessage($reason);
 
         return sprintf(
             'Fields "%s" conflict because %s. Use different aliases on the fields to fetch both if this was intentional.',
@@ -895,7 +891,7 @@ public static function reasonMessage($reason)
                 static function ($tmp): string {
                     [$responseName, $subReason] = $tmp;
 
-                    $reasonMessage = self::reasonMessage($subReason);
+                    $reasonMessage = static::reasonMessage($subReason);
 
                     return sprintf('subfields "%s" conflict because %s', $responseName, $reasonMessage);
                 },
diff --git a/src/Validator/Rules/PossibleFragmentSpreads.php b/src/Validator/Rules/PossibleFragmentSpreads.php
index 5c744a06f..be7352324 100644
--- a/src/Validator/Rules/PossibleFragmentSpreads.php
+++ b/src/Validator/Rules/PossibleFragmentSpreads.php
@@ -37,7 +37,7 @@ public function getVisitor(ValidationContext $context)
                 }
 
                 $context->reportError(new Error(
-                    self::typeIncompatibleAnonSpreadMessage($parentType, $fragType),
+                    static::typeIncompatibleAnonSpreadMessage($parentType, $fragType),
                     [$node]
                 ));
             },
@@ -55,14 +55,14 @@ public function getVisitor(ValidationContext $context)
                 }
 
                 $context->reportError(new Error(
-                    self::typeIncompatibleSpreadMessage($fragName, $parentType, $fragType),
+                    static::typeIncompatibleSpreadMessage($fragName, $parentType, $fragType),
                     [$node]
                 ));
             },
         ];
     }
 
-    private function doTypesOverlap(Schema $schema, CompositeType $fragType, CompositeType $parentType)
+    protected function doTypesOverlap(Schema $schema, CompositeType $fragType, CompositeType $parentType)
     {
         // Checking in the order of the most frequently used scenarios:
         // Parent type === fragment type
@@ -139,7 +139,7 @@ public static function typeIncompatibleAnonSpreadMessage($parentType, $fragType)
         );
     }
 
-    private function getFragmentType(ValidationContext $context, $name)
+    protected function getFragmentType(ValidationContext $context, $name)
     {
         $frag = $context->getFragment($name);
         if ($frag) {
diff --git a/src/Validator/Rules/ProvidedRequiredArguments.php b/src/Validator/Rules/ProvidedRequiredArguments.php
index 5f6773edb..362fa3304 100644
--- a/src/Validator/Rules/ProvidedRequiredArguments.php
+++ b/src/Validator/Rules/ProvidedRequiredArguments.php
@@ -42,7 +42,7 @@ public function getVisitor(ValidationContext $context)
                         }
 
                         $context->reportError(new Error(
-                            self::missingFieldArgMessage($fieldNode->name->value, $argDef->name, $argDef->getType()),
+                            static::missingFieldArgMessage($fieldNode->name->value, $argDef->name, $argDef->getType()),
                             [$fieldNode]
                         ));
                     }
diff --git a/src/Validator/Rules/QueryComplexity.php b/src/Validator/Rules/QueryComplexity.php
index 1f35876df..29ef92302 100644
--- a/src/Validator/Rules/QueryComplexity.php
+++ b/src/Validator/Rules/QueryComplexity.php
@@ -30,23 +30,19 @@
 
 class QueryComplexity extends QuerySecurityRule
 {
-    /** @var int */
-    private $maxQueryComplexity;
+    protected int $maxQueryComplexity;
 
     /** @var array<string, mixed> */
-    private $rawVariableValues = [];
+    protected array $rawVariableValues = [];
 
     /** @var NodeList<VariableDefinitionNode> */
-    private $variableDefs;
+    protected NodeList $variableDefs;
 
-    /** @var ArrayObject */
-    private $fieldNodeAndDefs;
+    protected ArrayObject $fieldNodeAndDefs;
 
-    /** @var ValidationContext */
-    private $context;
+    protected ValidationContext $context;
 
-    /** @var int */
-    private $complexity;
+    protected int $complexity;
 
     public function __construct($maxQueryComplexity)
     {
@@ -94,7 +90,7 @@ public function getVisitor(ValidationContext $context)
                         }
 
                         $context->reportError(
-                            new Error(self::maxQueryComplexityErrorMessage(
+                            new Error(static::maxQueryComplexityErrorMessage(
                                 $this->getMaxQueryComplexity(),
                                 $this->getQueryComplexity()
                             ))
@@ -105,7 +101,7 @@ public function getVisitor(ValidationContext $context)
         );
     }
 
-    private function fieldComplexity($node, $complexity = 0)
+    protected function fieldComplexity($node, $complexity = 0)
     {
         if (isset($node->selectionSet) && $node->selectionSet instanceof SelectionSetNode) {
             foreach ($node->selectionSet->selections as $childNode) {
@@ -116,7 +112,7 @@ private function fieldComplexity($node, $complexity = 0)
         return $complexity;
     }
 
-    private function nodeComplexity(Node $node, $complexity = 0)
+    protected function nodeComplexity(Node $node, $complexity = 0)
     {
         switch (true) {
             case $node instanceof FieldNode:
@@ -171,7 +167,7 @@ private function nodeComplexity(Node $node, $complexity = 0)
         return $complexity;
     }
 
-    private function astFieldInfo(FieldNode $field)
+    protected function astFieldInfo(FieldNode $field)
     {
         $fieldName    = $this->getFieldName($field);
         $astFieldInfo = [null, null];
@@ -187,7 +183,7 @@ private function astFieldInfo(FieldNode $field)
         return $astFieldInfo;
     }
 
-    private function directiveExcludesField(FieldNode $node)
+    protected function directiveExcludesField(FieldNode $node)
     {
         foreach ($node->directives as $directiveNode) {
             if ($directiveNode->name->value === 'deprecated') {
@@ -247,7 +243,7 @@ public function setRawVariableValues(?array $rawVariableValues = null): void
         $this->rawVariableValues = $rawVariableValues ?? [];
     }
 
-    private function buildFieldArguments(FieldNode $node)
+    protected function buildFieldArguments(FieldNode $node)
     {
         $rawVariableValues = $this->getRawVariableValues();
         $astFieldInfo      = $this->astFieldInfo($node);
diff --git a/src/Validator/Rules/QueryDepth.php b/src/Validator/Rules/QueryDepth.php
index 88c5e2052..a2a288021 100644
--- a/src/Validator/Rules/QueryDepth.php
+++ b/src/Validator/Rules/QueryDepth.php
@@ -18,8 +18,7 @@
 
 class QueryDepth extends QuerySecurityRule
 {
-    /** @var int */
-    private $maxQueryDepth;
+    protected int $maxQueryDepth;
 
     public function __construct($maxQueryDepth)
     {
@@ -40,7 +39,7 @@ public function getVisitor(ValidationContext $context)
                         }
 
                         $context->reportError(
-                            new Error(self::maxQueryDepthErrorMessage($this->getMaxQueryDepth(), $maxDepth))
+                            new Error(static::maxQueryDepthErrorMessage($this->getMaxQueryDepth(), $maxDepth))
                         );
                     },
                 ],
@@ -48,7 +47,7 @@ public function getVisitor(ValidationContext $context)
         );
     }
 
-    private function fieldDepth($node, $depth = 0, $maxDepth = 0)
+    protected function fieldDepth($node, $depth = 0, $maxDepth = 0)
     {
         if (isset($node->selectionSet) && $node->selectionSet instanceof SelectionSetNode) {
             foreach ($node->selectionSet->selections as $childNode) {
@@ -59,7 +58,7 @@ private function fieldDepth($node, $depth = 0, $maxDepth = 0)
         return $maxDepth;
     }
 
-    private function nodeDepth(Node $node, $depth = 0, $maxDepth = 0)
+    protected function nodeDepth(Node $node, $depth = 0, $maxDepth = 0)
     {
         switch (true) {
             case $node instanceof FieldNode:
diff --git a/src/Validator/Rules/QuerySecurityRule.php b/src/Validator/Rules/QuerySecurityRule.php
index 5e14d5971..bc5ef1bb9 100644
--- a/src/Validator/Rules/QuerySecurityRule.php
+++ b/src/Validator/Rules/QuerySecurityRule.php
@@ -25,7 +25,7 @@ abstract class QuerySecurityRule extends ValidationRule
     public const DISABLED = 0;
 
     /** @var FragmentDefinitionNode[] */
-    private $fragments = [];
+    protected array $fragments = [];
 
     /**
      * check if equal to 0 no check is done. Must be greater or equal to 0.
diff --git a/src/Validator/Rules/ScalarLeafs.php b/src/Validator/Rules/ScalarLeafs.php
index a18aed868..c56990669 100644
--- a/src/Validator/Rules/ScalarLeafs.php
+++ b/src/Validator/Rules/ScalarLeafs.php
@@ -26,13 +26,13 @@ public function getVisitor(ValidationContext $context)
                 if (Type::isLeafType(Type::getNamedType($type))) {
                     if ($node->selectionSet) {
                         $context->reportError(new Error(
-                            self::noSubselectionAllowedMessage($node->name->value, $type),
+                            static::noSubselectionAllowedMessage($node->name->value, $type),
                             [$node->selectionSet]
                         ));
                     }
                 } elseif (! $node->selectionSet) {
                     $context->reportError(new Error(
-                        self::requiredSubselectionMessage($node->name->value, $type),
+                        static::requiredSubselectionMessage($node->name->value, $type),
                         [$node]
                     ));
                 }
diff --git a/src/Validator/Rules/SingleFieldSubscription.php b/src/Validator/Rules/SingleFieldSubscription.php
index 2381e2015..7b9e7df71 100644
--- a/src/Validator/Rules/SingleFieldSubscription.php
+++ b/src/Validator/Rules/SingleFieldSubscription.php
@@ -36,7 +36,7 @@ public function getVisitor(ValidationContext $context): array
                         }
 
                         $context->reportError(new Error(
-                            self::multipleFieldsInOperation($node->name->value ?? null),
+                            static::multipleFieldsInOperation($node->name->value ?? null),
                             $offendingSelections
                         ));
                     }
diff --git a/src/Validator/Rules/UniqueArgumentNames.php b/src/Validator/Rules/UniqueArgumentNames.php
index 35f16bb58..3597cb7df 100644
--- a/src/Validator/Rules/UniqueArgumentNames.php
+++ b/src/Validator/Rules/UniqueArgumentNames.php
@@ -19,7 +19,7 @@
 class UniqueArgumentNames extends ValidationRule
 {
     /** @var NameNode[] */
-    public $knownArgNames;
+    protected array $knownArgNames;
 
     public function getSDLVisitor(SDLValidationContext $context)
     {
@@ -46,7 +46,7 @@ public function getASTVisitor(ASTValidationContext $context)
                 $argName = $node->name->value;
                 if ($this->knownArgNames[$argName] ?? false) {
                     $context->reportError(new Error(
-                        self::duplicateArgMessage($argName),
+                        static::duplicateArgMessage($argName),
                         [$this->knownArgNames[$argName], $node->name]
                     ));
                 } else {
diff --git a/src/Validator/Rules/UniqueDirectivesPerLocation.php b/src/Validator/Rules/UniqueDirectivesPerLocation.php
index 2ffab6e77..6722d34c1 100644
--- a/src/Validator/Rules/UniqueDirectivesPerLocation.php
+++ b/src/Validator/Rules/UniqueDirectivesPerLocation.php
@@ -72,7 +72,7 @@ public function getASTVisitor(ASTValidationContext $context)
 
                     if (isset($knownDirectives[$directiveName])) {
                         $context->reportError(new Error(
-                            self::duplicateDirectiveMessage($directiveName),
+                            static::duplicateDirectiveMessage($directiveName),
                             [$knownDirectives[$directiveName], $directive]
                         ));
                     } else {
diff --git a/src/Validator/Rules/UniqueFragmentNames.php b/src/Validator/Rules/UniqueFragmentNames.php
index 1203faeca..336d6c597 100644
--- a/src/Validator/Rules/UniqueFragmentNames.php
+++ b/src/Validator/Rules/UniqueFragmentNames.php
@@ -17,7 +17,7 @@
 class UniqueFragmentNames extends ValidationRule
 {
     /** @var NameNode[] */
-    public $knownFragmentNames;
+    protected array $knownFragmentNames;
 
     public function getVisitor(ValidationContext $context)
     {
@@ -33,7 +33,7 @@ public function getVisitor(ValidationContext $context)
                     $this->knownFragmentNames[$fragmentName] = $node->name;
                 } else {
                     $context->reportError(new Error(
-                        self::duplicateFragmentNameMessage($fragmentName),
+                        static::duplicateFragmentNameMessage($fragmentName),
                         [$this->knownFragmentNames[$fragmentName], $node->name]
                     ));
                 }
diff --git a/src/Validator/Rules/UniqueInputFieldNames.php b/src/Validator/Rules/UniqueInputFieldNames.php
index d4e08b654..ff51bd63f 100644
--- a/src/Validator/Rules/UniqueInputFieldNames.php
+++ b/src/Validator/Rules/UniqueInputFieldNames.php
@@ -20,10 +20,10 @@
 class UniqueInputFieldNames extends ValidationRule
 {
     /** @var array<string, NameNode> */
-    public $knownNames;
+    protected array $knownNames;
 
     /** @var array<array<string, NameNode>> */
-    public $knownNameStack;
+    protected array $knownNameStack;
 
     public function getVisitor(ValidationContext $context)
     {
@@ -55,7 +55,7 @@ public function getASTVisitor(ASTValidationContext $context)
 
                 if (isset($this->knownNames[$fieldName])) {
                     $context->reportError(new Error(
-                        self::duplicateInputFieldMessage($fieldName),
+                        static::duplicateInputFieldMessage($fieldName),
                         [$this->knownNames[$fieldName], $node->name]
                     ));
                 } else {
diff --git a/src/Validator/Rules/UniqueOperationNames.php b/src/Validator/Rules/UniqueOperationNames.php
index 70b02e4bd..899b5e206 100644
--- a/src/Validator/Rules/UniqueOperationNames.php
+++ b/src/Validator/Rules/UniqueOperationNames.php
@@ -17,7 +17,7 @@
 class UniqueOperationNames extends ValidationRule
 {
     /** @var NameNode[] */
-    public $knownOperationNames;
+    protected array $knownOperationNames;
 
     public function getVisitor(ValidationContext $context)
     {
@@ -32,7 +32,7 @@ public function getVisitor(ValidationContext $context)
                         $this->knownOperationNames[$operationName->value] = $operationName;
                     } else {
                         $context->reportError(new Error(
-                            self::duplicateOperationNameMessage($operationName->value),
+                            static::duplicateOperationNameMessage($operationName->value),
                             [$this->knownOperationNames[$operationName->value], $operationName]
                         ));
                     }
diff --git a/src/Validator/Rules/UniqueVariableNames.php b/src/Validator/Rules/UniqueVariableNames.php
index 76661296e..6eabb7f9a 100644
--- a/src/Validator/Rules/UniqueVariableNames.php
+++ b/src/Validator/Rules/UniqueVariableNames.php
@@ -15,7 +15,7 @@
 class UniqueVariableNames extends ValidationRule
 {
     /** @var NameNode[] */
-    public $knownVariableNames;
+    protected array $knownVariableNames;
 
     public function getVisitor(ValidationContext $context)
     {
@@ -31,7 +31,7 @@ public function getVisitor(ValidationContext $context)
                     $this->knownVariableNames[$variableName] = $node->variable->name;
                 } else {
                     $context->reportError(new Error(
-                        self::duplicateVariableMessage($variableName),
+                        static::duplicateVariableMessage($variableName),
                         [$this->knownVariableNames[$variableName], $node->variable->name]
                     ));
                 }
diff --git a/src/Validator/Rules/ValidationRule.php b/src/Validator/Rules/ValidationRule.php
index 3e9d01725..a6e50496f 100644
--- a/src/Validator/Rules/ValidationRule.php
+++ b/src/Validator/Rules/ValidationRule.php
@@ -4,6 +4,8 @@
 
 namespace GraphQL\Validator\Rules;
 
+use GraphQL\Language\AST\Node;
+use GraphQL\Language\VisitorOperation;
 use GraphQL\Validator\SDLValidationContext;
 use GraphQL\Validator\ValidationContext;
 
@@ -11,14 +13,18 @@
 
 abstract class ValidationRule
 {
-    /** @var string */
-    protected $name;
+    protected string $name;
 
-    public function getName()
+    public function getName(): string
     {
-        return $this->name === '' || $this->name === null  ? static::class : $this->name;
+        return $this->name === '' || $this->name === null
+            ? static::class
+            : $this->name;
     }
 
+    /**
+     * @return array<string, callable(Node): VisitorOperation|mixed|null>|array<string, array<string, callable(Node): VisitorOperation|mixed|null>>
+     */
     public function __invoke(ValidationContext $context)
     {
         return $this->getVisitor($context);
@@ -29,7 +35,7 @@ public function __invoke(ValidationContext $context)
      *
      * @see \GraphQL\Language\Visitor
      *
-     * @return mixed[]
+     * @return array<string, callable(Node): VisitorOperation|mixed|null>|array<string, array<string, callable(Node): VisitorOperation|mixed|null>>
      */
     public function getVisitor(ValidationContext $context)
     {
@@ -41,7 +47,7 @@ public function getVisitor(ValidationContext $context)
      *
      * @see \GraphQL\Language\Visitor
      *
-     * @return mixed[]
+     * @return array<string, callable(Node): VisitorOperation|mixed|null>|array<string, array<string, callable(Node): VisitorOperation|mixed|null>>
      */
     public function getSDLVisitor(SDLValidationContext $context)
     {
diff --git a/src/Validator/Rules/ValuesOfCorrectType.php b/src/Validator/Rules/ValuesOfCorrectType.php
index 7cff73219..30071effc 100644
--- a/src/Validator/Rules/ValuesOfCorrectType.php
+++ b/src/Validator/Rules/ValuesOfCorrectType.php
@@ -65,7 +65,7 @@ public function getVisitor(ValidationContext $context)
 
                 $context->reportError(
                     new Error(
-                        self::getBadValueMessage((string) $type, Printer::doPrint($node), null, $context, $fieldName),
+                        static::getBadValueMessage((string) $type, Printer::doPrint($node), null, $context, $fieldName),
                         $node
                     )
                 );
@@ -113,7 +113,7 @@ static function ($field): string {
 
                     $context->reportError(
                         new Error(
-                            self::requiredFieldMessage($type->name, $fieldName, (string) $fieldType),
+                            static::requiredFieldMessage($type->name, $fieldName, (string) $fieldType),
                             $node
                         )
                     );
@@ -139,7 +139,7 @@ static function ($field): string {
 
                 $context->reportError(
                     new Error(
-                        self::unknownFieldMessage($parentType->name, $node->name->value, $didYouMean),
+                        static::unknownFieldMessage($parentType->name, $node->name->value, $didYouMean),
                         $node
                     )
                 );
@@ -151,7 +151,7 @@ static function ($field): string {
                 } elseif (! $type->getValue($node->value)) {
                     $context->reportError(
                         new Error(
-                            self::getBadValueMessage(
+                            static::getBadValueMessage(
                                 $type->name,
                                 Printer::doPrint($node),
                                 $this->enumTypeSuggestion($type, $node),
@@ -187,7 +187,7 @@ public static function badValueMessage($typeName, $valueName, $message = null)
     /**
      * @param VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode $node
      */
-    private function isValidScalar(ValidationContext $context, ValueNode $node, $fieldName)
+    protected function isValidScalar(ValidationContext $context, ValueNode $node, $fieldName)
     {
         // Report any error at the full type expected by the location.
         /** @var ScalarType|EnumType|InputObjectType|ListOfType|NonNull $locationType */
@@ -202,7 +202,7 @@ private function isValidScalar(ValidationContext $context, ValueNode $node, $fie
         if (! $type instanceof ScalarType) {
             $context->reportError(
                 new Error(
-                    self::getBadValueMessage(
+                    static::getBadValueMessage(
                         (string) $locationType,
                         Printer::doPrint($node),
                         $this->enumTypeSuggestion($type, $node),
@@ -224,7 +224,7 @@ private function isValidScalar(ValidationContext $context, ValueNode $node, $fie
             // Ensure a reference to the original error is maintained.
             $context->reportError(
                 new Error(
-                    self::getBadValueMessage(
+                    static::getBadValueMessage(
                         (string) $locationType,
                         Printer::doPrint($node),
                         $error->getMessage(),
@@ -244,7 +244,7 @@ private function isValidScalar(ValidationContext $context, ValueNode $node, $fie
     /**
      * @param VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode $node
      */
-    private function enumTypeSuggestion($type, ValueNode $node)
+    protected function enumTypeSuggestion($type, ValueNode $node)
     {
         if ($type instanceof EnumType) {
             $suggestions = Utils::suggestionList(
@@ -278,15 +278,15 @@ public static function unknownFieldMessage($typeName, $fieldName, $message = nul
             ($message ? sprintf('; %s', $message) : '.');
     }
 
-    private static function getBadValueMessage($typeName, $valueName, $message = null, $context = null, $fieldName = null)
+    protected static function getBadValueMessage($typeName, $valueName, $message = null, $context = null, $fieldName = null)
     {
         if ($context) {
             $arg = $context->getArgument();
             if ($arg) {
-                return self::badArgumentValueMessage($typeName, $valueName, $fieldName, $arg->name, $message);
+                return static::badArgumentValueMessage($typeName, $valueName, $fieldName, $arg->name, $message);
             }
         }
 
-        return self::badValueMessage($typeName, $valueName, $message);
+        return static::badValueMessage($typeName, $valueName, $message);
     }
 }
diff --git a/src/Validator/Rules/VariablesAreInputTypes.php b/src/Validator/Rules/VariablesAreInputTypes.php
index b96824403..300236217 100644
--- a/src/Validator/Rules/VariablesAreInputTypes.php
+++ b/src/Validator/Rules/VariablesAreInputTypes.php
@@ -29,7 +29,7 @@ public function getVisitor(ValidationContext $context)
 
                 $variableName = $node->variable->name->value;
                 $context->reportError(new Error(
-                    self::nonInputTypeOnVarMessage($variableName, Printer::doPrint($node->type)),
+                    static::nonInputTypeOnVarMessage($variableName, Printer::doPrint($node->type)),
                     [$node->type]
                 ));
             },
diff --git a/src/Validator/Rules/VariablesInAllowedPosition.php b/src/Validator/Rules/VariablesInAllowedPosition.php
index fefb42fac..cbd8ddf72 100644
--- a/src/Validator/Rules/VariablesInAllowedPosition.php
+++ b/src/Validator/Rules/VariablesInAllowedPosition.php
@@ -27,7 +27,7 @@ class VariablesInAllowedPosition extends ValidationRule
      *
      * @var VariableDefinitionNode[]
      */
-    public $varDefMap;
+    protected array $varDefMap;
 
     public function getVisitor(ValidationContext $context)
     {
@@ -63,7 +63,7 @@ public function getVisitor(ValidationContext $context)
                         }
 
                         $context->reportError(new Error(
-                            self::badVarPosMessage($varName, $varType, $type),
+                            static::badVarPosMessage($varName, $varType, $type),
                             [$varDef, $node]
                         ));
                     }
@@ -99,7 +99,7 @@ public static function badVarPosMessage($varName, $varType, $expectedType)
      * @param ValueNode|null $varDefaultValue
      * @param mixed          $locationDefaultValue
      */
-    private function allowedVariableUsage(Schema $schema, Type $varType, $varDefaultValue, Type $locationType, $locationDefaultValue): bool
+    protected function allowedVariableUsage(Schema $schema, Type $varType, $varDefaultValue, Type $locationType, $locationDefaultValue): bool
     {
         if ($locationType instanceof NonNull && ! $varType instanceof NonNull) {
             $hasNonNullVariableDefaultValue = $varDefaultValue && ! $varDefaultValue instanceof NullValueNode;


Test patch: