Passed
Pull Request — 2.1 (#64)
by Vincent
11:26 queued 05:25
created

Mapper::setMetadata()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Bdf\Prime\Mapper;
4
5
use Bdf\Prime\Behaviors\BehaviorInterface;
6
use Bdf\Prime\Cache\CacheInterface;
7
use Bdf\Prime\Entity\Hydrator\MapperHydrator;
8
use Bdf\Prime\Entity\Hydrator\MapperHydratorInterface;
9
use Bdf\Prime\Entity\ImportableInterface;
10
use Bdf\Prime\Exception\PrimeException;
11
use Bdf\Prime\IdGenerators\AutoIncrementGenerator;
12
use Bdf\Prime\IdGenerators\GeneratorInterface;
13
use Bdf\Prime\IdGenerators\NullGenerator;
14
use Bdf\Prime\IdGenerators\TableGenerator;
15
use Bdf\Prime\Mapper\Builder\FieldBuilder;
16
use Bdf\Prime\Mapper\Builder\IndexBuilder;
17
use Bdf\Prime\Mapper\Info\MapperInfo;
18
use Bdf\Prime\Platform\PlatformInterface;
19
use Bdf\Prime\Relations\Builder\RelationBuilder;
20
use Bdf\Prime\Relations\Exceptions\RelationNotFoundException;
21
use Bdf\Prime\Repository\EntityRepository;
22
use Bdf\Prime\Repository\RepositoryEventsSubscriberInterface;
23
use Bdf\Prime\Repository\RepositoryInterface;
24
use Bdf\Prime\ServiceLocator;
25
use Bdf\Serializer\PropertyAccessor\PropertyAccessorInterface;
26
use Bdf\Serializer\PropertyAccessor\ReflectionAccessor;
27
use LogicException;
28
use stdClass;
29
30
/**
31
 * Mapper
32
 *
33
 * Contient les méta données de la table.
34
 *
35
 * @todo Convertir la donnée avec le type approprié sur les methodes setId, hydrateOne
36
 *
37
 * @template E as object
38
 *
39
 * @psalm-import-type FieldDefinition from FieldBuilder
40
 * @psalm-import-type RelationDefinition from RelationBuilder
41
 */
42
abstract class Mapper
43
{
44
    /**
45
     * Enable/Disable query result cache on repository
46
     * If null global cache will be set.
47
     * Set it to false to deactivate cache on this repository
48
     * Set the cache instance in configure method
49
     *
50
     * @var false|CacheInterface
51
     */
52
    protected $resultCache;
53
54
    /**
55
     * @var Metadata
56
     */
57
    private $metadata;
58
59
    /**
60
     * Id generator
61
     *
62
     * Could be defined as string (generator class name). It would be instantiated
63
     * by mapper on generator() method
64
     *
65
     * @var \Bdf\Prime\IdGenerators\GeneratorInterface|null
66
     */
67
    protected $generator;
68
69
    /**
70
     * @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...
71
     */
72
    private $repositoryClass = EntityRepository::class;
73
74
    /**
75
     * The real name of entity class. Could be an none existing class
76
     *
77
     * @var class-string<E>
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<E> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<E>.
Loading history...
78
     */
79
    private $entityClass;
80
81
    /**
82
     * The property accessor class name to use by default
83
     *
84
     * @var class-string<PropertyAccessorInterface>
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<PropertyAccessorInterface> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<PropertyAccessorInterface>.
Loading history...
85
     */
86
    private $propertyAccessorClass = ReflectionAccessor::class;
87
88
    /**
89
     * Set repository read only.
90
     *
91
     * @var bool
92
     */
93
    private $readOnly = false;
94
95
    /**
96
     * Use schema resolver
97
     * Disable if schema has not to be manage by this app
98
     *
99
     * @var bool
100
     */
101
    private $useSchemaManager = true;
102
103
    /**
104
     * Use quote identifier
105
     * Allows query builder to use quote identifier
106
     *
107
     * @var bool
108
     */
109
    private $useQuoteIdentifier = false;
110
111
    /**
112
     * The relation builder
113
     *
114
     * @var RelationBuilder
115
     */
116
    private $relationBuilder;
117
118
    /**
119
     * The collection of behaviors
120
     *
121
     * @var BehaviorInterface<E>[]
122
     */
123
    private $behaviors;
124
125
    /**
126
     * The service locator
127
     *
128
     * @var ServiceLocator
129
     */
130
    protected $serviceLocator;
131
132
    /**
133
     * @var MapperHydratorInterface<E>|null
134
     */
135
    protected $hydrator;
136
137
138
    /**
139
     * Mapper constructor
140
     *
141
     * @param ServiceLocator $serviceLocator
142
     * @param class-string<E>|null $entityClass
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<E>|null at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<E>|null.
Loading history...
143
     * @param Metadata|null $metadata
144
     * @param MapperHydratorInterface<E>|null $hydrator
145
     * @param CacheInterface|null $resultCache
146
     */
147 528
    public function __construct(ServiceLocator $serviceLocator, ?string $entityClass = null, ?Metadata $metadata = null, MapperHydratorInterface $hydrator = null, CacheInterface $resultCache = null)
148
    {
149 528
        $this->entityClass = $entityClass ?? stdClass::class;
150 528
        $this->metadata = $metadata;
151 528
        $this->serviceLocator = $serviceLocator;
152 528
        $this->resultCache = $resultCache;
153 528
        $this->hydrator = $hydrator;
154
    }
155
156
    /**
157
     * Custom configuration
158
     */
159 523
    public function configure(): void
160
    {
161
        // to overwrite
162 523
    }
163
164
    /**
165
     * Get entity class
166
     *
167
     * @return class-string<E>
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<E> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<E>.
Loading history...
168
     * @final
169
     */
170 540
    public function getEntityClass(): string
171
    {
172 540
        return $this->entityClass;
173
    }
174
175
    /**
176
     * Get metadata
177
     *
178
     * @return Metadata
179
     * @final
180
     */
181 1257
    public function metadata(): Metadata
182
    {
183 1257
        return $this->metadata;
184
    }
185
186
    /**
187
     * Set property accessor class name
188
     *
189
     * @param class-string<PropertyAccessorInterface> $className
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<PropertyAccessorInterface> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<PropertyAccessorInterface>.
Loading history...
190
     * @final
191
     */
192 1
    public function setPropertyAccessorClass(string $className): void
193
    {
194 1
        $this->propertyAccessorClass = $className;
195
    }
196
197
    /**
198
     * Get property accessor class name
199
     *
200
     * @return class-string<PropertyAccessorInterface>
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<PropertyAccessorInterface> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<PropertyAccessorInterface>.
Loading history...
201
     * @final
202
     */
203 522
    public function getPropertyAccessorClass(): string
204
    {
205 522
        return $this->propertyAccessorClass;
206
    }
207
208
    /**
209
     * Set repository class name
210
     *
211
     * @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...
212
     * @final
213
     */
214 1
    public function setRepositoryClass(string $className): void
215
    {
216 1
        $this->repositoryClass = $className;
217
    }
218
219
    /**
220
     * Get repository class name
221
     *
222
     * @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...
223
     * @final
224
     */
225 2
    public function getRepositoryClass(): string
226
    {
227 2
        return $this->repositoryClass;
228
    }
229
230
    /**
231
     * Set the repository read only
232
     *
233
     * @param bool $flag
234
     * @final
235
     */
236 556
    public function setReadOnly(bool $flag): void
237
    {
238 556
        $this->readOnly = $flag;
239
    }
240
241
    /**
242
     * Get repository read only state
243
     *
244
     * @return bool
245
     * @final
246
     */
247 665
    public function isReadOnly(): bool
248
    {
249 665
        return $this->readOnly;
250
    }
251
252
    /**
253
     * Disable schema manager on repository
254
     * @final
255
     */
256 1
    public function disableSchemaManager(): void
257
    {
258 1
        $this->useSchemaManager = false;
259
    }
260
261
    /**
262
     * Does repository have a schema manager
263
     *
264
     * @return bool
265
     * @final
266
     */
267 872
    public function hasSchemaManager(): bool
268
    {
269 872
        return $this->useSchemaManager;
270
    }
271
272
    /**
273
     * Set the query builder quote identifier
274
     *
275
     * @param bool $flag
276
     * @final
277
     */
278 3
    public function setQuoteIdentifier(bool $flag): void
279
    {
280 3
        $this->useQuoteIdentifier = $flag;
281
    }
282
283
    /**
284
     * Does query builder use quote identifier
285
     *
286
     * @return bool
287
     * @final
288
     */
289 522
    public function hasQuoteIdentifier(): bool
290
    {
291 522
        return $this->useQuoteIdentifier;
292
    }
293
294
    /**
295
     * Set generator ID
296
     *
297
     * @param string|GeneratorInterface $generator
298
     * @final
299
     */
300 3
    public function setGenerator($generator): void
301
    {
302 3
        if (!is_string($generator) && !$generator instanceof GeneratorInterface) {
0 ignored issues
show
introduced by
$generator is always a sub-type of Bdf\Prime\IdGenerators\GeneratorInterface.
Loading history...
303 1
            throw new LogicException('Trying to set an invalid generator in "' . get_class($this) . '"');
304
        }
305
306 2
        $this->generator = $generator;
0 ignored issues
show
Documentation Bug introduced by
It seems like $generator can also be of type string. However, the property $generator is declared as type Bdf\Prime\IdGenerators\GeneratorInterface|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
307
    }
308
309
    /**
310
     * Get generator ID
311
     *
312
     * @return GeneratorInterface
313
     * @final
314
     */
315 647
    public function generator(): GeneratorInterface
316
    {
317 647
        if ($this->generator === null) {
318 307
            if ($this->metadata->isAutoIncrementPrimaryKey()) {
319 209
                $this->generator = new AutoIncrementGenerator($this);
320 262
            } elseif ($this->metadata->isSequencePrimaryKey()) {
321 229
                $this->generator = new TableGenerator($this);
322
            } else {
323 307
                $this->generator = new NullGenerator();
324
            }
325 601
        } elseif (is_string($this->generator)) {
0 ignored issues
show
introduced by
The condition is_string($this->generator) is always false.
Loading history...
326 1
            $className = $this->generator;
327 1
            $this->generator = new $className($this);
328
        }
329
330 647
        return $this->generator;
331
    }
332
333
    /**
334
     * @return MapperHydratorInterface<E>
335
     * @final
336
     *
337
     * @psalm-suppress InvalidNullableReturnType
338
     * @psalm-suppress NullableReturnStatement
339
     */
340 6
    public function hydrator(): MapperHydratorInterface
341
    {
342 6
        return $this->hydrator;
343
    }
344
345
    /**
346
     * @param MapperHydratorInterface<E> $hydrator
347
     *
348
     * @return $this
349
     * @final
350
     */
351 476
    public function setHydrator(MapperHydratorInterface $hydrator)
352
    {
353 476
        $this->hydrator = $hydrator;
354 476
        $this->hydrator->setPrimeInstantiator($this->serviceLocator->instantiator());
355
356 476
        if ($this->metadata !== null) {
357
            $this->hydrator->setPrimeMetadata($this->metadata);
358
        }
359
360 476
        return $this;
361
    }
362
363
    /**
364
     * Set ID value en entity
365
     * Only sequenceable attribute is set (the first one)
366
     *
367
     * @param E $entity
0 ignored issues
show
Bug introduced by
The type Bdf\Prime\Mapper\E 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...
368
     * @param mixed $value
369
     *
370
     * @return void
371
     * @final
372
     */
373 4
    public function setId($entity, $value): void
374
    {
375 4
        $this->hydrateOne($entity, $this->metadata->primary['attributes'][0], $value);
376
    }
377
378
    /**
379
     * Get ID value of an entity
380
     * Only sequenceable attribute is get (the first one)
381
     *
382
     * @param E $entity
383
     *
384
     * @return mixed
385
     * @final
386
     */
387 107
    public function getId($entity)
388
    {
389 107
        return $this->extractOne($entity, $this->metadata->primary['attributes'][0]);
390
    }
391
392
    /**
393
     * Get attribute value of an entity
394
     *
395
     * @param E $entity
396
     * @param string $attribute
397
     *
398
     * @return mixed
399
     * @final
400
     */
401 450
    public function extractOne($entity, string $attribute)
402
    {
403 450
        return $this->hydrator->extractOne($entity, $attribute);
404
    }
405
406
    /**
407
     * Hydrate on property value of an entity
408
     *
409
     * @param E $entity
410
     * @param string $attribute
411
     * @param mixed  $value
412
     *
413
     * @return void
414
     * @final
415
     */
416 374
    public function hydrateOne($entity, string $attribute, $value): void
417
    {
418 374
        $this->hydrator->hydrateOne($entity, $attribute, $value);
419
    }
420
421
    /**
422
     * Get primary key criteria
423
     *
424
     * @param E $entity
425
     *
426
     * @return array
427
     * @final
428
     */
429 173
    public function primaryCriteria($entity): array
430
    {
431 173
        return $this->hydrator->flatExtract($entity, array_flip($this->metadata->primary['attributes']));
432
    }
433
434
    /**
435
     * Instanciate the related class entity
436
     *
437
     * @return E
438
     * @final
439
     */
440 529
    public function instantiate()
441
    {
442
        /** @var E */
443 529
        return $this->serviceLocator->instantiator()
444 529
            ->instantiate($this->metadata->entityClass, $this->metadata->instantiatorHint);
445
    }
446
447
    /**
448
     * User api to instantiate related entity
449
     *
450
     * @param array $data
451
     *
452
     * @return E
453
     * @final
454
     */
455 97
    public function entity(array $data)
456
    {
457 97
        $entity = $this->instantiate();
458
459
        // Allows custom import from developpers.
460 97
        if ($entity instanceof ImportableInterface) {
461 96
            $entity->import($data);
462
        } else {
463 1
            $this->serviceLocator->hydrator($this->metadata->entityClass)
464 1
                ->hydrate($entity, $data);
465
        }
466
467 97
        return $entity;
468
    }
469
470
    /**
471
     * Transform entity to db one dimension array
472
     *
473
     * @param E $entity Entity object
474
     * @param array|null $attributes  Attribute should be flipped as ['key' => true]
475
     *
476
     * @return array
477
     * @final
478
     */
479 658
    public function prepareToRepository($entity, array $attributes = null): array
480
    {
481 658
        return $this->hydrator->flatExtract($entity, $attributes);
482
    }
483
484
    /**
485
     * Get valid array for entity
486
     *
487
     * Inject one dimension array (db field) into entity
488
     * Map attribute and cast value
489
     *
490
     * $optimisation est un tableau donné par le query builder dans le but
491
     * d'optimiser le chargement des relations et des tableaux associatifs. Il contient les entités regroupés par
492
     * la valeur du champs demandé
493
     *
494
     * @param array             $data  Db data
495
     * @param PlatformInterface $platform
496
     *
497
     * @return E
498
     */
499 472
    public function prepareFromRepository(array $data, PlatformInterface $platform)
500
    {
501 472
        $entity = $this->instantiate();
502
503 472
        $this->hydrator->flatHydrate($entity, $data, $platform->types());
504
505 472
        return $entity;
506
    }
507
508
    /**
509
     * Get the repository
510
     *
511
     * @return RepositoryInterface<E>
512
     * @final
513
     */
514 416
    public function repository(): RepositoryInterface
515
    {
516 416
        $className = $this->repositoryClass;
517
518 416
        return new $className($this, $this->serviceLocator, $this->resultCache === false ? null : $this->resultCache);
519
    }
520
521
    /**
522
     * Get the mapper info
523
     *
524
     * @return MapperInfo
525
     * @throws PrimeException
526
     * @final
527
     */
528 77
    public function info(): MapperInfo
529
    {
530 77
        $platform = $this->serviceLocator->connection($this->metadata()->connection)->platform();
531
532 77
        return new MapperInfo($this, $platform->types());
533
    }
534
535
    /**
536
     * Get defined relation
537
     *
538
     * Build object relation defined by user
539
     *
540
     * @param string $relationName
541
     *
542
     * @return array  Metadata for relation definition
543
     *
544
     * @throws \RuntimeException  If relation or type does not exist
545
     */
546 266
    public function relation(string $relationName): array
547
    {
548 266
        $relations = $this->relations();
549
550 266
        if (!isset($relations[$relationName])) {
551 7
            throw new RelationNotFoundException('Relation "' . $relationName . '" is not set in ' . $this->metadata->entityName);
552
        }
553
554 261
        return $relations[$relationName];
0 ignored issues
show
Bug Best Practice introduced by
The expression return $relations[$relationName] returns the type Bdf\Prime\Relations\Builder\RelationDefinition which is incompatible with the type-hinted return array.
Loading history...
555
    }
556
557
    //
558
    //------------ API configuration du mapping
559
    //
560
561
    /**
562
     * Definition du schema
563
     *
564
     * Definition
565
     *  - connection         : The connection name declare in connection manager (mandatory).
566
     *  - database           : The database name.
567
     *  - table              : The table name (mandatory).
568
     *  - tableOptions       : The table options (ex: engine => myisam).
569
     *
570
     * <code>
571
     *  return [
572
     *     'connection'   => (string),
573
     *     'database'     => (string),
574
     *     'table'        => (string),
575
     *     'tableOptions' => (array),
576
     *  ];
577
     * </code>
578
     *
579
     * @return array
580
     */
581
    abstract public function schema(): array;
582
583
    /**
584
     * Gets repository fields builder
585
     *
586
     * @return iterable<string, FieldDefinition>
587
     * @final
588
     *
589
     * @todo should be final
590
     */
591 520
    public function fields(): iterable
592
    {
593 520
        $builder = new FieldBuilder();
594 520
        $this->buildFields($builder);
595
596 520
        foreach ($this->behaviors() as $behavior) {
597 4
            $behavior->changeSchema($builder);
598
        }
599
600 520
        return $builder;
601
    }
602
603
    /**
604
     * Build fields from this mapper.
605
     *
606
     * To overwrite.
607
     *
608
     * @param FieldBuilder $builder
609
     */
610
    public function buildFields(FieldBuilder $builder): void
0 ignored issues
show
Unused Code introduced by
The parameter $builder 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

610
    public function buildFields(/** @scrutinizer ignore-unused */ FieldBuilder $builder): 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...
611
    {
612
        throw new LogicException('Fields must be defined in mapper '.__CLASS__);
613
    }
614
615
    /**
616
     * Sequence definition.
617
     *
618
     * The metadata will build the sequence info using this method if the primary key is defined as sequence (Metadata::PK_SEQUENCE).
619
     * Definition:
620
     *  - connection         : The connection name declare in connection manager. The table connection will be used by default.
621
     *  - table              : The table sequence name.
622
     *                         The table name with suffix '_seq' will be used by default.
623
     *  - column             : The sequence column name. Default 'id'.
624
     *  - tableOptions       : The sequence table options (ex: engine => myisam).
625
     *
626
     * <code>
627
     *  return [
628
     *     'connection'   => (string),
629
     *     'table'        => (string),
630
     *     'column'       => (string),
631
     *     'tableOptions' => (array),
632
     *  ];
633
     * </code>
634
     *
635
     * @return 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...
636
     *     connection?: string|null,
637
     *     table?: string|null,
638
     *     column?: string|null,
639
     *     tableOptions?: array,
640
     * }
641
     */
642 514
    public function sequence(): array
643
    {
644 514
        return [
645 514
            'connection'   => null,
646 514
            'table'        => null,
647 514
            'column'       => null,
648 514
            'tableOptions' => [],
649 514
        ];
650
    }
651
652
    /**
653
     * Gets custom filters
654
     * To overwrite
655
     *
656
     * <code>
657
     *  return [
658
     *      'customFilterName' => function(<Bdf\Prime\Query\QueryInterface> $query, <mixed> $value) {
659
     *          return <void>
660
     *      },
661
     *  ];
662
     * </code>
663
     *
664
     * @return array<string, callable>
665
     */
666 458
    public function filters(): array
667
    {
668 458
        return [];
669
    }
670
671
    /**
672
     * Array of index
673
     *
674
     * <code>
675
     *  return [
676
     *      ['attribute1', 'attribute2']
677
     *  ];
678
     * </code>
679
     *
680
     * @return array
681
     * @final
682
     *
683
     * @todo Make final
684
     */
685 518
    public function indexes(): array
686
    {
687 518
        $builder = new IndexBuilder();
688
689 518
        $this->buildIndexes($builder);
690
691 518
        return $builder->build();
692
    }
693
694
    /**
695
     * Build the table indexes
696
     * Note: Indexes can be added on undeclared fields
697
     *
698
     * <code>
699
     * public function buildIndexes(IndexBuilder $builder)
700
     * {
701
     *     $builder
702
     *         ->add()->on('name')->unique()
703
     *         ->add()->on('reference', ['length' => 12])
704
     *         ->add()->on(['type', 'date'])
705
     * }
706
     * </code>
707
     *
708
     * @param IndexBuilder $builder
709
     */
710 508
    public function buildIndexes(IndexBuilder $builder): void
0 ignored issues
show
Unused Code introduced by
The parameter $builder 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

710
    public function buildIndexes(/** @scrutinizer ignore-unused */ IndexBuilder $builder): 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...
711
    {
712 508
    }
713
714
    /**
715
     * Repository extension
716
     * returns additional methods in repository
717
     *
718
     * <code>
719
     * return [
720
     *     'customMethod' => function($query, $test) {
721
     *
722
     *     },
723
     * ];
724
     *
725
     * $repository->customMethod('test');
726
     * </code>
727
     *
728
     * @return array<string, callable>
729
     */
730
    public function scopes(): array
731
    {
732
        throw new LogicException('No scopes have been defined in "' . get_class($this) . '"');
733
    }
734
735
    /**
736
     * Get custom queries for repository
737
     * A custom query works mostly like scopes, but with some differences :
738
     * - Cannot be called using a query (i.e. $query->where(...)->myScope())
739
     * - The function has responsability of creating the query instance
740
     * - The first argument is the repository
741
     *
742
     * <code>
743
     * return [
744
     *     'findByCustom' => function (EntityRepository $repository, $search) {
745
     *         return $repository->make(MyCustomQuery::class)->where('first', $search)->first();
746
     *     }
747
     * ];
748
     * </code>
749
     *
750
     * @return array<string, callable(\Bdf\Prime\Repository\RepositoryInterface<E>,mixed...):mixed>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string, callable(\...ace<E>,mixed...):mixed> at position 4 could not be parsed: Expected '>' at position 4, but found 'callable'.
Loading history...
751
     */
752 399
    public function queries(): array
753
    {
754 399
        return [];
755
    }
756
757
    /**
758
     * Register event on notifier
759
     *
760
     * @param RepositoryEventsSubscriberInterface<E> $notifier
761
     * @final
762
     */
763 416
    public function events(RepositoryEventsSubscriberInterface $notifier): void
764
    {
765 416
        $this->customEvents($notifier);
766
767 416
        foreach ($this->behaviors() as $behavior) {
768 4
            $behavior->subscribe($notifier);
769
        }
770
    }
771
772
    /**
773
     * Register custom event on notifier
774
     *
775
     * To overwrite.
776
     *
777
     * @param RepositoryEventsSubscriberInterface<E> $notifier
778
     */
779 383
    public function customEvents(RepositoryEventsSubscriberInterface $notifier): void
780
    {
781
        // To overwrite
782 383
    }
783
784
    /**
785
     * Get all behaviors
786
     *
787
     * @return BehaviorInterface<E>[]
788
     */
789 523
    final public function behaviors(): array
790
    {
791 523
        if ($this->behaviors === null) {
792 523
            $this->behaviors = $this->getDefinedBehaviors();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getDefinedBehaviors() of type array is incompatible with the declared type Bdf\Prime\Behaviors\BehaviorInterface of property $behaviors.

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...
793
        }
794
795 523
        return $this->behaviors;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->behaviors could return the type Bdf\Prime\Behaviors\BehaviorInterface which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
796
    }
797
798
    /**
799
     * Custom definition of behaviors
800
     *
801
     * To overwrite.
802
     *
803
     * @return BehaviorInterface<E>[]
804
     */
805 520
    public function getDefinedBehaviors(): array
806
    {
807 520
        return [];
0 ignored issues
show
Bug Best Practice introduced by
The expression return array() returns the type array which is incompatible with the documented return type Bdf\Prime\Behaviors\BehaviorInterface.
Loading history...
808
    }
809
810
    /**
811
     * Get all relations
812
     *
813
     * @return array<string, RelationDefinition>
814
     * @final
815
     *
816
     * @todo should be final
817
     */
818 657
    public function relations(): array
819
    {
820 657
        if ($this->relationBuilder === null) {
821 521
            $this->relationBuilder = new RelationBuilder();
822 521
            $this->buildRelations($this->relationBuilder);
823
        }
824
825 657
        return $this->relationBuilder->relations();
826
    }
827
828
    /**
829
     * Build relations from this mapper.
830
     *
831
     * To overwrite.
832
     *
833
     * @param RelationBuilder $builder
834
     */
835 207
    public function buildRelations(RelationBuilder $builder): void
0 ignored issues
show
Unused Code introduced by
The parameter $builder 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

835
    public function buildRelations(/** @scrutinizer ignore-unused */ RelationBuilder $builder): 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...
836
    {
837
        // to overwrite
838 207
    }
839
840
    /**
841
     * Get all constraints
842
     *
843
     * @return array
844
     */
845 523
    final public function constraints(): array
846
    {
847 523
        $constraints = $this->customConstraints();
848
849 523
        foreach ($this->behaviors() as $behavior) {
850 4
            $constraints += $behavior->constraints();
851
        }
852
853 523
        return $constraints;
854
    }
855
856
    /**
857
     * Register custom event on notifier
858
     *
859
     * To overwrite.
860
     *
861
     * <code>
862
     * return [
863
     *     'attribute' => 'value'
864
     * ]
865
     * </code>
866
     *
867
     * @return array
868
     */
869 501
    public function customConstraints(): array
870
    {
871 501
        return [];
872
    }
873
874
    /**
875
     * Clear dependencies for break cyclic references
876
     *
877
     * @internal
878
     */
879 1
    public function destroy(): void
880
    {
881 1
        $this->serviceLocator = null;
882 1
        $this->generator = null;
883 1
        $this->hydrator = null;
884 1
        $this->metadata = null;
885
    }
886
887
    /**
888
     * @internal
889
     */
890
    public function setResultCache(CacheInterface $resultCache): void
891
    {
892
        $this->resultCache = $resultCache;
893
    }
894
895
    /**
896
     * @internal
897
     */
898 2
    public function setMetadata(Metadata $metadata): void
899
    {
900 2
        $this->metadata = $metadata;
901
    }
902
903
    /**
904
     * @param class-string<E> $entityClass
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<E> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<E>.
Loading history...
905
     * @internal
906
     */
907 447
    public function setEntityClass(string $entityClass): void
908
    {
909 447
        $this->entityClass = $entityClass;
910
    }
911
912
    /**
913
     * Must be called after constructor and setters
914
     *
915
     * @internal
916
     */
917 525
    public function build(): void
918
    {
919 525
        $metadata = $this->metadata;
920
921 525
        if (!$metadata) {
0 ignored issues
show
introduced by
$metadata is of type Bdf\Prime\Mapper\Metadata, thus it always evaluated to true.
Loading history...
922 520
            $metadata = $this->metadata = new Metadata();
923
        }
924
925 525
        $this->configure();
926
927 525
        $metadata->build($this);
928
929 525
        $this->hydrator ??= new MapperHydrator();
930 525
        $this->hydrator->setPrimeMetadata($metadata);
931 525
        $this->hydrator->setPrimeInstantiator($this->serviceLocator->instantiator());
932
    }
933
}
934