PersistService   A
last analyzed

Complexity

Total Complexity 36

Size/Duplication

Total Lines 429
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 36
eloc 150
c 1
b 0
f 0
dl 0
loc 429
rs 9.52

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
A refresh() 0 26 3
A deleteCollection() 0 11 2
A save() 0 6 2
A persist() 0 27 3
A saveCollection() 0 12 2
A beginTransaction() 0 9 2
A rollbackTransaction() 0 9 2
A update() 0 12 2
A flush() 0 14 2
A insert() 0 12 2
A clear() 0 14 2
A delete() 0 8 2
A getEntityName() 0 3 1
A commitTransaction() 0 9 2
A createCollectionEvent() 0 16 1
A createEvent() 0 15 2
A createErrorEvent() 0 9 1
A dispatchEvent() 0 16 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Arp\DoctrineEntityRepository\Persistence;
6
7
use Arp\DoctrineEntityRepository\Constant\EntityEventName;
8
use Arp\DoctrineEntityRepository\Persistence\Event\AbstractEntityEvent;
9
use Arp\DoctrineEntityRepository\Persistence\Event\CollectionEvent;
10
use Arp\DoctrineEntityRepository\Persistence\Event\EntityErrorEvent;
11
use Arp\DoctrineEntityRepository\Persistence\Event\EntityEvent;
12
use Arp\DoctrineEntityRepository\Persistence\Exception\PersistenceException;
13
use Arp\Entity\EntityInterface;
14
use Doctrine\ORM\EntityManagerInterface;
15
use Psr\EventDispatcher\EventDispatcherInterface;
16
use Psr\Log\LoggerInterface;
17
18
/**
19
 * @author  Alex Patterson <[email protected]>
20
 * @package Arp\DoctrineEntityRepository\Persistence
21
 */
