EntityRepository::saving()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.032

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 9
ccs 4
cts 5
cp 0.8
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 2
crap 2.032
1
<?php
2
3
namespace Bdf\Prime\Repository;
4
5
use BadMethodCallException;
6
use Bdf\Event\EventNotifier;
7
use Bdf\Prime\Cache\CacheInterface;
8
use Bdf\Prime\Collection\CollectionFactory;
9
use Bdf\Prime\Collection\CollectionInterface;
10
use Bdf\Prime\Collection\EntityCollection;
11
use Bdf\Prime\Collection\Indexer\SingleEntityIndexer;
12
use Bdf\Prime\Connection\ConnectionInterface;
13
use Bdf\Prime\Connection\Event\ConnectionClosedListenerInterface;
14
use Bdf\Prime\Connection\TransactionManagerInterface;
15
use Bdf\Prime\Entity\Criteria;
16
use Bdf\Prime\Events;
17
use Bdf\Prime\Exception\EntityNotFoundException;
18
use Bdf\Prime\Exception\PrimeException;
19
use Bdf\Prime\Mapper\Mapper;
20
use Bdf\Prime\Mapper\Metadata;
21
use Bdf\Prime\Query\Contract\ReadOperation;
22
use Bdf\Prime\Query\Contract\WriteOperation;
23
use Bdf\Prime\Query\QueryInterface;
24
use Bdf\Prime\Query\QueryRepositoryExtension;
25
use Bdf\Prime\Relations\EntityRelation;
26
use Bdf\Prime\Relations\Relation;
27
use Bdf\Prime\Relations\RelationInterface;
28
use Bdf\Prime\Repository\Write\Writer;
29
use Bdf\Prime\Repository\Write\WriterInterface;
30
use Bdf\Prime\Schema\NullStructureUpgrader;
31
use Bdf\Prime\Schema\RepositoryUpgrader;
32
use Bdf\Prime\Schema\StructureUpgraderInterface;
33
use Bdf\Prime\ServiceLocator;
34
use Closure;
35
use Doctrine\Common\EventSubscriber;
36
use Exception;
37
38
/**
39
 * Db repository
40
 *
41
 * implementation de l'abstraction d'un dépot de données.
42
 *
43
 * @todo fix: il est possible de desactiver temporairement le cache sur des methodes d ecriture
44
 *
45
 * @package Bdf\Prime\Repository
46
 *
47
 * @template E as object
48
 * @implements RepositoryInterface<E>
49
 * @implements RepositoryEventsSubscriberInterface<E>
50
 *
51
 * @mixin RepositoryQueryFactory<E>
52
 * @mixin QueryInterface<ConnectionInterface, E>
53
 *
54
 * @method E|null get(mixed $key)
55
 * @method E getOrFail(mixed $key)
56
 * @method E getOrNew(mixed $key)
57
 */
