Passed
Pull Request — master (#69)
by Christoffer
02:03
created

findConflictsBetweenSubSelectionSets()   C

Complexity

Conditions 7
Paths 12

Size

Total Lines 74
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 40
nc 12
nop 5
dl 0
loc 74
rs 6.6374
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Digia\GraphQL\Validation\Conflict;
4
5
use Digia\GraphQL\Error\InvalidTypeException;
6
use Digia\GraphQL\Language\AST\Node\FieldNode;
7
use Digia\GraphQL\Language\AST\Node\FragmentDefinitionNode;
8
use Digia\GraphQL\Language\AST\Node\FragmentSpreadNode;
9
use Digia\GraphQL\Language\AST\Node\InlineFragmentNode;
10
use Digia\GraphQL\Language\AST\Node\SelectionSetNode;
11
use Digia\GraphQL\Type\Definition\InterfaceType;
12
use Digia\GraphQL\Type\Definition\NamedTypeInterface;
13
use Digia\GraphQL\Type\Definition\ObjectType;
14
use Digia\GraphQL\Validation\ValidationContextInterface;
15
use function Digia\GraphQL\Type\getNamedType;
16
use function Digia\GraphQL\Util\typeFromAST;
17
use function Digia\GraphQL\Validation\compareArguments;
18
use function Digia\GraphQL\Validation\compareTypes;
19
20
/**
21
 * Algorithm:
22
 *
23
 * Conflicts occur when two fields exist in a query which will produce the same
24
 * response name, but represent differing values, thus creating a conflict.
25
 * The algorithm below finds all conflicts via making a series of comparisons
26
 * between fields. In order to compare as few fields as possible, this makes
27
 * a series of comparisons "within" sets of fields and "between" sets of fields.
28
 *
29
 * Given any selection set, a collection produces both a set of fields by
30
 * also including all inline fragments, as well as a list of fragments
31
 * referenced by fragment spreads.
32
 *
33
 * A) Each selection set represented in the document first compares "within" its
34
 * collected set of fields, finding any conflicts between every pair of
35
 * overlapping fields.
36
 * Note: This is the *only time* that a the fields "within" a set are compared
37
 * to each other. After this only fields "between" sets are compared.
38
 *
39
 * B) Also, if any fragment is referenced in a selection set, then a
40
 * comparison is made "between" the original set of fields and the
41
 * referenced fragment.
42
 *
43
 * C) Also, if multiple fragments are referenced, then comparisons
44
 * are made "between" each referenced fragment.
45
 *
46
 * D) When comparing "between" a set of fields and a referenced fragment, first
47
 * a comparison is made between each field in the original set of fields and
48
 * each field in the the referenced set of fields.
49
 *
50
 * E) Also, if any fragment is referenced in the referenced selection set,
51
 * then a comparison is made "between" the original set of fields and the
52
 * referenced fragment (recursively referring to step D).
53
 *
54
 * F) When comparing "between" two fragments, first a comparison is made between
55
 * each field in the first referenced set of fields and each field in the the
56
 * second referenced set of fields.
57
 *
58
 * G) Also, any fragments referenced by the first must be compared to the
59
 * second, and any fragments referenced by the second must be compared to the
60
 * first (recursively referring to step F).
61
 *
62
 * H) When comparing two fields, if both have selection sets, then a comparison
63
 * is made "between" both selection sets, first comparing the set of fields in
64
 * the first selection set with the set of fields in the second.
65
 *
66
 * I) Also, if any fragment is referenced in either selection set, then a
67
 * comparison is made "between" the other set of fields and the
68
 * referenced fragment.
69
 *
70
 * J) Also, if two fragments are referenced in both selection sets, then a
71
 * comparison is made "between" the two fragments.
72
 *
73
 */
74
trait FindsConflictsTrait
75
{
76
    /**
77
     * A cache for the "field map" and list of fragment names found in any given
78
     * selection set. Selection sets may be asked for this information multiple
79
     * times, so this improves the performance of this validator.
80
     */
81
    protected $cachedFieldsAndFragmentNames = [];
82
83
    /**
84
     * A memoization for when two fragments are compared "between" each other for
85
     * conflicts. Two fragments may be compared many times, so memoizing this can
86
     * dramatically improve the performance of this validator.
87
     *
88
     * @var PairSet
89
     */
90
    protected $comparedFragmentPairs;
91
92
    /**
93
     * @return ValidationContextInterface
94
     */
95
    abstract public function getValidationContext(): ValidationContextInterface;
96
97
    /**
98
     * @param SelectionSetNode        $selectionSet
99
     * @param NamedTypeInterface|null $parentType
100
     * @return array|Conflict[]
101
     * @throws InvalidTypeException
102
     */
103
    protected function findConflictsWithinSelectionSet(
104
        SelectionSetNode $selectionSet,
105
        ?NamedTypeInterface $parentType = null
106
    ): array {
107
        $this->comparedFragmentPairs = new PairSet();
108
109
        $context = $this->getFieldsAndFragmentNames($selectionSet, $parentType);
110
111
        // (A) Find find all conflicts "within" the fields of this selection set.
112
        // Note: this is the *only place* `collectConflictsWithin` is called.
113
        $this->collectConflictsWithin($context);
114
115
        $fieldMap      = $context->getFieldMap();
116
        $fragmentNames = $context->getFragmentNames();
117
118
        // (B) Then collect conflicts between these fields and those represented by
119
        // each spread fragment name found.
120
        if (!empty($fragmentNames)) {
121
            $fragmentNamesCount = \count($fragmentNames);
122
            $comparedFragments  = [];
123
124
            /** @noinspection ForeachInvariantsInspection */
125
            for ($i = 0; $i < $fragmentNamesCount; $i++) {
126
                $this->collectConflictsBetweenFieldsAndFragment(
127
                    $context,
128
                    $comparedFragments,
129
                    $fieldMap,
130
                    $fragmentNames[$i],
131
                    false/* $areMutuallyExclusive */
132
                );
133
134
                // (C) Then compare this fragment with all other fragments found in this
135
                // selection set to collect conflicts between fragments spread together.
136
                // This compares each item in the list of fragment names to every other
137
                // item in that same list (except for itself).
138
                for ($j = $i + 1; $j < $fragmentNamesCount; $j++) {
139
                    $this->collectConflictsBetweenFragments(
140
                        $context,
141
                        $fragmentNames[$i],
142
                        $fragmentNames[$j],
143
                        false/* $areMutuallyExclusive */
144
                    );
145
                }
146
            }
147
        }
148
149
        return $context->getConflicts();
150
    }
151
152
    /**
153
     * Collect all conflicts found between a set of fields and a fragment reference
154
     * including via spreading in any nested fragments.
155
     *
156
     * @param ComparisonContext $context
157
     * @param array             $comparedFragments
158
     * @param array             $fieldMap
159
     * @param string            $fragmentName
160
     * @param bool              $areMutuallyExclusive
161
     * @throws InvalidTypeException
162
     */
163
    protected function collectConflictsBetweenFieldsAndFragment(
164
        ComparisonContext $context,
165
        array &$comparedFragments,
166
        array $fieldMap,
167
        string $fragmentName,
168
        bool $areMutuallyExclusive
169
    ): void {
170
        // Memoize so a fragment is not compared for conflicts more than once.
171
        if (isset($comparedFragments[$fragmentName])) {
172
            return;
173
        }
174
175
        $comparedFragments[$fragmentName] = true;
176
177
        $fragment = $this->getValidationContext()->getFragment($fragmentName);
178
179
        if (null === $fragment) {
180
            return;
181
        }
182
183
        $contextB = $this->getReferencedFieldsAndFragmentNames($fragment);
184
185
        $fieldMapB = $contextB->getFieldMap();
186
187
        // Do not compare a fragment's fieldMap to itself.
188
        if ($fieldMap === $fieldMapB) {
189
            return;
190
        }
191
192
        // (D) First collect any conflicts between the provided collection of fields
193
        // and the collection of fields represented by the given fragment.
194
        $this->collectConflictsBetween(
195
            $context,
196
            $fieldMap,
197
            $fieldMapB,
198
            $areMutuallyExclusive
199
        );
200
201
        $fragmentNamesB = $contextB->getFragmentNames();
202
203
        // (E) Then collect any conflicts between the provided collection of fields
204
        // and any fragment names found in the given fragment.
205
        if (!empty($fragmentNamesB)) {
206
            $fragmentNamesBCount = \count($fragmentNamesB);
207
208
            /** @noinspection ForeachInvariantsInspection */
209
            for ($i = 0; $i < $fragmentNamesBCount; $i++) {
210
                $this->collectConflictsBetweenFieldsAndFragment(
211
                    $context,
212
                    $comparedFragments,
213
                    $fieldMap,
214
                    $fragmentNamesB[$i],
215
                    $areMutuallyExclusive
216
                );
217
            }
218
        }
219
    }
220
221
    /**
222
     * Collect all conflicts found between two fragments, including via spreading in
223
     * any nested fragments.
224
     *
225
     * @param ComparisonContext $context
226
     * @param string            $fragmentNameA
227
     * @param string            $fragmentNameB
228
     * @param bool              $areMutuallyExclusive
229
     * @throws InvalidTypeException
230
     */
231
    protected function collectConflictsBetweenFragments(
232
        ComparisonContext $context,
233
        string $fragmentNameA,
234
        string $fragmentNameB,
235
        bool $areMutuallyExclusive
236
    ): void {
237
        // No need to compare a fragment to itself.
238
        if ($fragmentNameA === $fragmentNameB) {
239
            return;
240
        }
241
242
        // Memoize so two fragments are not compared for conflicts more than once.
243
        if ($this->comparedFragmentPairs->has($fragmentNameA, $fragmentNameB, $areMutuallyExclusive)) {
244
            return;
245
        }
246
247
        $this->comparedFragmentPairs->add($fragmentNameA, $fragmentNameB, $areMutuallyExclusive);
248
249
        $fragmentA = $this->getValidationContext()->getFragment($fragmentNameA);
250
        $fragmentB = $this->getValidationContext()->getFragment($fragmentNameB);
251
252
        if (null === $fragmentA || null === $fragmentB) {
253
            return;
254
        }
255
256
        $contextA = $this->getReferencedFieldsAndFragmentNames($fragmentA);
257
        $contextB = $this->getReferencedFieldsAndFragmentNames($fragmentB);
258
259
        // (F) First, collect all conflicts between these two collections of fields
260
        // (not including any nested fragments).
261
        $this->collectConflictsBetween(
262
            $context,
263
            $contextA->getFieldMap(),
264
            $contextB->getFieldMap(),
265
            $areMutuallyExclusive
266
        );
267
268
        $fragmentNamesB = $contextB->getFragmentNames();
269
270
        // (G) Then collect conflicts between the first fragment and any nested
271
        // fragments spread in the second fragment.
272
        if (!empty($fragmentNamesB)) {
273
            $fragmentNamesBCount = \count($fragmentNamesB);
274
275
            /** @noinspection ForeachInvariantsInspection */
276
            for ($j = 0; $j < $fragmentNamesBCount; $j++) {
277
                $this->collectConflictsBetweenFragments(
278
                    $context,
279
                    $fragmentNameA,
280
                    $fragmentNamesB[$j],
281
                    $areMutuallyExclusive
282
                );
283
            }
284
        }
285
286
        $fragmentNamesA = $contextA->getFragmentNames();
287
288
        // (G) Then collect conflicts between the second fragment and any nested
289
        // fragments spread in the first fragment.
290
        if (!empty($fragmentNamesA)) {
291
            $fragmentNamesACount = \count($fragmentNamesA);
292
293
            /** @noinspection ForeachInvariantsInspection */
294
            for ($i = 0; $i < $fragmentNamesACount; $i++) {
295
                $this->collectConflictsBetweenFragments(
296
                    $context,
297
                    $fragmentNamesA[$i],
298
                    $fragmentNameB,
299
                    $areMutuallyExclusive
300
                );
301
            }
302
        }
303
    }
304
305
    /**
306
     * Find all conflicts found between two selection sets, including those found
307
     * via spreading in fragments. Called when determining if conflicts exist
308
     * between the sub-fields of two overlapping fields.
309
     *
310
     * @param NamedTypeInterface|null $parentTypeA
311
     * @param SelectionSetNode        $selectionSetA
312
     * @param NamedTypeInterface|null $parentTypeB
313
     * @param SelectionSetNode        $selectionSetB
314
     * @param bool                    $areMutuallyExclusive
315
     * @return Conflict[]
316
     * @throws InvalidTypeException
317
     */
318
    protected function findConflictsBetweenSubSelectionSets(
319
        ?NamedTypeInterface $parentTypeA,
320
        SelectionSetNode $selectionSetA,
321
        ?NamedTypeInterface $parentTypeB,
322
        SelectionSetNode $selectionSetB,
323
        bool $areMutuallyExclusive
324
    ): array {
325
        $context = new ComparisonContext();
326
327
        $contextA            = $this->getFieldsAndFragmentNames($selectionSetA, $parentTypeA);
328
        $contextB            = $this->getFieldsAndFragmentNames($selectionSetB, $parentTypeB);
329
        $fieldMapA           = $contextA->getFieldMap();
330
        $fieldMapB           = $contextB->getFieldMap();
331
        $fragmentNamesA      = $contextA->getFragmentNames();
332
        $fragmentNamesB      = $contextB->getFragmentNames();
333
        $fragmentNamesACount = \count($fragmentNamesA);
334
        $fragmentNamesBCount = \count($fragmentNamesB);
335
336
        // (H) First, collect all conflicts between these two collections of field.
337
        $this->collectConflictsBetween(
338
            $context,
339
            $fieldMapA,
340
            $fieldMapB,
341
            $areMutuallyExclusive
342
        );
343
344
        // (I) Then collect conflicts between the first collection of fields and
345
        // those referenced by each fragment name associated with the second.
346
        if (!empty($fragmentNamesB)) {
347
            $comparedFragments = [];
348
349
            /** @noinspection ForeachInvariantsInspection */
350
            for ($j = 0; $j < $fragmentNamesBCount; $j++) {
351
                $this->collectConflictsBetweenFieldsAndFragment(
352
                    $context,
353
                    $comparedFragments,
354
                    $fieldMapA,
355
                    $fragmentNamesB[$j],
356
                    $areMutuallyExclusive
357
                );
358
            }
359
        }
360
361
        // (I) Then collect conflicts between the second collection of fields and
362
        // those referenced by each fragment name associated with the first.
363
        if (!empty($fragmentNamesA)) {
364
            $comparedFragments = [];
365
366
            /** @noinspection ForeachInvariantsInspection */
367
            for ($i = 0; $i < $fragmentNamesACount; $i++) {
368
                $this->collectConflictsBetweenFieldsAndFragment(
369
                    $context,
370
                    $comparedFragments,
371
                    $fieldMapB,
372
                    $fragmentNamesA[$i],
373
                    $areMutuallyExclusive
374
                );
375
            }
376
        }
377
378
        /** @noinspection ForeachInvariantsInspection */
379
        for ($i = 0; $i < $fragmentNamesACount; $i++) {
380
            /** @noinspection ForeachInvariantsInspection */
381
            for ($j = 0; $j < $fragmentNamesBCount; $j++) {
382
                $this->collectConflictsBetweenFragments(
383
                    $context,
384
                    $fragmentNamesA[$i],
385
                    $fragmentNamesB[$j],
386
                    $areMutuallyExclusive
387
                );
388
            }
389
        }
390
391
        return $context->getConflicts();
392
    }
393
394
    /**
395
     * Collect all Conflicts "within" one collection of fields.
396
     *
397
     * @param ComparisonContext $context
398
     */
399
    protected function collectConflictsWithin(ComparisonContext $context): void
400
    {
401
        // A field map is a keyed collection, where each key represents a response
402
        // name and the value at that key is a list of all fields which provide that
403
        // response name. For every response name, if there are multiple fields, they
404
        // must be compared to find a potential conflict.
405
        foreach ($context->getFieldMap() as $responseName => $fields) {
406
            $fieldsCount = \count($fields);
407
408
            // This compares every field in the list to every other field in this list
409
            // (except to itself). If the list only has one item, nothing needs to
410
            // be compared.
411
            if ($fieldsCount > 1) {
412
                /** @noinspection ForeachInvariantsInspection */
413
                for ($i = 0; $i < $fieldsCount; $i++) {
414
                    for ($j = $i + 1; $j < $fieldsCount; $j++) {
415
                        $conflict = $this->findConflict(
416
                            $responseName,
417
                            $fields[$i],
418
                            $fields[$j],
419
                            // within one collection is never mutually exclusive
420
                            false/* $areMutuallyExclusive */
421
                        );
422
423
                        if (null !== $conflict) {
424
                            $context->reportConflict($conflict);
425
                        }
426
                    }
427
                }
428
            }
429
        }
430
    }
431
432
    /**
433
     * Collect all Conflicts between two collections of fields. This is similar to,
434
     * but different from the `collectConflictsWithin` function above. This check
435
     * assumes that `collectConflictsWithin` has already been called on each
436
     * provided collection of fields. This is true because this validator traverses
437
     * each individual selection set.
438
     *
439
     * @param ComparisonContext $context
440
     * @param array             $fieldMapA
441
     * @param array             $fieldMapB
442
     * @param bool              $parentFieldsAreMutuallyExclusive
443
     */
444
    protected function collectConflictsBetween(
445
        ComparisonContext $context,
446
        array $fieldMapA,
447
        array $fieldMapB,
448
        bool $parentFieldsAreMutuallyExclusive
449
    ): void {
450
        // A field map is a keyed collection, where each key represents a response
451
        // name and the value at that key is a list of all fields which provide that
452
        // response name. For any response name which appears in both provided field
453
        // maps, each field from the first field map must be compared to every field
454
        // in the second field map to find potential conflicts.
455
        foreach ($fieldMapA as $responseName => $fieldA) {
456
            $fieldB = $fieldMapB[$responseName];
457
458
            if (null !== $fieldB) {
459
                $fieldsACount = \count($fieldA);
460
                $fieldsBCount = \count($fieldB);
461
                for ($i = 0; $i < $fieldsACount; $i++) {
462
                    for ($j = 0; $j < $fieldsBCount; $j++) {
463
                        $conflict = $this->findConflict(
464
                            $responseName,
465
                            $fieldA,
466
                            $fieldB,
467
                            $parentFieldsAreMutuallyExclusive
468
                        );
469
470
                        if (null !== $conflict) {
471
                            $context->reportConflict($conflict);
472
                        }
473
                    }
474
                }
475
            }
476
        }
477
    }
478
479
    /**
480
     * Determines if there is a conflict between two particular fields, including
481
     * comparing their sub-fields.
482
     *
483
     * @param string       $responseName
484
     * @param FieldContext $fieldA
485
     * @param FieldContext $fieldB
486
     * @param bool         $parentFieldsAreMutuallyExclusive
487
     * @return Conflict|null
488
     */
489
    protected function findConflict(
490
        string $responseName,
491
        FieldContext $fieldA,
492
        FieldContext $fieldB,
493
        bool $parentFieldsAreMutuallyExclusive
494
    ): ?Conflict {
495
        $parentTypeA = $fieldA->getParentType();
496
        $parentTypeB = $fieldB->getParentType();
497
498
        // If it is known that two fields could not possibly apply at the same
499
        // time, due to the parent types, then it is safe to permit them to diverge
500
        // in aliased field or arguments used as they will not present any ambiguity
501
        // by differing.
502
        // It is known that two parent types could never overlap if they are
503
        // different Object types. Interface or Union types might overlap - if not
504
        // in the current state of the schema, then perhaps in some future version,
505
        // thus may not safely diverge.
506
        $areMutuallyExclusive = $parentFieldsAreMutuallyExclusive
507
            || ($parentTypeA !== $parentTypeB
508
                && $parentTypeA instanceof ObjectType
509
                && $parentTypeB instanceof ObjectType);
510
511
        $nodeA = $fieldA->getNode();
512
        $nodeB = $fieldB->getNode();
513
514
        if (!$areMutuallyExclusive) {
515
            // Two aliases must refer to the same field.
516
            $nameA = $nodeA->getNameValue();
517
            $nameB = $nodeB->getNameValue();
518
519
            if ($nameA !== $nameB) {
520
                return new Conflict(
521
                    $responseName,
522
                    sprintf('%s and %s are different fields', $nameA, $nameB),
523
                    [$nodeA],
524
                    [$nodeB]
525
                );
526
            }
527
        }
528
529
        $definitionA = $fieldA->getDefinition();
530
        $definitionB = $fieldB->getDefinition();
531
532
        // Two field calls must have the same arguments.
533
        if (!compareArguments($nodeA->getArguments(), $nodeB->getArguments())) {
534
            return new Conflict(
535
                $responseName,
536
                'they have differing arguments',
537
                [$nodeA],
538
                [$nodeB]
539
            );
540
        }
541
542
        // The return type for each field.
543
        $typeA = null !== $definitionA ? $definitionA->getType() : null;
544
        $typeB = null !== $definitionB ? $definitionB->getType() : null;
545
546
        if (null !== $typeA && null !== $typeB && compareTypes($typeA, $typeB)) {
547
            return new Conflict(
548
                $responseName,
549
                sprintf('they return conflicting types %s and %s', (string)$typeA, (string)$typeB),
550
                [$nodeA],
551
                [$nodeB]
552
            );
553
        }
554
555
        // Collect and compare sub-fields. Use the same "visited fragment names" list
556
        // for both collections so fields in a fragment reference are never
557
        // compared to themselves.
558
        $selectionSetA = $nodeA->getSelectionSet();
559
        $selectionSetB = $nodeB->getSelectionSet();
560
561
        if (null !== $selectionSetA && null !== $selectionSetB) {
562
            $conflicts = $this->findConflictsBetweenSubSelectionSets(
563
                getNamedType($typeA),
564
                $selectionSetA,
565
                getNamedType($typeB),
566
                $selectionSetB,
567
                $areMutuallyExclusive
568
            );
569
570
            return $this->subfieldConflicts($conflicts, $responseName, $nodeA, $nodeB);
571
        }
572
573
        return null;
574
    }
575
576
    /**
577
     * Given a selection set, return the collection of fields (a mapping of response
578
     * name to field nodes and definitions) as well as a list of fragment names
579
     * referenced via fragment spreads.
580
     *
581
     * @param SelectionSetNode        $selectionSet
582
     * @param NamedTypeInterface|null $parentType
583
     * @return ComparisonContext
584
     * @throws InvalidTypeException
585
     */
586
    protected function getFieldsAndFragmentNames(
587
        SelectionSetNode $selectionSet,
588
        ?NamedTypeInterface $parentType
589
    ): ComparisonContext {
590
        $cached = $this->cachedFieldsAndFragmentNames[(string)$selectionSet] ?? null;
591
592
        if (null === $cached) {
593
            $cached = $this->collectFieldsAndFragmentNames(new ComparisonContext(), $selectionSet, $parentType);
594
595
            $this->cachedFieldsAndFragmentNames[(string)$selectionSet] = $cached;
596
        }
597
598
        return $cached;
599
    }
600
601
    /**
602
     * Given a reference to a fragment, return the represented collection of fields
603
     * as well as a list of nested fragment names referenced via fragment spreads.
604
     *
605
     * @param FragmentDefinitionNode $fragment
606
     * @return ComparisonContext
607
     * @throws InvalidTypeException
608
     */
609
    protected function getReferencedFieldsAndFragmentNames(FragmentDefinitionNode $fragment): ComparisonContext
610
    {
611
        $cached = $this->cachedFieldsAndFragmentNames[(string)$fragment] ?? null;
612
613
        if (null !== $cached) {
614
            return $cached;
615
        }
616
617
        /** @var NamedTypeInterface $fragmentType */
618
        $fragmentType = typeFromAST($this->getValidationContext()->getSchema(), $fragment->getTypeCondition());
619
620
        return $this->getFieldsAndFragmentNames($fragment->getSelectionSet(), $fragmentType);
621
    }
622
623
    /**
624
     * @param ComparisonContext       $context
625
     * @param SelectionSetNode        $selectionSet
626
     * @param NamedTypeInterface|null $parentType
627
     * @return ComparisonContext
628
     * @throws InvalidTypeException
629
     */
630
    protected function collectFieldsAndFragmentNames(
631
        ComparisonContext $context,
632
        SelectionSetNode $selectionSet,
633
        ?NamedTypeInterface $parentType
634
    ): ComparisonContext {
635
        foreach ($selectionSet->getSelections() as $selection) {
636
            if ($selection instanceof FieldNode) {
637
                $fieldName = $selection->getNameValue();
638
639
                $definition = ($parentType instanceof ObjectType || $parentType instanceof InterfaceType)
640
                    ? $parentType->getFields()[$fieldName]
641
                    : null;
642
643
                $context->registerField(new FieldContext($parentType, $selection, $definition));
0 ignored issues
show
Bug introduced by
It seems like $parentType can also be of type Digia\GraphQL\Type\Definition\NamedTypeInterface; however, parameter $parentType of Digia\GraphQL\Validation...dContext::__construct() does only seem to accept null|Digia\GraphQL\Type\...\CompositeTypeInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

643
                $context->registerField(new FieldContext(/** @scrutinizer ignore-type */ $parentType, $selection, $definition));
Loading history...
644
            } elseif ($selection instanceof FragmentSpreadNode) {
645
                $context->registerFragment($selection);
646
            } elseif ($selection instanceof InlineFragmentNode) {
647
                $typeCondition = $selection->getTypeCondition();
648
649
                $inlineFragmentType = null !== $typeCondition
650
                    ? typeFromAST($this->getValidationContext()->getSchema(), $typeCondition)
651
                    : $parentType;
652
653
                $this->collectFieldsAndFragmentNames($context, $selection->getSelectionSet(), $inlineFragmentType);
0 ignored issues
show
Bug introduced by
It seems like $inlineFragmentType can also be of type Digia\GraphQL\Type\Definition\TypeInterface; however, parameter $parentType of Digia\GraphQL\Validation...ieldsAndFragmentNames() does only seem to accept null|Digia\GraphQL\Type\...tion\NamedTypeInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

653
                $this->collectFieldsAndFragmentNames($context, $selection->getSelectionSet(), /** @scrutinizer ignore-type */ $inlineFragmentType);
Loading history...
654
            }
655
        }
656
657
        return $context;
658
    }
659
660
    /**
661
     * Given a series of Conflicts which occurred between two sub-fields, generate
662
     * a single Conflict.
663
     *
664
     * @param array|Conflict[] $conflicts
665
     * @param string           $responseName
666
     * @param FieldNode        $nodeA
667
     * @param FieldNode        $nodeB
668
     * @return Conflict|null
669
     */
670
    protected function subfieldConflicts(
671
        array $conflicts,
672
        string $responseName,
673
        FieldNode $nodeA,
674
        FieldNode $nodeB
675
    ): ?Conflict {
676
        if (empty($conflicts)) {
677
            return null;
678
        }
679
680
        return new Conflict(
681
            $responseName,
682
            array_map(function (Conflict $conflict) {
683
                return $conflict->getReason();
684
            }, $conflicts),
685
            array_reduce($conflicts, function ($allFields, Conflict $conflict) {
686
                return array_merge($allFields, $conflict->getFieldsA());
687
            }, [$nodeA]),
688
            array_reduce($conflicts, function ($allFields, Conflict $conflict) {
689
                return array_merge($allFields, $conflict->getFieldsB());
690
            }, [$nodeB])
691
        );
692
    }
693
}
694