22
class PersistService implements PersistServiceInterface
23
{
24
    /**
25
     * @var string
26
     */
27
    protected string $entityName;
28
29
    /**
30
     * @var EntityManagerInterface
31
     */
32
    protected EntityManagerInterface $entityManager;
33
34
    /**
35
     * @var EventDispatcherInterface
36
     */
37
    protected EventDispatcherInterface $eventDispatcher;
38
39
    /**
40
     * @var LoggerInterface
41
     */
42
    protected LoggerInterface $logger;
43
44
    /**
45
     * @param string                   $entityName
46
     * @param EntityManagerInterface  $entityManager
47
     * @param EventDispatcherInterface $eventDispatcher
48
     * @param LoggerInterface          $logger
49
     */
50
    public function __construct(
51
        string $entityName,
52
        EntityManagerInterface $entityManager,
53
        EventDispatcherInterface $eventDispatcher,
54
        LoggerInterface $logger
55
    ) {
56
        $this->entityName = $entityName;
57
        $this->entityManager = $entityManager;
58
        $this->eventDispatcher = $eventDispatcher;
59
        $this->logger = $logger;
60
    }
61
62
    /**
63
     * Return the full qualified class name of the entity.
64
     *
65
     * @return string
66
     */
67
    public function getEntityName(): string
68
    {
69
        return $this->entityName;
70
    }
71
72
    /**
73
     * @param EntityInterface      $entity
74
     * @param array<string|int, mixed> $options
75
     *
76
     * @return EntityInterface
77
     *
78
     * @throws PersistenceException
79
     */
80
    public function save(EntityInterface $entity, array $options = []): EntityInterface
81
    {
82
        if ($entity->hasId()) {
83
            return $this->update($entity, $options);
84
        }
85
        return $this->insert($entity, $options);
86
    }
87
88
    /**
89
     * @param iterable<EntityInterface> $collection The collection of entities that should be saved
90
     * @param array<string|int, mixed>  $options    the optional save options
91
     *
92
     * @return iterable<EntityInterface>
93
     *
94
     * @throws PersistenceException
95
     */
96
    public function saveCollection(iterable $collection, array $options = []): iterable
97
    {
98
        $event = $this->createCollectionEvent(EntityEventName::SAVE_COLLECTION, $collection, $options);
99
100
        try {
101
            /** @var CollectionEvent $event */
102
            $event = $this->dispatchEvent($event);
103
        } catch (\Exception $e) {
104
            $this->dispatchEvent($this->createErrorEvent(EntityEventName::SAVE_COLLECTION_ERROR, $e));
105
        }
106
107
        return $event->getCollection();
108
    }
109
110
    /**
111
     * @param EntityInterface          $entity
112
     * @param array<string|int, mixed> $options
113
     *
114
     * @return EntityInterface
115
     *
116
     * @throws PersistenceException
117
     */
118
    protected function update(EntityInterface $entity, array $options = []): EntityInterface
119
    {
120
        $event = $this->createEvent(EntityEventName::UPDATE, $entity, $options);
121
122
        try {
123
            /** @var EntityEvent $event */
124
            $event = $this->dispatchEvent($event);
125
        } catch (\Exception $e) {
126
            $this->dispatchEvent($this->createErrorEvent(EntityEventName::UPDATE_ERROR, $e));
127
        }
128
129
        return $event->getEntity() ?? $entity;
130
    }
131
132
    /**
133
     * @param EntityInterface      $entity
134
     * @param array<string|int, mixed> $options
135
     *
136
     * @return EntityInterface
137
     *
138
     * @throws PersistenceException
139
     */
140
    protected function insert(EntityInterface $entity, array $options = []): EntityInterface
141
    {
142
        $event = $this->createEvent(EntityEventName::CREATE, $entity, $options);
143
144
        try {
145
            /** @var EntityEvent $event */
146
            $event = $this->dispatchEvent($event);
147
        } catch (\Exception $e) {
148
            $this->dispatchEvent($this->createErrorEvent(EntityEventName::CREATE_ERROR, $e));
149
        }
150
151
        return $event->getEntity() ?? $entity;
152
    }
153
154
    /**
155
     * @param EntityInterface      $entity
156
     * @param array<string|int, mixed> $options
157
     *
158
     * @return bool
159
     *
160
     * @throws PersistenceException
161
     */
162
    public function delete(EntityInterface $entity, array $options = []): bool
163
    {
164
        try {
165
            $this->dispatchEvent($this->createEvent(EntityEventName::DELETE, $entity, $options));
166
            return true;
167
        } catch (\Exception $e) {
168
            $this->dispatchEvent($this->createErrorEvent(EntityEventName::DELETE_ERROR, $e));
169
            return false;
170
        }
171
    }
172
173
    /**
174
     * @param iterable<EntityInterface> $collection
175
     * @param array<string|int, mixed>  $options
176
     *
177
     * @return int
178
     *
179
     * @throws PersistenceException
180
     */
181
    public function deleteCollection(iterable $collection, array $options = []): int
182
    {
183
        $event = $this->createCollectionEvent(EntityEventName::DELETE_COLLECTION, $collection, $options);
184
185
        try {
186
            $event = $this->dispatchEvent($event);
187
        } catch (\Exception $e) {
188
            $this->dispatchEvent($this->createErrorEvent(EntityEventName::DELETE_COLLECTION_ERROR, $e));
189
        }
190
191
        return (int)$event->getParam('deleted_count', 0);
192
    }
193
194
    /**
195
     * Schedule the entity for insertion.
196
     *
197
     * @param EntityInterface $entity
198
     *
199
     * @throws PersistenceException
200
     */
201
    public function persist(EntityInterface $entity): void
202
    {
203
        if (!$entity instanceof $this->entityName) {
204
            $errorMessage = sprintf(
205
                'The \'entity\' argument must be an object of type \'%s\'; \'%s\' provided in \'%s\'',
206
                $this->entityName,
207
                get_class($entity),
208
                __METHOD__
209
            );
210
211
            $this->logger->error($errorMessage);
212
213
            throw new PersistenceException($errorMessage);
214
        }
215
216
        try {
217
            $this->entityManager->persist($entity);
218
        } catch (\Exception $e) {
219
            $errorMessage = sprintf(
220
                'The persist operation failed for entity \'%s\': %s',
221
                $this->entityName,
222
                $e->getMessage()
223
            );
224
225
            $this->logger->error($errorMessage, ['exception' => $e]);
226
227
            throw new PersistenceException($errorMessage, $e->getCode(), $e);
228
        }
229
    }
230
231
    /**
232
     * Perform a flush of the unit of work.
233
     *
234
     * @throws PersistenceException
235
     */
236
    public function flush(): void
237
    {
238
        try {
239
            $this->entityManager->flush();
240
        } catch (\Exception $e) {
241
            $errorMessage = sprintf(
242
                'The flush operation failed for entity \'%s\': %s',
243
                $this->entityName,
244
                $e->getMessage()
245
            );
246
247
            $this->logger->error($errorMessage, ['exception' => $e, 'entity_name' => $this->entityName]);
248
249
            throw new PersistenceException($errorMessage, $e->getCode(), $e);
250
        }
251
    }
252
253
    /**
254
     * Release managed entities from the identity map.
255
     *
256
     * @return void
257
     *
258
     * @throws PersistenceException
259
     */
260
    public function clear(): void
261
    {
262
        try {
263
            $this->entityManager->clear();
264
        } catch (\Exception $e) {
265
            $errorMessage = sprintf(
266
                'The clear operation failed for entity \'%s\': %s',
267
                $this->entityName,
268
                $e->getMessage()
269
            );
270
271
            $this->logger->error($errorMessage, ['exception' => $e, 'entity_name' => $this->entityName]);
272
273
            throw new PersistenceException($errorMessage, $e->getCode(), $e);
274
        }
275
    }
276
277
    /**
278
     * @param EntityInterface $entity
279
     *
280
     * @throws PersistenceException
281
     */
282
    public function refresh(EntityInterface $entity): void
283
    {
284
        $entityName = $this->entityName;
285
286
        if (!$entity instanceof $entityName) {
287
            throw new PersistenceException(
288
                sprintf(
289
                    'The \'entity\' argument must be an object of type \'%s\'; \'%s\' provided in \'%s\'',
290
                    $entityName,
291
                    get_class($entity),
292
                    __METHOD__
293
                )
294
            );
295
        }
296
297
        try {
298
            $this->entityManager->refresh($entity);
299
        } catch (\Exception $e) {
300
            throw new PersistenceException(
301
                sprintf(
302
                    'The refresh operation failed for entity \'%s\' : %s',
303
                    $entityName,
304
                    $e->getMessage()
305
                ),
306
                $e->getCode(),
307
                $e
308
            );
309
        }
310
    }
311
312
    /**
313
     * @throws PersistenceException
314
     */
315
    public function beginTransaction(): void
316
    {
317
        try {
318
            $this->entityManager->beginTransaction();
319
        } catch (\Exception $e) {
320
            throw new PersistenceException(
321
                sprintf('Failed to start transaction : %s', $e->getMessage()),
322
                $e->getCode(),
323
                $e
324
            );
325
        }
326
    }
327
328
    /**
329
     * @throws PersistenceException
330
     */
331
    public function commitTransaction(): void
332
    {
333
        try {
334
            $this->entityManager->commit();
335
        } catch (\Exception $e) {
336
            throw new PersistenceException(
337
                sprintf('Failed to commit transaction : %s', $e->getMessage()),
338
                $e->getCode(),
339
                $e
340
            );
341
        }
342
    }
343
344
    /**
345
     * @throws PersistenceException
346
     */
347
    public function rollbackTransaction(): void
348
    {
349
        try {
350
            $this->entityManager->rollback();
351
        } catch (\Exception $e) {
352
            throw new PersistenceException(
353
                sprintf('Failed to rollback transaction : %s', $e->getMessage()),
354
                $e->getCode(),
355
                $e
356
            );
357
        }
358
    }
359
360
    /**
361
     * Perform the event dispatch.
362
     *
363
     * @param AbstractEntityEvent $event The event that should be dispatched.
364
     *
365
     * @return AbstractEntityEvent
366
     * @throws PersistenceException
367
     */
368
    protected function dispatchEvent(AbstractEntityEvent $event): AbstractEntityEvent
369
    {
370
        $result = $this->eventDispatcher->dispatch($event);
371
372
        if (!$result instanceof AbstractEntityEvent) {
373
            throw new PersistenceException(
374
                sprintf(
375
                    'The return \'event\' must be an object of type \'%s\'; \'%s\' returned for entity \'%s\'',
376
                    AbstractEntityEvent::class,
377
                    get_class($result),
378
                    $this->getEntityName()
379
                )
380
            );
381
        }
382
383
        return $result;
384
    }
385
386
    /**
387
     * @param string                   $eventName
388
     * @param EntityInterface|null     $entity
389
     * @param array<string|int, mixed> $params
390
     *
391
     * @return EntityEvent
392
     */
393
    protected function createEvent(string $eventName, EntityInterface $entity = null, array $params = []): EntityEvent
394
    {
395
        $event = new EntityEvent(
396
            $eventName,
397
            $this,
398
            $this->entityManager,
399
            $this->logger,
400
            $params
401
        );
402
403
        if (null !== $entity) {
404
            $event->setEntity($entity);
405
        }
406
407
        return $event;
408
    }
409
410
    /**
411
     * @param string                    $eventName
412
     * @param iterable<EntityInterface> $collection
413
     * @param array<string|int, mixed>  $params
414
     *
415
     * @return CollectionEvent
416
     */
417
    protected function createCollectionEvent(
418
        string $eventName,
419
        iterable $collection,
420
        array $params = []
421
    ): CollectionEvent {
422
        $event = new CollectionEvent(
423
            $eventName,
424
            $this,
425
            $this->entityManager,
426
            $this->logger,
427
            $params
428
        );
429
430
        $event->setCollection($collection);
431
432
        return $event;
433
    }
434
435
    /**
436
     * @param string               $eventName
437
     * @param \Throwable           $exception
438
     * @param array<string|int, mixed> $params
439
     *
440
     * @return EntityErrorEvent
441
     */
442
    protected function createErrorEvent(string $eventName, \Throwable $exception, array $params = []): EntityErrorEvent
443
    {
444
        return new EntityErrorEvent(
445
            $eventName,
446
            $this,
447
            $this->entityManager,
448
            $this->logger,
449
            $exception,
450
            $params
451
        );
452
    }
453
}
454