58
class EntityRepository implements RepositoryInterface, EventSubscriber, ConnectionClosedListenerInterface, RepositoryEventsSubscriberInterface
59
{
60
    use EventNotifier;
61
62
    /**
63
     * @var Mapper<E>
64
     */
65
    protected $mapper;
66
67
    /**
68
     * @var ServiceLocator
69
     */
70
    protected $serviceLocator;
71
72
    /**
73
     * Query result cache
74
     *
75
     * @var CacheInterface
76
     */
77
    protected $resultCache;
78
79
    /**
80
     * Disable the global constraints for one query
81
     *
82
     * @var bool
83
     */
84
    protected $withoutConstraints = false;
85
86
    /**
87
     * Cache of relation instance
88
     *
89
     * @var array<string, RelationInterface<E, object>>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string, RelationInterface<E, object>> at position 4 could not be parsed: Expected '>' at position 4, but found 'RelationInterface'.
Loading history...
90
     */
91
    protected $relations = [];
92
93
    /**
94
     * The collection factory
95
     *
96
     * @var CollectionFactory
97
     */
98
    protected $collectionFactory;
99
100
    /**
101
     * @var RepositoryQueryFactory<E>
102
     */
103
    protected $queries;
104
105
    /**
106
     * @var WriterInterface<E>
107
     */
108
    protected $writer;
109
110
    /**
111
     * @var ConnectionInterface|null
112
     */
113
    protected $connection;
114
115
116
    /**
117
     * Constructor
118
     *
119
     * @param Mapper<E> $mapper
120
     * @param ServiceLocator $serviceLocator
121
     * @param CacheInterface|null $cache
122
     */
123 411
    public function __construct(Mapper $mapper, ServiceLocator $serviceLocator, ?CacheInterface $cache = null)
124
    {
125 411
        $this->resultCache = $cache;
126 411
        $this->mapper = $mapper;
127 411
        $this->serviceLocator = $serviceLocator;
128
129 411
        $this->collectionFactory = CollectionFactory::forRepository($this);
130 411
        $this->queries = new RepositoryQueryFactory($this, $cache);
131 411
        $this->writer = new Writer($this, $serviceLocator);
132
133 411
        $this->mapper->events($this);
134
    }
135
136
    /**
137
     * {@inheritdoc}
138
     */
139 355
    public function repository($entity): ?RepositoryInterface
140
    {
141 355
        return $this->serviceLocator->repository($entity);
142
    }
143
144
    /**
145
     * {@inheritdoc}
146
     */
147 1085
    public function mapper(): Mapper
148
    {
149 1085
        return $this->mapper;
150
    }
151
152
    /**
153
     * {@inheritdoc}
154
     */
155 879
    public function metadata(): Metadata
156
    {
157 879
        return $this->mapper->metadata();
158
    }
159
160
    /**
161
     * {@inheritdoc}
162
     */
163 414
    public function isReadOnly(): bool
164
    {
165 414
        return $this->mapper->isReadOnly();
166
    }
167
168
    /**
169
     * {@inheritdoc}
170
     */
171 1
    public function criteria(array $criteria = []): Criteria
172
    {
173 1
        return new Criteria($criteria);
174
    }
175
176
    /**
177
     * {@inheritdoc}
178
     */
179 94
    public function entity(array $data = [])
180
    {
181 94
        return $this->mapper->entity($data);
182
    }
183
184
    /**
185
     * {@inheritdoc}
186
     */
187 101
    public function entityName(): string
188
    {
189 101
        return $this->mapper->metadata()->entityName;
190
    }
191
192
    /**
193
     * {@inheritdoc}
194
     */
195 178
    public function entityClass(): string
196
    {
197 178
        return $this->mapper->metadata()->entityClass;
198
    }
199
200
    /**
201
     * {@inheritdoc}
202
     */
203 20
    public function collection(array $entities = []): CollectionInterface
204
    {
205 20
        return new EntityCollection($this, $entities);
0 ignored issues
show
Bug introduced by
$entities of type array is incompatible with the type Bdf\Prime\Collection\CollectionInterface expected by parameter $storage of Bdf\Prime\Collection\Ent...llection::__construct(). ( Ignorable by Annotation )

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

205
        return new EntityCollection($this, /** @scrutinizer ignore-type */ $entities);
Loading history...
206
    }
207
208
    /**
209
     * {@inheritdoc}
210
     */
211 675
    public function collectionFactory(): CollectionFactory
212
    {
213 675
        return $this->collectionFactory;
214
    }
215
216
    /**
217
     * Hydrate on property value of an entity
218
     *
219
     * @param E $entity
220
     * @param string $property
221
     * @param mixed  $value
222
     *
223
     * @return void
224
     *
225
     * @see Mapper::hydrateOne()
226
     */
227 8
    public function hydrateOne($entity, string $property, $value): void
228
    {
229 8
        $this->mapper->hydrateOne($entity, $property, $value);
230
    }
231
232
    /**
233
     * Get attribute value of an entity
234
     *
235
     * @param E $entity
236
     * @param string $property
237
     *
238
     * @return mixed
239
     *
240
     * @see Mapper::extractOne()
241
     */
242 70
    public function extractOne($entity, string $property)
243
    {
244 70
        return $this->mapper->extractOne($entity, $property);
245
    }
246
247
    /**
248
     * {@inheritdoc}
249
     */
250 827
    public function connection(): ConnectionInterface
251
    {
252 827
        if ($this->connection === null) {
253
            //Repository query factory load the connection on its constructor. Use lazy to let the connection being loaded as late as possible.
254 353
            $this->connection = $this->serviceLocator->connection($this->mapper->metadata()->connection);
255 353
            $this->connection->getEventManager()->addEventSubscriber($this);
256
        }
257
258 827
        return $this->connection;
259
    }
260
261
    /**
262
     * Set the new connection for next queries
263
     *
264
     * If work is set, the connection will be available only for the work content
265
     * Else the repository will be linked with this new connection
266
     *
267
     * @param string   $connection
268
     * @param Closure|null $work
269
     *
270
     * @return $this|mixed  Returns the work result if set or the instance if not set
271
     */
272 6
    public function on($connection, ?Closure $work = null)
273
    {
274 6
        $original = $this->changeActiveConnection($connection);
275
276 6
        if ($work !== null) {
277
            try {
278 3
                return $work($this);
279
            } finally {
280 3
                $this->changeActiveConnection($original);
281
            }
282
        }
283
284 3
        return $this;
285
    }
286
287
    /**
288
     * Launch transactional queries
289
     *
290
     * @param callable(EntityRepository):R $work
291
     * @return R
292
     *
293
     * @throws Exception
294
     * @throws PrimeException
295
     *
296
     * @template R
297
     */
298 56
    public function transaction(callable $work)
299
    {
300 56
        $connection = $this->connection();
301
302 56
        if (!$connection instanceof TransactionManagerInterface) {
303
            throw new BadMethodCallException('Transactions are not supported by the connection '.$connection->getName());
304
        }
305
306
        // @todo handle Doctrine DBAL Exception ?
307
        // @todo transaction method on connection ?
308
        try {
309 56
            $connection->beginTransaction();
310
311 56
            $result = $work($this);
312
313 55
            if ($result === false) {
314
                $connection->rollback();
315
            } else {
316 55
                $connection->commit();
317
            }
318 1
        } catch (Exception $e) {
319 1
            $connection->rollback();
320
321 1
            throw $e;
322
        }
323
324 55
        return $result;
325
    }
326
327
    /**
328
     * Load relations on given entity
329
     * If the relation is already loaded, the relation will not be reloaded
330
     * Use reloadRelation for force loading
331
     *
332
     * @param E $entity
333
     * @param string|array $relations
334
     *
335
     * @return void
336
     * @throws PrimeException
337
     *
338
     * @see EntityRepository::reloadRelations() For force load relations
339
     */
340
    #[ReadOperation]
341 72
    public function loadRelations($entity, $relations): void
342
    {
343 72
        foreach (Relation::sanitizeRelations((array)$relations) as $relationName => $meta) {
344 72
            $this->relation($relationName)->loadIfNotLoaded(
345 72
                new SingleEntityIndexer($this->mapper, $entity),
346 72
                $meta['relations'],
347 72
                $meta['constraints']
348 72
            );
349
        }
350
    }
351
352
    /**
353
     * Force loading relations on given entity
354
     *
355
     * @param E $entity
356
     * @param string|array $relations
357
     *
358
     * @return void
359
     * @throws PrimeException
360
     *
361
     * @see EntityRepository::loadRelations() For loading relation only if not yet loaded
362
     */
363
    #[ReadOperation]
364 9
    public function reloadRelations($entity, $relations): void
365
    {
366 9
        foreach (Relation::sanitizeRelations((array)$relations) as $relationName => $meta) {
367 9
            $this->relation($relationName)->load(
368 9
                new SingleEntityIndexer($this->mapper, $entity),
369 9
                $meta['relations'],
370 9
                $meta['constraints']
371 9
            );
372
        }
373
    }
374
375
    /**
376
     * Get a entity relation wrapper linked to the entity
377
     *
378
     * @param string $relationName
379
     * @param E $entity
380
     *
381
     * @return EntityRelation<E, object>
382
     */
383 166
    public function onRelation(string $relationName, $entity): EntityRelation
384
    {
385 166
        return new EntityRelation($entity, $this->relation($relationName));
386
    }
387
388
    /**
389
     * {@inheritdoc}
390
     */
391 414
    public function relation(string $relationName): RelationInterface
392
    {
393 414
        if (!isset($this->relations[$relationName])) {
394 265
            $this->relations[$relationName] = Relation::make($this, $relationName, $this->mapper->relation($relationName));
395
        }
396
397 410
        return $this->relations[$relationName];
398
    }
399
400
    /**
401
     * {@inheritdoc}
402
     */
403
    #[WriteOperation]
404 27
    public function saveAll($entity, $relations): int
405
    {
406 27
        $relations = Relation::sanitizeRelations((array)$relations);
407
408 27
        return $this->transaction(function () use ($entity, $relations) {
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->transactio...ion(...) { /* ... */ }) returns the type Bdf\Prime\Repository\R which is incompatible with the type-hinted return integer.
Loading history...
409 27
            $nb = $this->save($entity);
410
411 27
            foreach ($relations as $relationName => $info) {
412 13
                $nb += $this->relation($relationName)->saveAll($entity, $info['relations']);
413
            }
414
415 27
            return $nb;
416 27
        });
417
    }
