Passed
Pull Request — master (#163)
by Wilmer
10:25
created

TestSchemaTrait::normalizeConstraints()   B

Complexity

Conditions 8
Paths 5

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 7
c 1
b 0
f 0
nc 5
nop 2
dl 0
loc 12
rs 8.4444
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\TestUtility;
6
7
use PDO;
8
use Yiisoft\Db\Constraint\Constraint;
9
use Yiisoft\Db\Exception\NotSupportedException;
10
use Yiisoft\Db\Schema\ColumnSchema;
11
use Yiisoft\Db\Schema\Schema;
12
use Yiisoft\Db\Schema\TableSchema;
13
14
use function array_keys;
15
use function array_map;
16
use function fclose;
17
use function fopen;
18
use function gettype;
19
use function is_array;
20
use function json_encode;
21
use function ksort;
22
use function print_r;
23
use function sort;
24
use function sprintf;
25
use function strtolower;
26
use function trim;
27
use function ucfirst;
28
29
trait TestSchemaTrait
30
{
31
    public function pdoAttributesProvider(): array
32
    {
33
        return [
34
            [[PDO::ATTR_EMULATE_PREPARES => true]],
35
            [[PDO::ATTR_EMULATE_PREPARES => false]],
36
        ];
37
    }
38
39
    public function testGetSchemaNames(): void
40
    {
41
        $schema = $this->getConnection()->getSchema();
0 ignored issues
show
Bug introduced by
It seems like getConnection() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

41
        $schema = $this->/** @scrutinizer ignore-call */ getConnection()->getSchema();
Loading history...
42
43
        $schemas = $schema->getSchemaNames();
44
45
        $this->assertNotEmpty($schemas);
0 ignored issues
show
Bug introduced by
It seems like assertNotEmpty() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

45
        $this->/** @scrutinizer ignore-call */ 
46
               assertNotEmpty($schemas);
Loading history...
46
47
        foreach ($this->expectedSchemas as $schema) {
48
            $this->assertContains($schema, $schemas);
0 ignored issues
show
Bug introduced by
It seems like assertContains() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

48
            $this->/** @scrutinizer ignore-call */ 
49
                   assertContains($schema, $schemas);
Loading history...
49
        }
50
    }
51
52
    /**
53
     * @dataProvider pdoAttributesProvider
54
     *
55
     * @param array $pdoAttributes
56
     */
57
    public function testGetTableNames(array $pdoAttributes): void
58
    {
59
        $connection = $this->getConnection(true);
60
61
        foreach ($pdoAttributes as $name => $value) {
62
            if ($name === PDO::ATTR_EMULATE_PREPARES && $connection->getDriverName() === 'sqlsrv') {
63
                continue;
64
            }
65
66
            $connection->getPDO()->setAttribute($name, $value);
67
        }
68
69
        $schema = $connection->getSchema();
70
71
        $tables = $schema->getTableNames();
72
73
        if ($connection->getDriverName() === 'sqlsrv') {
74
            $tables = array_map(static function ($item) {
75
                return trim($item, '[]');
76
            }, $tables);
77
        }
78
79
        $this->assertContains('customer', $tables);
80
        $this->assertContains('category', $tables);
81
        $this->assertContains('item', $tables);
82
        $this->assertContains('order', $tables);
83
        $this->assertContains('order_item', $tables);
84
        $this->assertContains('type', $tables);
85
        $this->assertContains('animal', $tables);
86
        $this->assertContains('animal_view', $tables);
87
    }
88
89
    /**
90
     * @dataProvider pdoAttributesProvider
91
     *
92
     * @param array $pdoAttributes
93
     */
94
    public function testGetTableSchemas(array $pdoAttributes): void
95
    {
96
        $connection = $this->getConnection();
97
98
        foreach ($pdoAttributes as $name => $values) {
99
            if ($name === PDO::ATTR_EMULATE_PREPARES  && $connection->getDriverName() === 'sqlsrv') {
100
                continue;
101
            }
102
103
            $connection->getPDO()->setAttribute($name, $value);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $value seems to be never defined.
Loading history...
104
        }
105
106
        $schema = $connection->getSchema();
107
108
        $tables = $schema->getTableSchemas();
109
110
        $this->assertCount(count($schema->getTableNames()), $tables);
0 ignored issues
show
Bug introduced by
It seems like assertCount() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

110
        $this->/** @scrutinizer ignore-call */ 
111
               assertCount(count($schema->getTableNames()), $tables);
Loading history...
111
112
        foreach ($tables as $table) {
113
            $this->assertInstanceOf(TableSchema::class, $table);
0 ignored issues
show
Bug introduced by
It seems like assertInstanceOf() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

113
            $this->/** @scrutinizer ignore-call */ 
114
                   assertInstanceOf(TableSchema::class, $table);
Loading history...
114
        }
115
    }
116
117
    public function testGetTableSchemasWithAttrCase(): void
118
    {
119
        $db = $this->getConnection(false);
120
121
        $db->getSlavePdo()->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
122
123
        $this->assertCount(count($db->getSchema()->getTableNames()), $db->getSchema()->getTableSchemas());
124
125
        $db->getSlavePdo()->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER);
126
127
        $this->assertCount(count($db->getSchema()->getTableNames()), $db->getSchema()->getTableSchemas());
128
    }
129
130
    public function testGetNonExistingTableSchema(): void
131
    {
132
        $this->assertNull($this->getConnection()->getSchema()->getTableSchema('nonexisting_table'));
0 ignored issues
show
Bug introduced by
It seems like assertNull() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

132
        $this->/** @scrutinizer ignore-call */ 
133
               assertNull($this->getConnection()->getSchema()->getTableSchema('nonexisting_table'));
Loading history...
133
    }
134
135
    public function testSchemaCache(): void
136
    {
137
        $db = $this->getConnection();
138
139
        $schema = $db->getSchema();
140
141
        $schema->getDb()->setEnableSchemaCache(true);
142
        $schema->getDb()->setSchemaCache($this->cache);
143
144
        $noCacheTable = $schema->getTableSchema('type', true);
145
        $cachedTable = $schema->getTableSchema('type', false);
146
147
        $this->assertEquals($noCacheTable, $cachedTable);
0 ignored issues
show
Bug introduced by
It seems like assertEquals() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

147
        $this->/** @scrutinizer ignore-call */ 
148
               assertEquals($noCacheTable, $cachedTable);
Loading history...
148
149
        $db->createCommand()->renameTable('type', 'type_test');
150
151
        $noCacheTable = $schema->getTableSchema('type', true);
152
153
        $this->assertNotSame($noCacheTable, $cachedTable);
0 ignored issues
show
Bug introduced by
It seems like assertNotSame() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

153
        $this->/** @scrutinizer ignore-call */ 
154
               assertNotSame($noCacheTable, $cachedTable);
Loading history...
154
155
        $db->createCommand()->renameTable('type_test', 'type');
156
    }
157
158
    /**
159
     * @depends testSchemaCache
160
     */
161
    public function testRefreshTableSchema(): void
162
    {
163
        $schema = $this->getConnection()->getSchema();
164
165
        $schema->getDb()->setEnableSchemaCache(true);
166
        $schema->getDb()->setSchemaCache($this->cache);
167
168
        $noCacheTable = $schema->getTableSchema('type', true);
169
170
        $schema->refreshTableSchema('type');
171
172
        $refreshedTable = $schema->getTableSchema('type', false);
173
174
        $this->assertNotSame($noCacheTable, $refreshedTable);
175
    }
176
177
    public function tableSchemaCachePrefixesProvider(): array
178
    {
179
        $configs = [
180
            [
181
                'prefix' => '',
182
                'name'   => 'type',
183
            ],
184
            [
185
                'prefix' => '',
186
                'name'   => '{{%type}}',
187
            ],
188
            [
189
                'prefix' => 'ty',
190
                'name'   => '{{%pe}}',
191
            ],
192
        ];
193
194
        $data = [];
195
        foreach ($configs as $config) {
196
            foreach ($configs as $testConfig) {
197
                if ($config === $testConfig) {
198
                    continue;
199
                }
200
201
                $description = sprintf(
202
                    "%s (with '%s' prefix) against %s (with '%s' prefix)",
203
                    $config['name'],
204
                    $config['prefix'],
205
                    $testConfig['name'],
206
                    $testConfig['prefix']
207
                );
208
                $data[$description] = [
209
                    $config['prefix'],
210
                    $config['name'],
211
                    $testConfig['prefix'],
212
                    $testConfig['name'],
213
                ];
214
            }
215
        }
216
217
        return $data;
218
    }
219
220
    /**
221
     * @depends testSchemaCache
222
     *
223
     * @dataProvider tableSchemaCachePrefixesProvider
224
     *
225
     * @param string $tablePrefix
226
     * @param string $tableName
227
     * @param string $testTablePrefix
228
     * @param string $testTableName
229
     */
230
    public function testTableSchemaCacheWithTablePrefixes(
231
        string $tablePrefix,
232
        string $tableName,
233
        string $testTablePrefix,
234
        string $testTableName
235
    ): void {
236
        $schema = $this->getConnection()->getSchema();
237
238
        $schema->getDb()->setEnableSchemaCache(true);
239
        $schema->getDb()->setSchemaCache($this->cache);
240
        $schema->getDb()->setTablePrefix($tablePrefix);
241
242
        $noCacheTable = $schema->getTableSchema($tableName, true);
243
244
        $this->assertInstanceOf(TableSchema::class, $noCacheTable);
245
246
        /* Compare */
247
        $schema->getDb()->setTablePrefix($testTablePrefix);
248
249
        $testNoCacheTable = $schema->getTableSchema($testTableName);
250
251
        $this->assertSame($noCacheTable, $testNoCacheTable);
0 ignored issues
show
Bug introduced by
It seems like assertSame() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

251
        $this->/** @scrutinizer ignore-call */ 
252
               assertSame($noCacheTable, $testNoCacheTable);
Loading history...
252
253
        $schema->getDb()->setTablePrefix($tablePrefix);
254
255
        $schema->refreshTableSchema($tableName);
256
257
        $refreshedTable = $schema->getTableSchema($tableName, false);
258
259
        $this->assertInstanceOf(TableSchema::class, $refreshedTable);
260
        $this->assertNotSame($noCacheTable, $refreshedTable);
261
262
        /* Compare */
263
        $schema->getDb()->setTablePrefix($testTablePrefix);
264
265
        $schema->refreshTableSchema($testTablePrefix);
266
267
        $testRefreshedTable = $schema->getTableSchema($testTableName, false);
268
269
        $this->assertInstanceOf(TableSchema::class, $testRefreshedTable);
270
        $this->assertEquals($refreshedTable, $testRefreshedTable);
271
        $this->assertNotSame($testNoCacheTable, $testRefreshedTable);
272
    }
273
274
    public function testCompositeFk(): void
275
    {
276
        $schema = $this->getConnection()->getSchema();
277
278
        $table = $schema->getTableSchema('composite_fk');
279
280
        $fk = $table->getForeignKeys();
281
282
        $this->assertCount(1, $fk);
283
        $this->assertTrue(isset($fk['FK_composite_fk_order_item']));
0 ignored issues
show
Bug introduced by
It seems like assertTrue() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

283
        $this->/** @scrutinizer ignore-call */ 
284
               assertTrue(isset($fk['FK_composite_fk_order_item']));
Loading history...
284
        $this->assertEquals('order_item', $fk['FK_composite_fk_order_item'][0]);
285
        $this->assertEquals('order_id', $fk['FK_composite_fk_order_item']['order_id']);
286
        $this->assertEquals('item_id', $fk['FK_composite_fk_order_item']['item_id']);
287
    }
288
289
    public function testGetPDOType(): void
290
    {
291
        $values = [
292
            [null, PDO::PARAM_NULL],
293
            ['', PDO::PARAM_STR],
294
            ['hello', PDO::PARAM_STR],
295
            [0, PDO::PARAM_INT],
296
            [1, PDO::PARAM_INT],
297
            [1337, PDO::PARAM_INT],
298
            [true, PDO::PARAM_BOOL],
299
            [false, PDO::PARAM_BOOL],
300
            [$fp = fopen(__FILE__, 'rb'), PDO::PARAM_LOB],
301
        ];
302
303
        $schema = $this->getConnection()->getSchema();
304
305
        foreach ($values as $value) {
306
            $this->assertEquals($value[1], $schema->getPdoType($value[0]), 'type for value ' . print_r($value[0], true) . ' does not match.');
307
        }
308
309
        fclose($fp);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, 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

309
        fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
310
    }
311
312
    public function testColumnSchema(): void
313
    {
314
        $columns = $this->getExpectedColumns();
0 ignored issues
show
Bug introduced by
It seems like getExpectedColumns() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

314
        /** @scrutinizer ignore-call */ 
315
        $columns = $this->getExpectedColumns();
Loading history...
315
316
        $table = $this->getConnection(false)->getSchema()->getTableSchema('type', true);
317
318
        $expectedColNames = array_keys($columns);
319
320
        sort($expectedColNames);
321
322
        $colNames = $table->getColumnNames();
323
324
        sort($colNames);
325
326
        $this->assertEquals($expectedColNames, $colNames);
327
328
        foreach ($table->getColumns() as $name => $column) {
329
            $expected = $columns[$name];
330
            $this->assertSame(
331
                $expected['dbType'],
332
                $column->getDbType(),
333
                "dbType of column $name does not match. type is {$column->getType()}, dbType is {$column->getDbType()}."
334
            );
335
            $this->assertSame(
336
                $expected['phpType'],
337
                $column->getPhpType(),
338
                "phpType of column $name does not match. type is {$column->getType()}, dbType is {$column->getDbType()}."
339
            );
340
            $this->assertSame($expected['type'], $column->getType(), "type of column $name does not match.");
341
            $this->assertSame(
342
                $expected['allowNull'],
343
                $column->isAllowNull(),
344
                "allowNull of column $name does not match."
345
            );
346
            $this->assertSame(
347
                $expected['autoIncrement'],
348
                $column->isAutoIncrement(),
349
                "autoIncrement of column $name does not match."
350
            );
351
            $this->assertSame(
352
                $expected['enumValues'],
353
                $column->getEnumValues(),
354
                "enumValues of column $name does not match."
355
            );
356
            $this->assertSame($expected['size'], $column->getSize(), "size of column $name does not match.");
357
            $this->assertSame(
358
                $expected['precision'],
359
                $column->getPrecision(),
360
                "precision of column $name does not match."
361
            );
362
            $this->assertSame($expected['scale'], $column->getScale(), "scale of column $name does not match.");
363
            if (\is_object($expected['defaultValue'])) {
364
                $this->assertIsObject(
0 ignored issues
show
Bug introduced by
It seems like assertIsObject() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

364
                $this->/** @scrutinizer ignore-call */ 
365
                       assertIsObject(
Loading history...
365
                    $column->getDefaultValue(),
366
                    "defaultValue of column $name is expected to be an object but it is not."
367
                );
368
                $this->assertEquals(
369
                    (string) $expected['defaultValue'],
370
                    (string) $column->getDefaultValue(),
371
                    "defaultValue of column $name does not match."
372
                );
373
            } else {
374
                $this->assertEquals(
375
                    $expected['defaultValue'],
376
                    $column->getDefaultValue(),
377
                    "defaultValue of column $name does not match."
378
                );
379
            }
380
            if (isset($expected['dimension'])) { // PgSQL only
381
                $this->assertSame(
382
                    $expected['dimension'],
383
                    $column->getDimension(),
384
                    "dimension of column $name does not match"
385
                );
386
            }
387
        }
388
    }
389
390
    public function testColumnSchemaDbTypecastWithEmptyCharType(): void
391
    {
392
        $columnSchema = new ColumnSchema();
393
394
        $columnSchema->setType(Schema::TYPE_CHAR);
395
396
        $this->assertSame('', $columnSchema->dbTypecast(''));
397
    }
398
399
    public function testNegativeDefaultValues(): void
400
    {
401
        $schema = $this->getConnection()->getSchema();
402
403
        $table = $schema->getTableSchema('negative_default_values');
404
405
        $this->assertEquals(-123, $table->getColumn('tinyint_col')->getDefaultValue());
406
        $this->assertEquals(-123, $table->getColumn('smallint_col')->getDefaultValue());
407
        $this->assertEquals(-123, $table->getColumn('int_col')->getDefaultValue());
408
        $this->assertEquals(-123, $table->getColumn('bigint_col')->getDefaultValue());
409
        $this->assertEquals(-12345.6789, $table->getColumn('float_col')->getDefaultValue());
410
        $this->assertEquals(-33.22, $table->getColumn('numeric_col')->getDefaultValue());
411
    }
412
413
    public function testContraintTablesExistance(): void
414
    {
415
        $tableNames = [
416
            'T_constraints_1',
417
            'T_constraints_2',
418
            'T_constraints_3',
419
            'T_constraints_4',
420
        ];
421
422
        $schema = $this->getConnection()->getSchema();
423
424
        foreach ($tableNames as $tableName) {
425
            $tableSchema = $schema->getTableSchema($tableName);
426
            $this->assertInstanceOf(TableSchema::class, $tableSchema, $tableName);
427
        }
428
    }
429
430
    public function lowercaseConstraintsProvider(): array
431
    {
432
        return $this->constraintsProvider();
0 ignored issues
show
Bug introduced by
It seems like constraintsProvider() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

432
        return $this->/** @scrutinizer ignore-call */ constraintsProvider();
Loading history...
433
    }
434
435
    public function uppercaseConstraintsProvider(): array
436
    {
437
        return $this->constraintsProvider();
438
    }
439
440
    /**
441
     * @dataProvider constraintsProvider
442
     *
443
     * @param string $tableName
444
     * @param string $type
445
     * @param mixed $expected
446
     */
447
    public function testTableSchemaConstraints(string $tableName, string $type, $expected): void
448
    {
449
        if ($expected === false) {
450
            $this->expectException(NotSupportedException::class);
0 ignored issues
show
Bug introduced by
It seems like expectException() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

450
            $this->/** @scrutinizer ignore-call */ 
451
                   expectException(NotSupportedException::class);
Loading history...
451
        }
452
453
        $constraints = $this->getConnection(false)->getSchema()->{'getTable' . ucfirst($type)}($tableName);
454
455
        $this->assertMetadataEquals($expected, $constraints);
456
    }
457
458
    /**
459
     * @dataProvider uppercaseConstraintsProvider
460
     *
461
     * @param string $tableName
462
     * @param string $type
463
     * @param mixed $expected
464
     */
465
    public function testTableSchemaConstraintsWithPdoUppercase(string $tableName, string $type, $expected): void
466
    {
467
        if ($expected === false) {
468
            $this->expectException(NotSupportedException::class);
469
        }
470
471
        $connection = $this->getConnection();
472
        $connection->getSlavePdo()->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER);
473
        $constraints = $connection->getSchema()->{'getTable' . ucfirst($type)}($tableName, true);
474
        $this->assertMetadataEquals($expected, $constraints);
475
    }
476
477
    /**
478
     * @dataProvider lowercaseConstraintsProvider
479
     *
480
     * @param string $tableName
481
     * @param string $type
482
     * @param mixed $expected
483
     */
484
    public function testTableSchemaConstraintsWithPdoLowercase(string $tableName, string $type, $expected): void
485
    {
486
        if ($expected === false) {
487
            $this->expectException(NotSupportedException::class);
488
        }
489
490
        $connection = $this->getConnection();
491
        $connection->getSlavePdo()->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
492
        $constraints = $connection->getSchema()->{'getTable' . ucfirst($type)}($tableName, true);
493
494
        $this->assertMetadataEquals($expected, $constraints);
495
    }
496
497
    private function assertMetadataEquals($expected, $actual): void
498
    {
499
        switch (strtolower(gettype($expected))) {
500
            case 'object':
501
                $this->assertIsObject($actual);
502
                break;
503
            case 'array':
504
                $this->assertIsArray($actual);
0 ignored issues
show
Bug introduced by
It seems like assertIsArray() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

504
                $this->/** @scrutinizer ignore-call */ 
505
                       assertIsArray($actual);
Loading history...
505
                break;
506
            case 'null':
507
                $this->assertNull($actual);
508
                break;
509
        }
510
511
        if (is_array($expected)) {
512
            $this->normalizeArrayKeys($expected, false);
513
            $this->normalizeArrayKeys($actual, false);
514
        }
515
516
        $this->normalizeConstraints($expected, $actual);
517
518
        if (is_array($expected)) {
519
            $this->normalizeArrayKeys($expected, true);
520
            $this->normalizeArrayKeys($actual, true);
521
        }
522
523
        $this->assertEquals($expected, $actual);
524
    }
525
526
    private function normalizeArrayKeys(array &$array, bool $caseSensitive): void
527
    {
528
        $newArray = [];
529
530
        foreach ($array as $value) {
531
            if ($value instanceof Constraint) {
532
                $key = (array) $value;
533
                unset(
534
                    $key["\000Yiisoft\Db\Constraint\Constraint\000name"],
535
                    $key["\u0000Yiisoft\\Db\\Constraint\\ForeignKeyConstraint\u0000foreignSchemaName"]
536
                );
537
538
                foreach ($key as $keyName => $keyValue) {
539
                    if ($keyValue instanceof AnyCaseValue) {
540
                        $key[$keyName] = $keyValue->value;
541
                    } elseif ($keyValue instanceof AnyValue) {
542
                        $key[$keyName] = '[AnyValue]';
543
                    }
544
                }
545
546
                ksort($key, SORT_STRING);
547
548
                $newArray[$caseSensitive
549
                    ? json_encode($key, JSON_THROW_ON_ERROR)
550
                    : strtolower(json_encode($key, JSON_THROW_ON_ERROR))] = $value;
551
            } else {
552
                $newArray[] = $value;
553
            }
554
        }
555
556
        ksort($newArray, SORT_STRING);
557
558
        $array = $newArray;
559
    }
560
561
    private function normalizeConstraints(&$expected, &$actual): void
562
    {
563
        if (is_array($expected)) {
564
            foreach ($expected as $key => $value) {
565
                if (!$value instanceof Constraint || !isset($actual[$key]) || !$actual[$key] instanceof Constraint) {
566
                    continue;
567
                }
568
569
                $this->normalizeConstraintPair($value, $actual[$key]);
570
            }
571
        } elseif ($expected instanceof Constraint && $actual instanceof Constraint) {
572
            $this->normalizeConstraintPair($expected, $actual);
573
        }
574
    }
575
576
    private function normalizeConstraintPair(Constraint $expectedConstraint, Constraint $actualConstraint): void
577
    {
578
        if (get_class($expectedConstraint) !== get_class($actualConstraint)) {
579
            return;
580
        }
581
582
        foreach (array_keys((array) $expectedConstraint) as $name) {
583
            if ($expectedConstraint->getName() instanceof AnyValue) {
584
                $actualConstraint->name($expectedConstraint->getName());
585
            } elseif ($expectedConstraint->getName() instanceof AnyCaseValue) {
586
                $actualConstraint->name(new AnyCaseValue($actualConstraint->getName()));
587
            }
588
        }
589
    }
590
}
591