Passed
Pull Request — 2.1 (#68)
by Vincent
17:12 queued 10:39
created

Metadata   F

Complexity

Total Complexity 84

Size/Duplication

Total Lines 896
Duplicated Lines 0 %

Test Coverage

Coverage 86.86%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 84
eloc 226
c 1
b 0
f 0
dl 0
loc 896
ccs 205
cts 236
cp 0.8686
rs 2

42 Methods

Rating   Name   Duplication   Size   Complexity  
A fieldType() 0 3 1
A getExistingClassName() 0 7 3
A buildPolymorph() 0 24 3
A database() 0 3 1
A isBuilt() 0 3 1
A sequence() 0 5 2
A isCompositePrimaryKey() 0 3 1
A getInstantiatorHint() 0 20 6
A build() 0 17 2
B buildField() 0 48 8
A isForeignPrimaryKey() 0 3 1
A buildFields() 0 12 4
A attributeType() 0 3 1
A connection() 0 3 1
A buildIndexesFromFields() 0 20 5
A attributeFrom() 0 3 1
A fieldFrom() 0 3 1
A attributes() 0 3 1
A embedded() 0 3 1
A indexes() 0 3 1
B buildRelations() 0 35 8
A fields() 0 3 1
A buildMappedEmbedded() 0 11 2
A primary() 0 3 1
A tableOptions() 0 3 1
A buildSchema() 0 16 1
A meta() 0 7 2
A isSequencePrimaryKey() 0 3 1
A primaryMeta() 0 9 2
A firstPrimaryMeta() 0 5 1
A getEntityClass() 0 3 1
A isAutoIncrementPrimaryKey() 0 3 1
A isPrimary() 0 3 1
A buildIndexes() 0 4 1
A buildSequence() 0 11 2
A attributeExists() 0 3 1
A embeddeds() 0 3 1
A buildSimpleIndexes() 0 28 6
A fieldExists() 0 3 1
A buildEmbedded() 0 18 2
A table() 0 3 1
A eagerRelations() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Metadata often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Metadata, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Bdf\Prime\Mapper;
4
5
use Bdf\Prime\Entity\Instantiator\InstantiatorInterface;
6
use Bdf\Prime\Relations\Builder\RelationBuilder;
7
use Bdf\Prime\Relations\Relation;
8
use LogicException;
9
use stdClass;
10
11
/**
12
 * Metadata
13
 *
14
 * @todo gerer le nom de la base de données si non fourni
15
 * @todo exception si aucune primary ou unique n'a été définit ?
16
 * @todo doit on injecter si private ??
17
 *
18
 * @psalm-type FieldMetadata = array{
19
 *     primary: Metadata::PK_*|null,
20
 *     type: string,
21
 *     default: mixed,
22
 *     phpOptions: array<string, mixed>,
23
 *     field: string,
24
 *     attribute: string,
25
 *     embedded: string|null,
26
 *     length?: int,
27
 *     comment?: string,
28
 *     nillable?: bool,
29
 *     unsigned?: bool,
30
 *     unique?: bool|string,
31
 *     class?: class-string
32
 * }
33
 *
34
 * @psalm-type SequenceMetadata = array{
35
 *     connection: string|null,
36
 *     table: string|null,
37
 *     column: string|null,
38
 *     options: array
39
 * }
40
 *
41
 * @psalm-type EmbeddedMetadata = array{
42
 *     path: string,
43
 *     parentPath: string,
44
 *     paths: list<string>,
45
 *     class?: class-string,
46
 *     hint?: int|null,
47
 *     class_map?: array<string, class-string>,
48
 *     hints?: array<class-string, int|null>,
49
 *     discriminator_field?: string,
50
 *     discriminator_attribute?: string
51
 * }
52
 *
53
 * @psalm-type IndexMetadata = array{
54
 *     fields:array<string, array<string, string>>,
55
 *     unique?: bool
56
 * }&array<string, string>
57
 *
58
 * @psalm-import-type FieldDefinition from \Bdf\Prime\Mapper\Builder\FieldBuilder
59
 * @psalm-import-type RelationDefinition from RelationBuilder
60
 */
61
class Metadata
62
{
63
    /* constantes définissant le type de primary key */
64
    public const PK_AUTOINCREMENT = 'autoincrement';
65
    public const PK_AUTO = true;
66
    public const PK_SEQUENCE = 'sequence';
67
68
    /**
69
     * The expected entity classname
70
     *
71
     * @var class-string
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
72
     */
73
    public $entityName;
74
75
    /**
76
     * The instantiator hint
77
     *
78
     * @var int|null
79
     */
80
    public $instantiatorHint;
81
82
    /**
83
     * The class name to use
84
     *
85
     * if the class name does not exist, a stdClass will be used
86
     *
87
     * @var class-string
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
88
     */
89
    public $entityClass;
90
91
    /**
92
     * The property accessor class name to use
93
     * Usefull only for building metadata
94
     *
95
     * @var class-string
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
96
     */
97
    public $propertyAccessorClass;
98
99
    /**
100
     * @var string
101
     */
102
    public $connection;
103
104
    /**
105
     * @var string|null
106
     */
107
    public $database;
108
109
    /**
110
     * @var string
111
     */
112
    public $table;
113
114
    /**
115
     * @var boolean
116
     */
117
    public $useQuoteIdentifier;
118
119
    /**
120
     * @var array<string, string>
121
     */
122
    public $tableOptions = [];
123
124
    /**
125
     * The indexes
126
     * Format :
127
     * [
128
     *    [index_name] => [
129
     *         'fields' => [
130
     *             'field1' => [
131
     *                 'fieldOption' => 'optValue',
132
     *                 ...
133
     *             ],
134
     *             ...
135
     *         ],
136
     *         'unique' => true,
137
     *         'option1' => 'value1',
138
     *         'option2' => 'value2',
139
     *         ...
140
     *    ]
141
     * ]
142
     *
143
     * With :
144
     * - index_name : The index name as string for named index, or integer offset for generated name
145
     * - 'fields' : Array of fields where the index is applied. The fields are the database fields
146
     * - 'unique' : Not set for simple indexes, the value is true for indicate a unique constraint on the index
147
     * - options : Key/value index options, depends of the database platform and driver
148
     * - fieldOption : Option related to the field, like length or sort order
149
     *
150
     * @var IndexMetadata[]
151
     */
152
    public $indexes = [];
153
154
    /**
155
     * @var SequenceMetadata
0 ignored issues
show
Bug introduced by
The type Bdf\Prime\Mapper\SequenceMetadata was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
156
     */
157
    public $sequence = [
158
        'connection' => null,
159
        'table'      => null,
160
        'column'     => null,
161
        'options'    => [],
162
    ];
163
164
    /**
165
     * List of entity columns, indexed by the database columns name
166
     *
167
     * @var array<string, FieldMetadata>
168
     */
169
    public $fields = [];
170
171
    /**
172
     * List of entity columns, indexed by the entity property name
173
     *
174
     * @var array<string, FieldMetadata>
175
     */
176
    public $attributes = [];
177
178
    /**
179
     * @var array<string, EmbeddedMetadata>
180
     */
181
    public $embeddeds = [];
182
183
    /**
184
     * @var array{
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{ at position 2 could not be parsed: the token is null at position 2.
Loading history...
185
     *     type: Metadata::PK_*,
186
     *     attributes: list<string>,
187
     *     fields: list<string>
188
     * }
189
     */
190
    public $primary = [
191
        'type'          => self::PK_AUTO,
192
        'attributes'    => [],
193
        'fields'        => [],
194
    ];
195
196
    /**
197
     * The repository global constraints
198
     *
199
     * @var array
200
     */
201
    public $constraints = [];
202
203
    /**
204
     * Flag indiquant que le meta a déjà été construit
205
     *
206
     * @var bool
207
     */
208
    protected $built = false;
209
210
    /**
211
     * Relations that must be loaded eagerly
212
     *
213
     * @var array
214
     */
215
    public $eagerRelations = [];
216
217
    /**
218
     * Get entity class name
219
     *
220
     * @return class-string
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
221
     */
222 1
    public function getEntityClass(): string
223
    {
224 1
        return $this->entityClass;
225
    }
226
227
    /**
228
     * Is metadata built
229
     *
230
     * @return bool
231
     */
232 1
    public function isBuilt(): bool
233
    {
234 1
        return $this->built;
235
    }
236
237
    /**
238
     * Get connection identifier from locator
239
     *
240
     * @return string|null
241
     */
242 1
    public function connection(): ?string
243
    {
244 1
        return $this->connection;
245
    }
246
247
    /**
248
     * Get database name
249
     *
250
     * @return string|null
251
     */
252 1
    public function database(): ?string
253
    {
254 1
        return $this->database;
255
    }
256
257
    /**
258
     * Get table name
259
     *
260
     * @return string
261
     */
262 89
    public function table(): string
263
    {
264 89
        return $this->table;
265
    }
266
267
    /**
268
     * Get table options
269
     *
270
     * @return array
271
     */
272 866
    public function tableOptions(): array
273
    {
274 866
        return $this->tableOptions;
275
    }
276
277
    /**
278
     * Get indexes
279
     *
280
     * @return array<string, IndexMetadata>
281
     */
282 2
    public function indexes(): array
283
    {
284 2
        return $this->indexes;
285
    }
286
287
    /**
288
     * Get embedded meta
289
     *
290
     * @return array<string, EmbeddedMetadata>
291
     */
292 251
    public function embeddeds(): array
293
    {
294 251
        return $this->embeddeds;
295
    }
296
297
    /**
298
     * Get attribute embedded meta
299
     *
300
     * @param string $attribute
301
     *
302
     * @return EmbeddedMetadata|null
0 ignored issues
show
Bug introduced by
The type Bdf\Prime\Mapper\EmbeddedMetadata was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
303
     */
304 2
    public function embedded($attribute): ?array
305
    {
306 2
        return $this->embeddeds[$attribute] ?? null;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->embeddeds[$attribute] ?? null could return the type Bdf\Prime\Mapper\EmbeddedMetadata which is incompatible with the type-hinted return array|null. Consider adding an additional type-check to rule them out.
Loading history...
307
    }
308
309
    /**
310
     * Get attribute or field metadata
311
     *
312
     * @param string $key
313
     * @param string $type
314
     *
315
     * @return array|null
316
     */
317 1
    public function meta($key, $type = 'attributes')
318
    {
319 1
        if (isset($this->{$type}[$key])) {
320 1
            return $this->{$type}[$key];
321
        }
322
323 1
        return null;
324
    }
325
326
    /**
327
     * Returns primary attributes | fields | type
328
     *
329
     * @param 'attributes'|'fields'|'type' $type
0 ignored issues
show
Documentation Bug introduced by
The doc comment 'attributes'|'fields'|'type' at position 0 could not be parsed: Unknown type name ''attributes'' at position 0 in 'attributes'|'fields'|'type'.
Loading history...
330
     *
331
     * @return list<string>|Metadata::PK_*
332
     */
333 1
    public function primary($type = 'attributes')
334
    {
335 1
        return $this->primary[$type];
336
    }
337
338
    /**
339
     * Returns metadata for first primary key
340
     *
341
     * @return FieldMetadata
0 ignored issues
show
Bug introduced by
The type Bdf\Prime\Mapper\FieldMetadata was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
342
     */
343
    public function firstPrimaryMeta(): array
344
    {
345
        list($primary) = $this->primary['attributes'];
346
347
        return $this->attributes[$primary];
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->attributes[$primary] returns the type Bdf\Prime\Mapper\FieldMetadata which is incompatible with the type-hinted return array.
Loading history...
348
    }
349
350
    /**
351
     * Returns all metadata for primary key
352
     *
353
     * @return array<string, FieldMetadata>
354
     */
355
    public function primaryMeta(): array
356
    {
357
        $meta = [];
358
359
        foreach ($this->primary['attributes'] as $attribute) {
360
            $meta[$attribute] = $this->attributes[$attribute];
361
        }
362
363
        return $meta;
364
    }
365
366
    /**
367
     * Returns sequence info
368
     *
369
     * @param 'connection'|'table'|'column'|'options'|null $key
0 ignored issues
show
Documentation Bug introduced by
The doc comment 'connection'|'table'|'column'|'options'|null at position 0 could not be parsed: Unknown type name ''connection'' at position 0 in 'connection'|'table'|'column'|'options'|null.
Loading history...
370
     *
371
     * @return SequenceMetadata|string|array|null
372
     */
373
    public function sequence(?string $key = null)
374
    {
375
        return $key === null
376
            ? $this->sequence
377
            : $this->sequence[$key];
378
    }
379
380
    /**
381
     * Check if key is primary
382
     *
383
     * @param string $key
384
     * @param 'attributes'|'fields' $type
0 ignored issues
show
Documentation Bug introduced by
The doc comment 'attributes'|'fields' at position 0 could not be parsed: Unknown type name ''attributes'' at position 0 in 'attributes'|'fields'.
Loading history...
385
     *
386
     * @return bool
387
     */
388 1
    public function isPrimary($key, $type = 'attributes'): bool
389
    {
390 1
        return in_array($key, $this->primary[$type]);
391
    }
392
393
    /**
394
     * Is primary key an auto increment
395
     *
396
     * @return bool
397
     */
398 306
    public function isAutoIncrementPrimaryKey(): bool
399
    {
400 306
        return $this->primary['type'] === self::PK_AUTOINCREMENT;
401
    }
402
403
    /**
404
     * Is a sequence generated primary key
405
     *
406
     * @return bool
407
     *
408
     * @psalm-assert string $this->sequence['table']
409
     * @psalm-assert string $this->sequence['column']
410
     */
411 1037
    public function isSequencePrimaryKey(): bool
412
    {
413 1037
        return $this->primary['type'] === self::PK_SEQUENCE;
414
    }
415
416
    /**
417
     * Is a foreign key as primary key
418
     *
419
     * @return bool
420
     */
421 67
    public function isForeignPrimaryKey(): bool
422
    {
423 67
        return $this->primary['type'] === self::PK_AUTO;
424
    }
425
426
    /**
427
     * The primary key has multiple fields
428
     *
429
     * @return bool
430
     */
431 125
    public function isCompositePrimaryKey(): bool
432
    {
433 125
        return count($this->primary['attributes']) > 1;
434
    }
435
436
    /**
437
     * Get fields metadata
438
     *
439
     * @return array<string, FieldMetadata>
440
     */
441
    public function fields(): array
442
    {
443
        return $this->fields;
444
    }
445
446 1
    public function eagerRelations(): array
447
    {
448 1
        return $this->eagerRelations;
449
    }
450
451
    /**
452
     * Does field exist
453
     *
454
     * @param string $field
455
     *
456
     * @return bool
457
     */
458
    public function fieldExists($field): bool
459
    {
460
        return isset($this->fields[$field]);
461
    }
462
463
    /**
464
     * Get field type
465
     *
466
     * @param string $field
467
     *
468
     * @return string
469
     */
470
    public function fieldType($field): string
471
    {
472
        return $this->fields[$field]['type'];
473
    }
474
475
    /**
476
     * Get attributes
477
     *
478
     * @return array<string, FieldMetadata>
479
     */
480 251
    public function attributes(): array
481
    {
482 251
        return $this->attributes;
483
    }
484
485
    /**
486
     * Does attribute exist
487
     *
488
     * @param string $attribute
489
     *
490
     * @return bool
491
     */
492
    public function attributeExists($attribute): bool
493
    {
494
        return isset($this->attributes[$attribute]);
495
    }
496
497
    /**
498
     * Get attribute type
499
     *
500
     * @param string $attribute
501
     *
502
     * @return string
503
     */
504
    public function attributeType($attribute): string
505
    {
506
        return $this->attributes[$attribute]['type'];
507
    }
508
509
    /**
510
     * Get field from attribute alias if exists
511
     *
512
     * @param string $attribute
513
     *
514
     * @return string
515
     */
516
    public function fieldFrom($attribute): string
517
    {
518
        return $this->attributes[$attribute]['field'];
519
    }
520
521
    /**
522
     * Get attribute from field alias if exists
523
     *
524
     * @param string $field
525
     *
526
     * @return string
527
     */
528
    public function attributeFrom($field): string
529
    {
530
        return $this->fields[$field]['attribute'];
531
    }
532
533
    /**
534
     * @param Mapper $mapper
535
     * @psalm-param Mapper<E> $mapper
536
     * @template E as object
537
     */
538 520
    public function build(Mapper $mapper): void
539
    {
540 520
        if (!$this->built) {
541 520
            $this->entityName = $mapper->getEntityClass();
542 520
            $this->useQuoteIdentifier = $mapper->hasQuoteIdentifier();
543 520
            $this->constraints = $mapper->constraints();
544 520
            $this->propertyAccessorClass = $mapper->getPropertyAccessorClass();
545 520
            $this->entityClass = $this->getExistingClassName($this->entityName);
546 520
            $this->instantiatorHint = $this->getInstantiatorHint($this->entityClass);
547
548 520
            $this->buildSchema($mapper->schema());
549 520
            $this->buildFields($mapper->fields());
550 519
            $this->buildSequence($mapper->sequence());
551 519
            $this->buildIndexes($mapper->indexes());
552 519
            $this->buildRelations($mapper->relations());
553
554 519
            $this->built = true;
555
        }
556
    }
557
558
    /**
559
     * Get the classname if exists
560
     *
561
     * @param class-string $entityClass
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
562
     *
563
     * @return class-string
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
564
     */
565 520
    private function getExistingClassName($entityClass): string
566
    {
567 520
        if ($entityClass === stdClass::class || !class_exists($entityClass)) {
568 6
            return stdClass::class;
569
        }
570
571 516
        return $entityClass;
572
    }
573
574
    /**
575
     * Guess the instantiator hint
576
     *
577
     * This method will check the class constructor. If it has one non optional parameter
578
     * it will return no hint. Otherwise the default constructor hiint will be returned.
579
     *
580
     * @param class-string $className
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
581
     *
582
     * @return null|int
583
     */
584 520
    private function getInstantiatorHint($className): ?int
585
    {
586 520
        if ($className === stdClass::class) {
587 6
            return InstantiatorInterface::USE_CONSTRUCTOR_HINT;
588
        }
589
590
        try {
591 518
            $constructor = (new \ReflectionClass($className))->getConstructor();
592
593 516
            if ($constructor !== null) {
594 454
                foreach ($constructor->getParameters() as $parameter) {
595 453
                    if (!$parameter->isOptional()) {
596 7
                        return null;
597
                    }
598
                }
599
            }
600
601 511
            return InstantiatorInterface::USE_CONSTRUCTOR_HINT;
602 2
        } catch (\ReflectionException $exception) {
603 2
            return null;
604
        }
605
    }
606
607
    /**
608
     * Build schema metadata
609
     *
610
     * @param array{connection: string, database?: string, table: string, tableOptions?: array} $schema
611
     */
612 520
    private function buildSchema(array $schema): void
613
    {
614 520
        $schema += [
615 520
            'connection'   => null,
616 520
            'database'     => null,
617 520
            'table'        => null,
618 520
            'tableOptions' => [],
619 520
        ];
620
621
        //TODO Comment recuperer la database si non fournie
622
        //$service->connection($this->connection)->getDatabase();
623
624 520
        $this->connection   = $schema['connection'];
625 520
        $this->database     = $schema['database'];
626 520
        $this->table        = $schema['table'];
627 520
        $this->tableOptions = $schema['tableOptions'];
628
    }
629
630
    /**
631
     * Builds fields metadata
632
     *
633
     * @param iterable<string, FieldDefinition> $fields
634
     * @param EmbeddedMetadata|null $embeddedMeta
635
     */
636 520
    private function buildFields(iterable $fields, $embeddedMeta = null): void
637
    {
638 520
        foreach ($fields as $attribute => $meta) {
639 520
            if (isset($meta['embedded'])) {
640 395
                $this->buildFields(
641 395
                    $meta['embedded'],
642 395
                    empty($meta['polymorph'])
643 395
                        ? $this->buildEmbedded($attribute, $meta['class'], $embeddedMeta)
0 ignored issues
show
Bug introduced by
It seems like $embeddedMeta can also be of type Bdf\Prime\Mapper\EmbeddedMetadata; however, parameter $embeddedMeta of Bdf\Prime\Mapper\Metadata::buildEmbedded() does only seem to accept array|null, 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
                        ? $this->buildEmbedded($attribute, $meta['class'], /** @scrutinizer ignore-type */ $embeddedMeta)
Loading history...
644 395
                        : $this->buildPolymorph($attribute, $meta, $embeddedMeta)
0 ignored issues
show
Bug introduced by
It seems like $embeddedMeta can also be of type Bdf\Prime\Mapper\EmbeddedMetadata; however, parameter $embeddedMeta of Bdf\Prime\Mapper\Metadata::buildPolymorph() does only seem to accept array|null, 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

644
                        : $this->buildPolymorph($attribute, $meta, /** @scrutinizer ignore-type */ $embeddedMeta)
Loading history...
645 395
                );
646
            } else {
647 520
                $this->buildField($attribute, $meta, $embeddedMeta);
0 ignored issues
show
Bug introduced by
It seems like $embeddedMeta can also be of type Bdf\Prime\Mapper\EmbeddedMetadata; however, parameter $embeddedMeta of Bdf\Prime\Mapper\Metadata::buildField() does only seem to accept array|null, 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

647
                $this->buildField($attribute, $meta, /** @scrutinizer ignore-type */ $embeddedMeta);
Loading history...
648
            }
649
        }
650
    }
651
652
    /**
653
     * Builds indexes metadata
654
     *
655
     * @param array $indexes
656
     */
657 519
    private function buildIndexes(array $indexes): void
658
    {
659 519
        $this->buildSimpleIndexes($indexes);
660 519
        $this->buildIndexesFromFields();
661
    }
662
663
    /**
664
     * Build simple indexes declared on mapper
665
     * The field names will be mapped with DB attributes if exists, and will be normalized into the new format
666
     *
667
     * Supports old format :
668
     * [
669
     *     'index_name' => ['field1', 'field2', ...],
670
     *     ...
671
     * ]
672
     *
673
     * And new format :
674
     * [
675
     *     'index_name' => [
676
     *         'fields' => [
677
     *             'field1' => [fieldOptions],
678
     *             'field2' => [fieldOptions],
679
     *             ...
680
     *         ],
681
     *         'unique' => true,
682
     *         'option' => 'value',
683
     *         // More options
684
     *     ],
685
     *     ...
686
     * ]
687
     *
688
     * @param array $indexes
689
     */
690 519
    private function buildSimpleIndexes(array $indexes): void
691
    {
692 519
        foreach ($indexes as $name => $index) {
693
            // Legacy format compatibility
694 15
            if (!isset($index['fields'])) {
695 5
                $fields = [];
696
697 5
                foreach ($index as $field) {
698 5
                    $fields[$field] = [];
699
                }
700
701 5
                $index = ['fields' => $fields];
702
            }
703
704 15
            $fields = [];
705
706
            // Map the field name if exists
707 15
            foreach ($index['fields'] as $field => $options) {
708 15
                if (isset($this->attributes[$field])) {
709 15
                    $field = $this->attributes[$field]['field'];
710
                }
711
712 15
                $fields[$field] = $options;
713
            }
714
715 15
            $index['fields'] = $fields;
716
717 15
            $this->indexes[$name] = $index;
718
        }
719
    }
720
721
    /**
722
     * Extract unique indexes from field declaration
723
     * Must be called after @see Metadata::buildFields()
724
     *
725
     * The unique indexes will follow same format as simple indexes, but with the option 'unique' set as true
726
     */
727 519
    private function buildIndexesFromFields(): void
728
    {
729 519
        foreach ($this->fields as $field => $meta) {
730 519
            if (empty($meta['unique'])) {
731 519
                continue;
732
            }
733
734 9
            if (is_string($meta['unique'])) {
735 1
                if (isset($this->indexes[$meta['unique']])) {
736 1
                    $this->indexes[$meta['unique']]['fields'][$field] = [];
737
                } else {
738 1
                    $this->indexes[$meta['unique']] = [
739 1
                        'fields' => [$field => []],
740 1
                        'unique' => true,
741 1
                    ];
742
                }
743
            } else {
744 9
                $this->indexes[] = [
745 9
                    'fields' => [$field => []],
746 9
                    'unique' => true,
747 9
                ];
748
            }
749
        }
750
    }
751
752
    /**
753
     * Build Embedded meta
754
     *
755
     * @param string $attribute
756
     * @param class-string $class
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
757
     * @param array|null $embeddedMeta
758
     *
759
     * @return EmbeddedMetadata
760
     */
761 452
    private function buildEmbedded(string $attribute, string $class, ?array $embeddedMeta): array
762
    {
763 452
        if ($embeddedMeta === null) {
0 ignored issues
show
introduced by
The condition $embeddedMeta === null is always false.
Loading history...
764 450
            $attributePath = $attribute;
765 450
            $path          = 'root';
766 450
            $paths         = [$attributePath];
767
        } else {
768 186
            $attributePath = $embeddedMeta['path'].'.'.$attribute;
769 186
            $path          = $embeddedMeta['path'];
770 186
            $paths         = array_merge($embeddedMeta['paths'], [$attributePath]);
771
        }
772
773 452
        return $this->embeddeds[$attributePath] = [
774 452
            'class'           => $this->getExistingClassName($class),
775 452
            'hint'            => $this->getInstantiatorHint($class),
776 452
            'path'            => $attributePath,
777 452
            'parentPath'      => $path,
778 452
            'paths'           => $paths,
779 452
        ];
780
    }
781
782
    /**
783
     * Build a polymorph embedded
784
     *
785
     * @param string $attribute
786
     * @param array $meta
787
     * @param array|null $embeddedMeta
788
     *
789
     * @return EmbeddedMetadata
790
     */
791 2
    private function buildPolymorph($attribute, array $meta, ?array $embeddedMeta): array
792
    {
793 2
        if ($embeddedMeta === null) {
0 ignored issues
show
introduced by
The condition $embeddedMeta === null is always false.
Loading history...
794 2
            $attributePath = $attribute;
795 2
            $path          = 'root';
796 2
            $paths         = [$attributePath];
797
        } else {
798
            $attributePath = $embeddedMeta['path'].'.'.$attribute;
799
            $path          = $embeddedMeta['path'];
800
            $paths         = array_merge($embeddedMeta['paths'], [$attributePath]);
801
        }
802
803 2
        $hints = [];
804
805 2
        foreach ($meta['class_map'] as $class) {
806 2
            $hints[$class] = $this->getInstantiatorHint($class);
807
        }
808
809 2
        return $this->embeddeds[$attributePath] = [
810 2
            'path'       => $attributePath,
811 2
            'parentPath' => $path,
812 2
            'paths'      => $paths,
813 2
            'hints'      => $hints
814 2
        ] + $meta;
815
    }
816
817
    /**
818
     * Build mapped embedded meta
819
     *
820
     * @todo  parcourir la map pour construire les differents embeddeds 'attribute-discriminatorValue'
821
     *
822
     * @param string $attribute
823
     * @param string[] $map
824
     * @param string $discriminator
825
     * @param array  $embeddedMeta
826
     */
827 215
    private function buildMappedEmbedded($attribute, $map, $discriminator, $embeddedMeta = null): void
0 ignored issues
show
Unused Code introduced by
The parameter $discriminator is not used and could be removed. ( Ignorable by Annotation )

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

827
    private function buildMappedEmbedded($attribute, $map, /** @scrutinizer ignore-unused */ $discriminator, $embeddedMeta = null): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
828
    {
829 215
        $entity = reset($map);
830
831 215
        if (is_string($entity)) {
832 215
            list($entity) = Relation::parseEntity($entity);
833
        } else {
834
            $entity = $entity['entity'];
835
        }
836
837 215
        $this->buildEmbedded($attribute, $entity, $embeddedMeta);
838
    }
839
840
    /**
841
     * Build embedded relations missing in embedded
842
     *
843
     * @param iterable<string, RelationDefinition> $relations
844
     */
845 519
    private function buildRelations(iterable $relations): void
846
    {
847 519
        foreach ($relations as $attribute => $relation) {
848
            // il est possible de déclarer des relations sans attribut sur l'entity (cas de grosse collection)
849 429
            if (!empty($relation['detached'])) {
850 286
                continue;
851
            }
852
853 423
            if (isset($relation['mode']) && $relation['mode'] === RelationBuilder::MODE_EAGER) {
854 143
                $this->eagerRelations[$attribute] = [];
855
            }
856
857
            // si l'attribut est déjà définit, car embedded
858 423
            if (isset($this->embeddeds[$attribute])) {
859 342
                continue;
860
            }
861
862 334
            if (isset($relation['map'])) {
863 215
                $this->buildMappedEmbedded($attribute, $relation['map'], $relation['discriminator']);
864 215
                continue;
865
            }
866
867
            // si entity n'est pas definit
868 328
            if (!isset($relation['entity'])) {
869 49
                continue;
870
            }
871
872
            // Attention: un embedded relation doit appartenir à l'entity.
873
            // Ne pas pas etre dans un object de entity
874
            // C'est pour cette raison que le 3eme parametre est à null
875 318
            $this->buildEmbedded($attribute, $relation['entity'], null);
876
        }
877
878
        // Preformatage des eager relations
879 519
        $this->eagerRelations = Relation::sanitizeRelations($this->eagerRelations);
880
    }
881
882
    /**
883
     * Build field meta
884
     *
885
     * @param string $attribute
886
     * @param FieldDefinition $meta
0 ignored issues
show
Bug introduced by
The type Bdf\Prime\Mapper\FieldDefinition was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
887
     * @param EmbeddedMetadata|null $embeddedMeta
888
     */
889 520
    private function buildField($attribute, $meta, ?array $embeddedMeta): void
890
    {
891
        //concatenation de l'attribut parent
892 520
        if ($embeddedMeta === null) {
0 ignored issues
show
introduced by
The condition $embeddedMeta === null is always false.
Loading history...
893 519
            $attributePath = $attribute;
894 519
            $path          = null;
895
        } else {
896 395
            $attributePath = $embeddedMeta['path'] . '.' . $attribute;
897 395
            $path          = $embeddedMeta['path'];
898
        }
899
900
        // TODO call 'Inflector::tableize($attributePath) ?'
901 520
        $field = isset($meta['alias']) ? $meta['alias'] : $attributePath;
902 520
        unset($meta['alias']);
903
904 520
        if (isset($this->fields[$field])) {
905 1
            throw new LogicException('Alias "' . $field . '" is already in use. If you want to use the same database field on multiple properties, you can declare an event listener for the "afterLoad", "beforeInsert" and "beforeUpdate" events, using Mapper::customEvents() to manually set the value on all properties.');
906
        }
907
908
        // TODO ne pas gerer les defaults
909 520
        $meta += [
910 520
            'primary'    => null,
911 520
            'default'    => null,
912 520
            'phpOptions' => [],
913 520
        ];
914
915 520
        $this->fields[$field] = $this->attributes[$attributePath] = [
916 520
            'field'             => $field,
917 520
            'attribute'         => $attributePath,
918 520
            'embedded'          => $path,
919 520
        ] + $meta;
920
921
        /*
922
         * Construction des meta des primary keys.
923
         * Si un champs est en auto-increment, celui-ci doit etre placé en début de tableau.
924
         */
925 520
        if ($meta['primary']) {
926 510
            if ($this->primary['type'] !== self::PK_AUTO && $meta['primary'] !== self::PK_AUTO) {
927
                throw new LogicException('Trying to set a primary key');
928
            }
929
930 510
            if ($meta['primary'] === self::PK_AUTO) {
931 289
                $this->primary['attributes'][] = $attributePath;
932 289
                $this->primary['fields'][]     = $field;
933
            } else {
934 489
                $this->primary['type'] = $meta['primary'];
935 489
                array_unshift($this->primary['attributes'], $attributePath);
936 489
                array_unshift($this->primary['fields'], $field);
937
            }
938
        }
939
    }
940
941
    /**
942
     * Build sequence info if primary is a sequence
943
     *
944
     * @param array{connection?:string,table?:string,column?:string,tableOptions?:array} $sequence
945
     */
946 519
    private function buildSequence($sequence): void
947
    {
948 519
        if (!$this->isSequencePrimaryKey()) {
949 508
            return;
950
        }
951
952 256
        $this->sequence = [
0 ignored issues
show
Documentation Bug introduced by
It seems like array('connection' => $s...leOptions'] ?? array()) of type array<string,array|mixed|string> is incompatible with the declared type Bdf\Prime\Mapper\SequenceMetadata of property $sequence.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
953 256
            'connection' => $sequence['connection'] ?? $this->connection,
954 256
            'table'      => $sequence['table'] ?? $this->table . '_seq',
955 256
            'column'     => $sequence['column'] ?? 'id',
956 256
            'options'    => $sequence['tableOptions'] ?? [],
957 256
        ];
958
    }
959
}
960