418
419
    /**
420
     * {@inheritdoc}
421
     */
422
    #[WriteOperation]
423 24
    public function deleteAll($entity, $relations): int
424
    {
425 24
        $relations = Relation::sanitizeRelations((array)$relations);
426
427 24
        return $this->transaction(function () use ($entity, $relations) {
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->transactio...ion(...) { /* ... */ }) returns the type Bdf\Prime\Repository\R which is incompatible with the type-hinted return integer.
Loading history...
428 24
            $nb = $this->delete($entity);
429
430 24
            foreach ($relations as $relationName => $info) {
431 10
                $nb += $this->relation($relationName)->deleteAll($entity, $info['relations']);
432
            }
433
434 24
            return $nb;
435 24
        });
436
    }
437
438
    /**
439
     * {@inheritdoc}
440
     */
441 796
    public function constraints(string $context = null): array
442
    {
443 796
        if ($this->withoutConstraints === true) {
444 4
            $this->withoutConstraints = false;
445 4
            return [];
446
        }
447
448 795
        $constraints = $this->metadata()->constraints;
449
450 795
        if ($context && is_array($constraints)) {
451 581
            $context .= '.';
452 581
            foreach ($constraints as $key => $value) {
453 45
                $constraints[$context.$key] = $value;
454 45
                unset($constraints[$key]);
455
            }
456
        }
457
458 795
        return $constraints;
459
    }
