Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Doctrine\DBAL\Schema;
4
 
5
use BadMethodCallException;
6
use Doctrine\DBAL\Exception;
7
use Doctrine\DBAL\Platforms\AbstractPlatform;
8
use Doctrine\DBAL\Types;
9
use Doctrine\Deprecations\Deprecation;
10
 
11
use function array_intersect_key;
12
use function array_key_exists;
13
use function array_keys;
14
use function array_map;
15
use function array_merge;
16
use function array_unique;
17
use function assert;
18
use function count;
19
use function get_class;
20
use function sprintf;
21
use function strtolower;
22
 
23
/**
24
 * Compares two Schemas and return an instance of SchemaDiff.
25
 *
26
 * @method SchemaDiff compareSchemas(Schema $fromSchema, Schema $toSchema)
27
 */
28
class Comparator
29
{
30
    private ?AbstractPlatform $platform;
31
 
32
    /** @internal The comparator can be only instantiated by a schema manager. */
33
    public function __construct(?AbstractPlatform $platform = null)
34
    {
35
        if ($platform === null) {
36
            Deprecation::triggerIfCalledFromOutside(
37
                'doctrine/dbal',
38
                'https://github.com/doctrine/dbal/pull/4746',
39
                'Not passing a $platform to %s is deprecated.'
40
                    . ' Use AbstractSchemaManager::createComparator() to instantiate the comparator.',
41
                __METHOD__,
42
            );
43
        }
44
 
45
        $this->platform = $platform;
46
    }
47
 
48
    /** @param list<mixed> $args */
49
    public function __call(string $method, array $args): SchemaDiff
50
    {
51
        if ($method !== 'compareSchemas') {
52
            throw new BadMethodCallException(sprintf('Unknown method "%s"', $method));
53
        }
54
 
55
        return $this->doCompareSchemas(...$args);
56
    }
57
 
58
    /** @param list<mixed> $args */
59
    public static function __callStatic(string $method, array $args): SchemaDiff
60
    {
61
        if ($method !== 'compareSchemas') {
62
            throw new BadMethodCallException(sprintf('Unknown method "%s"', $method));
63
        }
64
 
65
        Deprecation::trigger(
66
            'doctrine/dbal',
67
            'https://github.com/doctrine/dbal/pull/4707',
68
            'Calling %s::%s() statically is deprecated.',
69
            self::class,
70
            $method,
71
        );
72
 
73
        $comparator = new self();
74
 
75
        return $comparator->doCompareSchemas(...$args);
76
    }
77
 
78
    /**
79
     * Returns a SchemaDiff object containing the differences between the schemas $fromSchema and $toSchema.
80
     *
81
     * This method should be called non-statically since it will be declared as non-static in the next major release.
82
     *
83
     * @return SchemaDiff
84
     *
85
     * @throws SchemaException
86
     */
87
    private function doCompareSchemas(
88
        Schema $fromSchema,
89
        Schema $toSchema
90
    ) {
91
        $createdSchemas   = [];
92
        $droppedSchemas   = [];
93
        $createdTables    = [];
94
        $alteredTables    = [];
95
        $droppedTables    = [];
96
        $createdSequences = [];
97
        $alteredSequences = [];
98
        $droppedSequences = [];
99
 
100
        $orphanedForeignKeys = [];
101
 
102
        $foreignKeysToTable = [];
103
 
104
        foreach ($toSchema->getNamespaces() as $namespace) {
105
            if ($fromSchema->hasNamespace($namespace)) {
106
                continue;
107
            }
108
 
109
            $createdSchemas[$namespace] = $namespace;
110
        }
111
 
112
        foreach ($fromSchema->getNamespaces() as $namespace) {
113
            if ($toSchema->hasNamespace($namespace)) {
114
                continue;
115
            }
116
 
117
            $droppedSchemas[$namespace] = $namespace;
118
        }
119
 
120
        foreach ($toSchema->getTables() as $table) {
121
            $tableName = $table->getShortestName($toSchema->getName());
122
            if (! $fromSchema->hasTable($tableName)) {
123
                $createdTables[$tableName] = $toSchema->getTable($tableName);
124
            } else {
125
                $tableDifferences = $this->diffTable(
126
                    $fromSchema->getTable($tableName),
127
                    $toSchema->getTable($tableName),
128
                );
129
 
130
                if ($tableDifferences !== false) {
131
                    $alteredTables[$tableName] = $tableDifferences;
132
                }
133
            }
134
        }
135
 
136
        /* Check if there are tables removed */
137
        foreach ($fromSchema->getTables() as $table) {
138
            $tableName = $table->getShortestName($fromSchema->getName());
139
 
140
            $table = $fromSchema->getTable($tableName);
141
            if (! $toSchema->hasTable($tableName)) {
142
                $droppedTables[$tableName] = $table;
143
            }
144
 
145
            // also remember all foreign keys that point to a specific table
146
            foreach ($table->getForeignKeys() as $foreignKey) {
147
                $foreignTable = strtolower($foreignKey->getForeignTableName());
148
                if (! isset($foreignKeysToTable[$foreignTable])) {
149
                    $foreignKeysToTable[$foreignTable] = [];
150
                }
151
 
152
                $foreignKeysToTable[$foreignTable][] = $foreignKey;
153
            }
154
        }
155
 
156
        foreach ($droppedTables as $tableName => $table) {
157
            if (! isset($foreignKeysToTable[$tableName])) {
158
                continue;
159
            }
160
 
161
            foreach ($foreignKeysToTable[$tableName] as $foreignKey) {
162
                if (isset($droppedTables[strtolower($foreignKey->getLocalTableName())])) {
163
                    continue;
164
                }
165
 
166
                $orphanedForeignKeys[] = $foreignKey;
167
            }
168
 
169
            // deleting duplicated foreign keys present on both on the orphanedForeignKey
170
            // and the removedForeignKeys from changedTables
171
            foreach ($foreignKeysToTable[$tableName] as $foreignKey) {
172
                // strtolower the table name to make if compatible with getShortestName
173
                $localTableName = strtolower($foreignKey->getLocalTableName());
174
                if (! isset($alteredTables[$localTableName])) {
175
                    continue;
176
                }
177
 
178
                foreach ($alteredTables[$localTableName]->getDroppedForeignKeys() as $droppedForeignKey) {
179
                    assert($droppedForeignKey instanceof ForeignKeyConstraint);
180
 
181
                    // We check if the key is from the removed table if not we skip.
182
                    if ($tableName !== strtolower($droppedForeignKey->getForeignTableName())) {
183
                        continue;
184
                    }
185
 
186
                    $alteredTables[$localTableName]->unsetDroppedForeignKey($droppedForeignKey);
187
                }
188
            }
189
        }
190
 
191
        foreach ($toSchema->getSequences() as $sequence) {
192
            $sequenceName = $sequence->getShortestName($toSchema->getName());
193
            if (! $fromSchema->hasSequence($sequenceName)) {
194
                if (! $this->isAutoIncrementSequenceInSchema($fromSchema, $sequence)) {
195
                    $createdSequences[] = $sequence;
196
                }
197
            } else {
198
                if ($this->diffSequence($sequence, $fromSchema->getSequence($sequenceName))) {
199
                    $alteredSequences[] = $toSchema->getSequence($sequenceName);
200
                }
201
            }
202
        }
203
 
204
        foreach ($fromSchema->getSequences() as $sequence) {
205
            if ($this->isAutoIncrementSequenceInSchema($toSchema, $sequence)) {
206
                continue;
207
            }
208
 
209
            $sequenceName = $sequence->getShortestName($fromSchema->getName());
210
 
211
            if ($toSchema->hasSequence($sequenceName)) {
212
                continue;
213
            }
214
 
215
            $droppedSequences[] = $sequence;
216
        }
217
 
218
        $diff = new SchemaDiff(
219
            $createdTables,
220
            $alteredTables,
221
            $droppedTables,
222
            $fromSchema,
223
            $createdSchemas,
224
            $droppedSchemas,
225
            $createdSequences,
226
            $alteredSequences,
227
            $droppedSequences,
228
        );
229
 
230
        $diff->orphanedForeignKeys = $orphanedForeignKeys;
231
 
232
        return $diff;
233
    }
234
 
235
    /**
236
     * @deprecated Use non-static call to {@see compareSchemas()} instead.
237
     *
238
     * @return SchemaDiff
239
     *
240
     * @throws SchemaException
241
     */
242
    public function compare(Schema $fromSchema, Schema $toSchema)
243
    {
244
        Deprecation::trigger(
245
            'doctrine/dbal',
246
            'https://github.com/doctrine/dbal/pull/4707',
247
            'Method compare() is deprecated. Use a non-static call to compareSchemas() instead.',
248
        );
249
 
250
        return $this->compareSchemas($fromSchema, $toSchema);
251
    }
252
 
253
    /**
254
     * @param Schema   $schema
255
     * @param Sequence $sequence
256
     */
257
    private function isAutoIncrementSequenceInSchema($schema, $sequence): bool
258
    {
259
        foreach ($schema->getTables() as $table) {
260
            if ($sequence->isAutoIncrementsFor($table)) {
261
                return true;
262
            }
263
        }
264
 
265
        return false;
266
    }
267
 
268
    /** @return bool */
269
    public function diffSequence(Sequence $sequence1, Sequence $sequence2)
270
    {
271
        if ($sequence1->getAllocationSize() !== $sequence2->getAllocationSize()) {
272
            return true;
273
        }
274
 
275
        return $sequence1->getInitialValue() !== $sequence2->getInitialValue();
276
    }
277
 
278
    /**
279
     * Returns the difference between the tables $fromTable and $toTable.
280
     *
281
     * If there are no differences this method returns the boolean false.
282
     *
283
     * @deprecated Use {@see compareTables()} and, optionally, {@see TableDiff::isEmpty()} instead.
284
     *
285
     * @return TableDiff|false
286
     *
287
     * @throws Exception
288
     */
289
    public function diffTable(Table $fromTable, Table $toTable)
290
    {
291
        Deprecation::triggerIfCalledFromOutside(
292
            'doctrine/dbal',
293
            'https://github.com/doctrine/dbal/pull/5770',
294
            '%s is deprecated. Use compareTables() instead.',
295
            __METHOD__,
296
        );
297
 
298
        $diff = $this->compareTables($fromTable, $toTable);
299
 
300
        if ($diff->isEmpty()) {
301
            return false;
302
        }
303
 
304
        return $diff;
305
    }
306
 
307
    /**
308
     * Compares the tables and returns the difference between them.
309
     *
310
     * @throws Exception
311
     */
312
    public function compareTables(Table $fromTable, Table $toTable): TableDiff
313
    {
314
        $addedColumns        = [];
315
        $modifiedColumns     = [];
316
        $droppedColumns      = [];
317
        $addedIndexes        = [];
318
        $modifiedIndexes     = [];
319
        $droppedIndexes      = [];
320
        $addedForeignKeys    = [];
321
        $modifiedForeignKeys = [];
322
        $droppedForeignKeys  = [];
323
 
324
        $fromTableColumns = $fromTable->getColumns();
325
        $toTableColumns   = $toTable->getColumns();
326
 
327
        /* See if all the columns in "from" table exist in "to" table */
328
        foreach ($toTableColumns as $columnName => $column) {
329
            if ($fromTable->hasColumn($columnName)) {
330
                continue;
331
            }
332
 
333
            $addedColumns[$columnName] = $column;
334
        }
335
 
336
        /* See if there are any removed columns in "to" table */
337
        foreach ($fromTableColumns as $columnName => $column) {
338
            // See if column is removed in "to" table.
339
            if (! $toTable->hasColumn($columnName)) {
340
                $droppedColumns[$columnName] = $column;
341
 
342
                continue;
343
            }
344
 
345
            $toColumn = $toTable->getColumn($columnName);
346
 
347
            // See if column has changed properties in "to" table.
348
            $changedProperties = $this->diffColumn($column, $toColumn);
349
 
350
            if ($this->platform !== null) {
351
                if ($this->columnsEqual($column, $toColumn)) {
352
                    continue;
353
                }
354
            } elseif (count($changedProperties) === 0) {
355
                continue;
356
            }
357
 
358
            $modifiedColumns[$column->getName()] = new ColumnDiff(
359
                $column->getName(),
360
                $toColumn,
361
                $changedProperties,
362
                $column,
363
            );
364
        }
365
 
366
        $renamedColumns = $this->detectRenamedColumns($addedColumns, $droppedColumns);
367
 
368
        $fromTableIndexes = $fromTable->getIndexes();
369
        $toTableIndexes   = $toTable->getIndexes();
370
 
371
        /* See if all the indexes in "from" table exist in "to" table */
372
        foreach ($toTableIndexes as $indexName => $index) {
373
            if (($index->isPrimary() && $fromTable->getPrimaryKey() !== null) || $fromTable->hasIndex($indexName)) {
374
                continue;
375
            }
376
 
377
            $addedIndexes[$indexName] = $index;
378
        }
379
 
380
        /* See if there are any removed indexes in "to" table */
381
        foreach ($fromTableIndexes as $indexName => $index) {
382
            // See if index is removed in "to" table.
383
            if (
384
                ($index->isPrimary() && $toTable->getPrimaryKey() === null) ||
385
                ! $index->isPrimary() && ! $toTable->hasIndex($indexName)
386
            ) {
387
                $droppedIndexes[$indexName] = $index;
388
 
389
                continue;
390
            }
391
 
392
            // See if index has changed in "to" table.
393
            $toTableIndex = $index->isPrimary() ? $toTable->getPrimaryKey() : $toTable->getIndex($indexName);
394
            assert($toTableIndex instanceof Index);
395
 
396
            if (! $this->diffIndex($index, $toTableIndex)) {
397
                continue;
398
            }
399
 
400
            $modifiedIndexes[$indexName] = $toTableIndex;
401
        }
402
 
403
        $renamedIndexes = $this->detectRenamedIndexes($addedIndexes, $droppedIndexes);
404
 
405
        $fromForeignKeys = $fromTable->getForeignKeys();
406
        $toForeignKeys   = $toTable->getForeignKeys();
407
 
408
        foreach ($fromForeignKeys as $fromKey => $fromConstraint) {
409
            foreach ($toForeignKeys as $toKey => $toConstraint) {
410
                if ($this->diffForeignKey($fromConstraint, $toConstraint) === false) {
411
                    unset($fromForeignKeys[$fromKey], $toForeignKeys[$toKey]);
412
                } else {
413
                    if (strtolower($fromConstraint->getName()) === strtolower($toConstraint->getName())) {
414
                        $modifiedForeignKeys[] = $toConstraint;
415
 
416
                        unset($fromForeignKeys[$fromKey], $toForeignKeys[$toKey]);
417
                    }
418
                }
419
            }
420
        }
421
 
422
        foreach ($fromForeignKeys as $fromConstraint) {
423
            $droppedForeignKeys[] = $fromConstraint;
424
        }
425
 
426
        foreach ($toForeignKeys as $toConstraint) {
427
            $addedForeignKeys[] = $toConstraint;
428
        }
429
 
430
        return new TableDiff(
431
            $toTable->getName(),
432
            $addedColumns,
433
            $modifiedColumns,
434
            $droppedColumns,
435
            $addedIndexes,
436
            $modifiedIndexes,
437
            $droppedIndexes,
438
            $fromTable,
439
            $addedForeignKeys,
440
            $modifiedForeignKeys,
441
            $droppedForeignKeys,
442
            $renamedColumns,
443
            $renamedIndexes,
444
        );
445
    }
446
 
447
    /**
448
     * Try to find columns that only changed their name, rename operations maybe cheaper than add/drop
449
     * however ambiguities between different possibilities should not lead to renaming at all.
450
     *
451
     * @param array<string,Column> $addedColumns
452
     * @param array<string,Column> $removedColumns
453
     *
454
     * @return array<string,Column>
455
     *
456
     * @throws Exception
457
     */
458
    private function detectRenamedColumns(array &$addedColumns, array &$removedColumns): array
459
    {
460
        $candidatesByName = [];
461
 
462
        foreach ($addedColumns as $addedColumnName => $addedColumn) {
463
            foreach ($removedColumns as $removedColumn) {
464
                if (! $this->columnsEqual($addedColumn, $removedColumn)) {
465
                    continue;
466
                }
467
 
468
                $candidatesByName[$addedColumn->getName()][] = [$removedColumn, $addedColumn, $addedColumnName];
469
            }
470
        }
471
 
472
        $renamedColumns = [];
473
 
474
        foreach ($candidatesByName as $candidates) {
475
            if (count($candidates) !== 1) {
476
                continue;
477
            }
478
 
479
            [$removedColumn, $addedColumn] = $candidates[0];
480
            $removedColumnName             = $removedColumn->getName();
481
            $addedColumnName               = strtolower($addedColumn->getName());
482
 
483
            if (isset($renamedColumns[$removedColumnName])) {
484
                continue;
485
            }
486
 
487
            $renamedColumns[$removedColumnName] = $addedColumn;
488
            unset(
489
                $addedColumns[$addedColumnName],
490
                $removedColumns[strtolower($removedColumnName)],
491
            );
492
        }
493
 
494
        return $renamedColumns;
495
    }
496
 
497
    /**
498
     * Try to find indexes that only changed their name, rename operations maybe cheaper than add/drop
499
     * however ambiguities between different possibilities should not lead to renaming at all.
500
     *
501
     * @param array<string,Index> $addedIndexes
502
     * @param array<string,Index> $removedIndexes
503
     *
504
     * @return array<string,Index>
505
     */
506
    private function detectRenamedIndexes(array &$addedIndexes, array &$removedIndexes): array
507
    {
508
        $candidatesByName = [];
509
 
510
        // Gather possible rename candidates by comparing each added and removed index based on semantics.
511
        foreach ($addedIndexes as $addedIndexName => $addedIndex) {
512
            foreach ($removedIndexes as $removedIndex) {
513
                if ($this->diffIndex($addedIndex, $removedIndex)) {
514
                    continue;
515
                }
516
 
517
                $candidatesByName[$addedIndex->getName()][] = [$removedIndex, $addedIndex, $addedIndexName];
518
            }
519
        }
520
 
521
        $renamedIndexes = [];
522
 
523
        foreach ($candidatesByName as $candidates) {
524
            // If the current rename candidate contains exactly one semantically equal index,
525
            // we can safely rename it.
526
            // Otherwise, it is unclear if a rename action is really intended,
527
            // therefore we let those ambiguous indexes be added/dropped.
528
            if (count($candidates) !== 1) {
529
                continue;
530
            }
531
 
532
            [$removedIndex, $addedIndex] = $candidates[0];
533
 
534
            $removedIndexName = strtolower($removedIndex->getName());
535
            $addedIndexName   = strtolower($addedIndex->getName());
536
 
537
            if (isset($renamedIndexes[$removedIndexName])) {
538
                continue;
539
            }
540
 
541
            $renamedIndexes[$removedIndexName] = $addedIndex;
542
            unset(
543
                $addedIndexes[$addedIndexName],
544
                $removedIndexes[$removedIndexName],
545
            );
546
        }
547
 
548
        return $renamedIndexes;
549
    }
550
 
551
    /**
552
     * @internal The method should be only used from within the {@see Comparator} class hierarchy.
553
     *
554
     * @return bool
555
     */
556
    public function diffForeignKey(ForeignKeyConstraint $key1, ForeignKeyConstraint $key2)
557
    {
558
        if (
559
            array_map('strtolower', $key1->getUnquotedLocalColumns())
560
            !== array_map('strtolower', $key2->getUnquotedLocalColumns())
561
        ) {
562
            return true;
563
        }
564
 
565
        if (
566
            array_map('strtolower', $key1->getUnquotedForeignColumns())
567
            !== array_map('strtolower', $key2->getUnquotedForeignColumns())
568
        ) {
569
            return true;
570
        }
571
 
572
        if ($key1->getUnqualifiedForeignTableName() !== $key2->getUnqualifiedForeignTableName()) {
573
            return true;
574
        }
575
 
576
        if ($key1->onUpdate() !== $key2->onUpdate()) {
577
            return true;
578
        }
579
 
580
        return $key1->onDelete() !== $key2->onDelete();
581
    }
582
 
583
    /**
584
     * Compares the definitions of the given columns
585
     *
586
     * @internal The method should be only used from within the {@see Comparator} class hierarchy.
587
     *
588
     * @throws Exception
589
     */
590
    public function columnsEqual(Column $column1, Column $column2): bool
591
    {
592
        if ($this->platform === null) {
593
            return $this->diffColumn($column1, $column2) === [];
594
        }
595
 
596
        return $this->platform->columnsEqual($column1, $column2);
597
    }
598
 
599
    /**
600
     * Returns the difference between the columns
601
     *
602
     * If there are differences this method returns the changed properties as a
603
     * string array, otherwise an empty array gets returned.
604
     *
605
     * @deprecated Use {@see columnsEqual()} instead.
606
     *
607
     * @return string[]
608
     */
609
    public function diffColumn(Column $column1, Column $column2)
610
    {
611
        Deprecation::triggerIfCalledFromOutside(
612
            'doctrine/dbal',
613
            'https://github.com/doctrine/dbal/pull/5650',
614
            '%s is deprecated. Use diffTable() instead.',
615
            __METHOD__,
616
        );
617
 
618
        $properties1 = $column1->toArray();
619
        $properties2 = $column2->toArray();
620
 
621
        $changedProperties = [];
622
 
623
        if (get_class($properties1['type']) !== get_class($properties2['type'])) {
624
            $changedProperties[] = 'type';
625
        }
626
 
627
        foreach (['notnull', 'unsigned', 'autoincrement'] as $property) {
628
            if ($properties1[$property] === $properties2[$property]) {
629
                continue;
630
            }
631
 
632
            $changedProperties[] = $property;
633
        }
634
 
635
        // Null values need to be checked additionally as they tell whether to create or drop a default value.
636
        // null != 0, null != false, null != '' etc. This affects platform's table alteration SQL generation.
637
        if (
638
            ($properties1['default'] === null) !== ($properties2['default'] === null)
639
            || $properties1['default'] != $properties2['default']
640
        ) {
641
            $changedProperties[] = 'default';
642
        }
643
 
644
        if (
645
            ($properties1['type'] instanceof Types\StringType && ! $properties1['type'] instanceof Types\GuidType) ||
646
            $properties1['type'] instanceof Types\BinaryType
647
        ) {
648
            // check if value of length is set at all, default value assumed otherwise.
649
            $length1 = $properties1['length'] ?? 255;
650
            $length2 = $properties2['length'] ?? 255;
651
            if ($length1 !== $length2) {
652
                $changedProperties[] = 'length';
653
            }
654
 
655
            if ($properties1['fixed'] !== $properties2['fixed']) {
656
                $changedProperties[] = 'fixed';
657
            }
658
        } elseif ($properties1['type'] instanceof Types\DecimalType) {
659
            if (($properties1['precision'] ?? 10) !== ($properties2['precision'] ?? 10)) {
660
                $changedProperties[] = 'precision';
661
            }
662
 
663
            if ($properties1['scale'] !== $properties2['scale']) {
664
                $changedProperties[] = 'scale';
665
            }
666
        }
667
 
668
        // A null value and an empty string are actually equal for a comment so they should not trigger a change.
669
        if (
670
            $properties1['comment'] !== $properties2['comment'] &&
671
            ! ($properties1['comment'] === null && $properties2['comment'] === '') &&
672
            ! ($properties2['comment'] === null && $properties1['comment'] === '')
673
        ) {
674
            $changedProperties[] = 'comment';
675
        }
676
 
677
        $customOptions1 = $column1->getCustomSchemaOptions();
678
        $customOptions2 = $column2->getCustomSchemaOptions();
679
 
680
        foreach (array_merge(array_keys($customOptions1), array_keys($customOptions2)) as $key) {
681
            if (! array_key_exists($key, $properties1) || ! array_key_exists($key, $properties2)) {
682
                $changedProperties[] = $key;
683
            } elseif ($properties1[$key] !== $properties2[$key]) {
684
                $changedProperties[] = $key;
685
            }
686
        }
687
 
688
        $platformOptions1 = $column1->getPlatformOptions();
689
        $platformOptions2 = $column2->getPlatformOptions();
690
 
691
        foreach (array_keys(array_intersect_key($platformOptions1, $platformOptions2)) as $key) {
692
            if ($properties1[$key] === $properties2[$key]) {
693
                continue;
694
            }
695
 
696
            $changedProperties[] = $key;
697
        }
698
 
699
        return array_unique($changedProperties);
700
    }
701
 
702
    /**
703
     * Finds the difference between the indexes $index1 and $index2.
704
     *
688 lars 705
     * Compares $index1 with $index2 and returns true if there are any
148 lars 706
     * differences or false in case there are no differences.
707
     *
708
     * @internal The method should be only used from within the {@see Comparator} class hierarchy.
709
     *
710
     * @return bool
711
     */
712
    public function diffIndex(Index $index1, Index $index2)
713
    {
714
        return ! ($index1->isFulfilledBy($index2) && $index2->isFulfilledBy($index1));
715
    }
716
}