460
461
    /**
462
     * Disable global constraints on this repository.
463
     * Only for the current query
464
     *
465
     * @return $this
466
     */
467 4
    public function withoutConstraints()
468
    {
469 4
        $this->withoutConstraints = true;
470
471 4
        return $this;
472
    }
473
474
    /**
475
     * Check whether the current query has global constraints
476
     *
477
     * @return bool
478
     */
479 4
    public function isWithoutConstraints(): bool
480
    {
481 4
        return $this->withoutConstraints;
482
    }
483
484
    /**
485
     * Get query builder
486
     *
487
     * @return QueryInterface<ConnectionInterface, E>
488
     */
489 397
    public function builder()
490
    {
491 397
        return $this->queries->builder();
492
    }
493
494
    /**
495
     * {@inheritdoc}
496
     */
497 448
    public function queries(): RepositoryQueryFactory
498
    {
499 448
        return $this->queries;
500
    }
501
502
    /**
503
     * {@inheritdoc}
504
     */
505 4
    public function writer(): WriterInterface
506
    {
507 4
        return $this->writer;
508
    }
509
510
    /**
511
     * Count entity
512
     *
513
     * @param array $criteria
514
     * @param string|array|null $attributes
515
     *
516
     * @return int
517
     * @throws PrimeException
518
     */
519
    #[ReadOperation]
520 44
    public function count(array $criteria = [], $attributes = null): int
521
    {
522
        /** @psalm-suppress UndefinedInterfaceMethod */
523 44
        return $this->builder()->where($criteria)->count($attributes);
0 ignored issues
show
Bug introduced by
The method count() does not exist on Bdf\Prime\Query\QueryInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Bdf\Prime\Query\QueryInterface. ( Ignorable by Annotation )

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

523
        return $this->builder()->where($criteria)->/** @scrutinizer ignore-call */ count($attributes);
Loading history...
524
    }
525
526
    /**
527
     * {@inheritdoc}
528
     */
529
    #[ReadOperation]
530 71
    public function exists($entity): bool
531
    {
532 71
        return $this->queries->countKeyValue($this->mapper()->primaryCriteria($entity)) > 0;
533
    }
534
535
    /**
536
     * {@inheritdoc}
537
     */
538
    #[ReadOperation]
539 44
    public function refresh($entity, array $criteria = [])
540
    {
541 44
        if (empty($criteria)) {
542 44
            return $this->queries->findById($this->mapper()->primaryCriteria($entity));
543
        }
544
545 1
        $criteria += $this->mapper()->primaryCriteria($entity);
546
547 1
        return $this->builder()->where($criteria)->first();
548
    }
549
550
    /**
551
     * Check if the entity is new
552
     *
553
     * @param E $entity
554
     *
555
     * @return bool|null Returns null if entity is composite primary
556
     */
557 105
    public function isNew($entity): ?bool
558
    {
559 105
        $metadata = $this->mapper->metadata();
560
561 105
        if ($metadata->isCompositePrimaryKey()) {
562 14
            return null;
563
        }
564
565 95
        $primaryValue = $this->mapper->getId($entity);
566
567 95
        if (empty($primaryValue)) {
568 34
            return true;
569
        }
570
571 67
        return $metadata->isForeignPrimaryKey() ? null : false;
572
    }
573
574
    /**
575
     * {@inheritdoc}
576
     */
577
    #[WriteOperation]
578 57
    public function save($entity): int
579
    {
580 57
        $isNew = $this->isNew($entity);
581
582 57
        if ($this->notify(Events::PRE_SAVE, [$entity, $this, $isNew]) === false) {
583
            return 0;
584
        }
585
586
        // composite primary
587 57
        if ($isNew === null) {
588 30
            $count = $this->replace($entity);
589 39
        } elseif ($isNew) {
590 24
            $count = $this->insert($entity);
591
        } else {
592 18
            $count = $this->update($entity);
593
        }
594
595 55
        $this->notify(Events::POST_SAVE, [$entity, $this, $count, $isNew]);
596
597 55
        return $count;
598
    }
599
600
    /**
601
     * Replace an entity
602
     *
603
     * @param E $entity
604
     *
605
     * @return int  Returns 2 if updated and 1 if inserting
606
     * @throws PrimeException
607
     */
608
    #[WriteOperation]
609 78
    public function replace($entity): int
610
    {
611 78
        $isNew = $this->isNew($entity);
612
613 78
        if ($isNew !== true && $this->exists($entity)) {
614 18
            return $this->update($entity) + 1;
615
        }
616
617 64
        return $this->insert($entity);
618
    }
619
620
    /**
621
     * Duplicate an entity
622
     * remove primary key and launch insertion
623
     *
624
     * @param E $entity
625
     *
626
     * @return int
627
     * @throws PrimeException
628
     */
629
    #[WriteOperation]
630 2
    public function duplicate($entity): int
631
    {
632 2
        $this->mapper()->setId($entity, null);
633
634 2
        return $this->insert($entity);
635
    }
636
637
    /**
638
     * Insert an entity
639
     *
640
     * @param E $entity
641
     * @param bool $ignore
642
     *
643
     * @return int
644
     * @throws PrimeException
645
     */
646
    #[WriteOperation]
647 633
    public function insert($entity, bool $ignore = false): int
648
    {
649 633
        return $this->writer->insert($entity, ['ignore' => $ignore]);
650
    }
651
652
    /**
653
     * Insert ignore
654
     *
655
     * @param E $entity
656
     *
657
     * @return int
658
     * @throws PrimeException
659
     */
660
    #[WriteOperation]
661 2
    public function insertIgnore($entity): int
662
    {
663 2
        return $this->insert($entity, true);
664
    }
665
666
    /**
667
     * {@inheritdoc}
668
     */
669
    #[WriteOperation]
670 62
    public function update($entity, array $attributes = null): int
671
    {
672 62
        return $this->writer->update($entity, ['attributes' => $attributes]);
673
    }
674
675
    /**
676
     * Update collection of entities
677
     *
678
     * @param array $attributes
679
     * @param array $criteria
680
     *
681
     * @return int
682
     * @throws PrimeException
683
     */
684
    #[WriteOperation]
685
    public function updateBy(array $attributes, array $criteria = []): int
686
    {
687
        return $this->builder()->where($criteria)->update($attributes);
688
    }
689
690
    /**
691
     * {@inheritdoc}
692
     */
693
    #[WriteOperation]
694 46
    public function delete($entity): int
695
    {
696 46
        return $this->writer->delete($entity);
697
    }
698
699
    /**
700
     * Remove a collection of entities
701
     *
702
     * @param array $criteria
703
     *
704
     * @return int
705
     * @throws PrimeException
706
     */
707
    #[WriteOperation]
708
    public function deleteBy(array $criteria): int
709
    {
710
        return $this->builder()->where($criteria)->delete();
711
    }
712
713
    /**
714
     * {@inheritdoc}
715
     */
716 871
    public function schema(bool $force = false): StructureUpgraderInterface
717
    {
718 871
        if (!$this->mapper->hasSchemaManager() && !$force) {
719
            return new NullStructureUpgrader();
720
        }
721
722 871
        return new RepositoryUpgrader($this->serviceLocator, $this->mapper->metadata());
723
    }
724
725
    /**
726
     * Gets custom filters
727
     *
728
     * @return array
729
     */
730
    public function filters()
731
    {
732
        return $this->mapper->filters();
733
    }
734
735
    /**
736
     * Repository extensions
737
     *
738
     * @return array<string, callable(\Bdf\Prime\Query\QueryInterface,mixed...):mixed>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string, callable(\...erface,mixed...):mixed> at position 4 could not be parsed: Expected '>' at position 4, but found 'callable'.
Loading history...
739
     */
740 1
    public function scopes()
741
    {
742 1
        return $this->mapper->scopes();
743
    }
744
745
    //----- events
746
747
    /**
748
     * {@inheritdoc}
749
     */
750 1
    public function loaded(callable $listener, bool $once = false)
751
    {
752 1
        if ($once) {
753 1
            $this->once(Events::POST_LOAD, $listener);
754
        } else {
755
            $this->listen(Events::POST_LOAD, $listener);
756
        }
757
758 1
        return $this;
759
    }
760
761
    /**
762
     * {@inheritdoc}
763
     */
764 1
    public function saving(callable $listener, bool $once = false)
765
    {
766 1
        if ($once) {
767 1
            $this->once(Events::PRE_SAVE, $listener);
768
        } else {
769
            $this->listen(Events::PRE_SAVE, $listener);
770
        }
771
772 1
        return $this;
773
    }
774
775
    /**
776
     * {@inheritdoc}
777
     */
778 1
    public function saved(callable $listener, bool $once = false)
779
    {
780 1
        if ($once) {
781 1
            $this->once(Events::POST_SAVE, $listener);
782
        } else {
783
            $this->listen(Events::POST_SAVE, $listener);
784
        }
785
786 1
        return $this;
787
    }
788
789
    /**
790
     * {@inheritdoc}
791
     */
792 5
    public function inserting(callable $listener, bool $once = false)
793
    {
794 5
        if ($once) {
795 1
            $this->once(Events::PRE_INSERT, $listener);
796
        } else {
797 4
            $this->listen(Events::PRE_INSERT, $listener);
798
        }
799
800 5
        return $this;
801
    }
802
803
    /**
804
     * {@inheritdoc}
805
     */
806 2
    public function inserted(callable $listener, bool $once = false)
807
    {
808 2
        if ($once) {
809 1
            $this->once(Events::POST_INSERT, $listener);
810
        } else {
811 1
            $this->listen(Events::POST_INSERT, $listener);
812
        }
813
814 2
        return $this;
815
    }
816
817
    /**
818
     * {@inheritdoc}
819
     */
820 6
    public function updating(callable $listener, bool $once = false)
821
    {
822 6
        if ($once) {
823 2
            $this->once(Events::PRE_UPDATE, $listener);
824
        } else {
825 4
            $this->listen(Events::PRE_UPDATE, $listener);
826
        }
827
828 6
        return $this;
829
    }
830
831
    /**
832
     * {@inheritdoc}
833
     */
834 2
    public function updated(callable $listener, bool $once = false)
835
    {
836 2
        if ($once) {
837 1
            $this->once(Events::POST_UPDATE, $listener);
838
        } else {
839 1
            $this->listen(Events::POST_UPDATE, $listener);
840
        }
841
842 2
        return $this;
843
    }
844
845
    /**
846
     * {@inheritdoc}
847
     */
848 6
    public function deleting(callable $listener, bool $once = false)
849
    {
850 6
        if ($once) {
851 1
            $this->once(Events::PRE_DELETE, $listener);
852
        } else {
853 5
            $this->listen(Events::PRE_DELETE, $listener);
854
        }
855
856 6
        return $this;
857
    }
858
859
    /**
860
     * {@inheritdoc}
861
     */
862 2
    public function deleted(callable $listener, bool $once = false)
863
    {
864 2
        if ($once) {
865 1
            $this->once(Events::POST_DELETE, $listener);
866
        } else {
867 1
            $this->listen(Events::POST_DELETE, $listener);
868
        }
869
870 2
        return $this;
871
    }
872
873
    /**
874
     * Query method
875
     * redirect method to the query builder
876
     *
877
     * @param string $name         Query builder method
878
     * @param array  $arguments
879
     *
880
     * @return int|QueryInterface|array|E
881
     */
882 147
    public function __call($name, $arguments)
883
    {
884 147
        return $this->queries->$name(...$arguments);
885
    }
886
887
    //--- Methodes for optimisation: alias of query methods
888
889
    /**
890
     * @see QueryRepositoryExtension::with
891
     *
892
     * @param string|array $relations
893
     *
894
     * @return QueryInterface<ConnectionInterface, E>
895
     */
896 88
    public function with($relations)
897
    {
898 88
        return $this->builder()->with($relations);
0 ignored issues
show
Bug introduced by
The method with() does not exist on Bdf\Prime\Query\QueryInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Bdf\Prime\Query\SqlQueryInterface. Are you sure you never get one of those? ( Ignorable by Annotation )

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

898
        return $this->builder()->/** @scrutinizer ignore-call */ with($relations);
Loading history...
899
    }
900
901
    /**
902
     * @see QueryRepositoryExtension::without
903
     *
904
     * @param string|array $relations
905
     *
906
     * @return QueryInterface<ConnectionInterface, E>
907
     */
908 25
    public function without($relations)
909
    {
910 25
        return $this->builder()->without($relations);
0 ignored issues
show
Bug introduced by
The method without() does not exist on Bdf\Prime\Query\QueryInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Bdf\Prime\Query\SqlQueryInterface. Are you sure you never get one of those? ( Ignorable by Annotation )

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

910
        return $this->builder()->/** @scrutinizer ignore-call */ without($relations);
Loading history...
911
    }
912
913
    /**
914
     * @see QueryRepositoryExtension::by
915
     *
916
     * @param string|array $attribute
917
     * @param boolean      $combine
918
     *
919
     * @return QueryInterface<ConnectionInterface, E>
920
     */
921 6
    public function by($attribute, $combine = false)
922
    {
923 6
        return $this->builder()->by($attribute, $combine);
0 ignored issues
show
Bug introduced by
The method by() does not exist on Bdf\Prime\Query\QueryInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Bdf\Prime\Query\SqlQueryInterface. Are you sure you never get one of those? ( Ignorable by Annotation )

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

923
        return $this->builder()->/** @scrutinizer ignore-call */ by($attribute, $combine);
Loading history...
924
    }
925
926
    /**
927
     * @see QueryInterface::wrapAs
928
     *
929
     * @param string $wrapperClass
930
     *
931
     * @return QueryInterface<ConnectionInterface, E>
932
     */
933 7
    public function wrapAs($wrapperClass)
934
    {
935 7
        return $this->builder()->wrapAs($wrapperClass);
936
    }
937
938
    /**
939
     * @param array $criteria
940
     * @param array $attributes
941
     *
942
     * @return E[]|CollectionInterface<E>
943
     * @throws PrimeException
944
     */
945
    #[ReadOperation]
946 5
    public function find(array $criteria, $attributes = null)
947
    {
948 5
        return $this->builder()->find($criteria, $attributes);
949
    }
950
951
    /**
952
     * @param array $criteria
953
     * @param array $attributes
954
     *
955
     * @return E|null
956
     * @throws PrimeException
957
     */
958
    #[ReadOperation]
959 11
    public function findOne(array $criteria, $attributes = null)
960
    {
961 11
        return $this->builder()->findOne($criteria, $attributes);
962
    }
963
964
    /**
965
     * @see QueryInterface::where
966
     *
967
     * @param string|array<string,mixed>|callable(static):void $column The restriction predicates.
968
     * @param string|mixed|null $operator The comparison operator, or the value is you want to use "=" operator
969
     * @param mixed $value
970
     *
971
     * @return QueryInterface<ConnectionInterface, E>
972
     */
973 92
    public function where($column, $operator = null, $value = null)
974
    {
975 92
        return $this->builder()->where($column, $operator, $value);
976
    }
977
978
    /**
979
     * {@inheritdoc}
980
     */
981 3
    public function onConnectionClosed()
982
    {
983 3
        $this->reset();
984
    }
985
986
    /**
987
     * {@inheritdoc}
988
     */
989 355
    public function getSubscribedEvents()
990
    {
991 355
        return [ConnectionClosedListenerInterface::EVENT_NAME];
992
    }
993
994
    /**
995
     * Free metadata information on the given entity
996
     * Note: This method is called by the model destructor
997
     *
998
     * @param E $entity
999
     *
1000
     * @return void
1001
     * @deprecated This is a no-op method. Will be removed in 3.0.
1002
     */
1003 1
    public function free($entity)
1004
    {
1005
        // No-op
1006 1
    }
1007
1008
    /**
1009
     * Clear dependencies for break cyclic references
1010
     * After this call, the repository will be unusable
1011
     *
1012
     * @internal
1013
     *
1014
     * @return void
1015
     */
1016 1
    public function destroy(): void
1017
    {
1018 1
        if ($this->connection !== null) {
1019 1
            $this->connection->getEventManager()->removeEventSubscriber($this);
1020 1
            $this->connection = null;
1021
        }
1022
1023 1
        $this->serviceLocator = null;
1024 1
        $this->queries = null;
1025 1
        $this->writer = null;
1026 1
        $this->relations = [];
1027 1
        $this->collectionFactory = null;
1028
1029 1
        $this->mapper->destroy();
1030 1
        $this->mapper = null;
1031
1032 1
        if ($this->resultCache) {
1033
            $this->resultCache->clear();
1034
            $this->resultCache = null;
1035
        }
1036
    }
1037
1038
    /**
1039
     * Change the active connection on the repository
1040
     * All queries will be reseted
1041
     *
1042
     * /!\ The method will not check if the connection exists nor the new connection is same as the active
1043
     *
1044
     * @param string $connectionName The new connection name
1045
     *
1046
     * @return string The last active connection name
1047
     */
1048 6
    private function changeActiveConnection($connectionName)
1049
    {
1050 6
        $this->reset();
1051
1052
        /** @var string $original */
1053 6
        $original = $this->mapper->metadata()->connection;
1054 6
        $this->mapper->metadata()->connection = $connectionName;
1055
1056 6
        return $original;
1057
    }
1058
1059
    /**
1060
     * Reset the inner queries and the connection.
1061
     * Use for invalidate prepared queries, or when connection changed
1062
     *
1063
     * @return void
1064
     */
1065 8
    private function reset(): void
1066
    {
1067 8
        if ($this->connection !== null) {
1068 7
            $this->connection->getEventManager()->removeEventSubscriber($this);
1069 7
            $this->connection = null;
1070
        }
1071
1072
        // Reset queries
1073 8
        $this->queries = new RepositoryQueryFactory($this, $this->resultCache);
1074 8
        $this->writer = new Writer($this, $this->serviceLocator);
1075 8
        $this->relations = []; // Relation may contains inner query : it must be reseted
1076
1077 8
        if ($this->resultCache) {
1078
            $this->resultCache->clear();
1079
        }
1080
    }
1081
}
1082