Passed
Pull Request — master (#8)
by Alex
03:52
created

PersistService   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 429
Duplicated Lines 0 %

Test Coverage

Coverage 87.07%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 36
eloc 150
c 1
b 0
f 0
dl 0
loc 429
ccs 128
cts 147
cp 0.8707
rs 9.52

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
A refresh() 0 26 3
A createCollectionEvent() 0 16 1
A save() 0 6 2
A persist() 0 27 3
A saveCollection() 0 12 2
A createEvent() 0 15 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 createErrorEvent() 0 9 1
A dispatchEvent() 0 16 2
A delete() 0 8 2
A getEntityName() 0 3 1
A commitTransaction() 0 9 2
A deleteCollection() 0 11 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 26
    public function __construct(
51
        string $entityName,
52
        EntityManagerInterface $entityManager,
53
        EventDispatcherInterface $eventDispatcher,
54
        LoggerInterface $logger
55
    ) {
56 26
        $this->entityName = $entityName;
57 26
        $this->entityManager = $entityManager;
58 26
        $this->eventDispatcher = $eventDispatcher;
59 26
        $this->logger = $logger;
60 26
    }
61
62
    /**
63
     * Return the full qualified class name of the entity.
64
     *
65
     * @return string
66
     */
67 1
    public function getEntityName(): string
68
    {
69 1
        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 4
    public function save(EntityInterface $entity, array $options = []): EntityInterface
81
    {
82 4
        if ($entity->hasId()) {
83 2
            return $this->update($entity, $options);
84
        }
85 2
        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 2
    protected function update(EntityInterface $entity, array $options = []): EntityInterface
119
    {
120 2
        $event = $this->createEvent(EntityEventName::UPDATE, $entity, $options);
121
122
        try {
123
            /** @var EntityEvent $event */
124 2
            $event = $this->dispatchEvent($event);
125 1
        } catch (\Exception $e) {
126 1
            $this->dispatchEvent($this->createErrorEvent(EntityEventName::UPDATE_ERROR, $e));
127
        }
128
129 2
        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 2
    protected function insert(EntityInterface $entity, array $options = []): EntityInterface
141
    {
142 2
        $event = $this->createEvent(EntityEventName::CREATE, $entity, $options);
143
144
        try {
145
            /** @var EntityEvent $event */
146 2
            $event = $this->dispatchEvent($event);
147 1
        } catch (\Exception $e) {
148 1
            $this->dispatchEvent($this->createErrorEvent(EntityEventName::CREATE_ERROR, $e));
149
        }
150
151 2
        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 2
    public function delete(EntityInterface $entity, array $options = []): bool
163
    {
164
        try {
165 2
            $this->dispatchEvent($this->createEvent(EntityEventName::DELETE, $entity, $options));
166 1
            return true;
167 1
        } catch (\Exception $e) {
168 1
            $this->dispatchEvent($this->createErrorEvent(EntityEventName::DELETE_ERROR, $e));
169 1
            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 2
    public function deleteCollection(iterable $collection, array $options = []): int
182
    {
183 2
        $event = $this->createCollectionEvent(EntityEventName::DELETE_COLLECTION, $collection, $options);
184
185
        try {
186 2
            $event = $this->dispatchEvent($event);
187 1
        } catch (\Exception $e) {
188 1
            $this->dispatchEvent($this->createErrorEvent(EntityEventName::DELETE_COLLECTION_ERROR, $e));
189
        }
190
191 2
        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 3
    public function persist(EntityInterface $entity): void
202
    {
203 3
        if (!$entity instanceof $this->entityName) {
204 1
            $errorMessage = sprintf(
205 1
                'The \'entity\' argument must be an object of type \'%s\'; \'%s\' provided in \'%s\'',
206 1
                $this->entityName,
207 1
                get_class($entity),
208 1
                __METHOD__
209
            );
210
211 1
            $this->logger->error($errorMessage);
212
213 1
            throw new PersistenceException($errorMessage);
214
        }
215
216
        try {
217 2
            $this->entityManager->persist($entity);
218 1
        } catch (\Exception $e) {
219 1
            $errorMessage = sprintf(
220 1
                'The persist operation failed for entity \'%s\': %s',
221 1
                $this->entityName,
222 1
                $e->getMessage()
223
            );
224
225 1
            $this->logger->error($errorMessage, ['exception' => $e]);
226
227 1
            throw new PersistenceException($errorMessage, $e->getCode(), $e);
228
        }
229 1
    }
230
231
    /**
232
     * Perform a flush of the unit of work.
233
     *
234
     * @throws PersistenceException
235
     */
236 2
    public function flush(): void
237
    {
238
        try {
239 2
            $this->entityManager->flush();
240 1
        } catch (\Exception $e) {
241 1
            $errorMessage = sprintf(
242 1
                'The flush operation failed for entity \'%s\': %s',
243 1
                $this->entityName,
244 1
                $e->getMessage()
245
            );
246
247 1
            $this->logger->error($errorMessage, ['exception' => $e, 'entity_name' => $this->entityName]);
248
249 1
            throw new PersistenceException($errorMessage, $e->getCode(), $e);
250
        }
251 1
    }
252
253
    /**
254
     * Release managed entities from the identity map.
255
     *
256
     * @return void
257
     *
258
     * @throws PersistenceException
259
     */
260 2
    public function clear(): void
261
    {
262
        try {
263 2
            $this->entityManager->clear();
264 1
        } catch (\Exception $e) {
265 1
            $errorMessage = sprintf(
266 1
                'The clear operation failed for entity \'%s\': %s',
267 1
                $this->entityName,
268 1
                $e->getMessage()
269
            );
270
271 1
            $this->logger->error($errorMessage, ['exception' => $e, 'entity_name' => $this->entityName]);
272
273 1
            throw new PersistenceException($errorMessage, $e->getCode(), $e);
274
        }
275 1
    }
276
277
    /**
278
     * @param EntityInterface $entity
279
     *
280
     * @throws PersistenceException
281
     */
282 3
    public function refresh(EntityInterface $entity): void
283
    {
284 3
        $entityName = $this->entityName;
285
286 3
        if (!$entity instanceof $entityName) {
287 1
            throw new PersistenceException(
288 1
                sprintf(
289 1
                    'The \'entity\' argument must be an object of type \'%s\'; \'%s\' provided in \'%s\'',
290
                    $entityName,
291 1
                    get_class($entity),
292 1
                    __METHOD__
293
                )
294
            );
295
        }
296
297
        try {
298 2
            $this->entityManager->refresh($entity);
299 1
        } catch (\Exception $e) {
300 1
            throw new PersistenceException(
301 1
                sprintf(
302 1
                    'The refresh operation failed for entity \'%s\' : %s',
303
                    $entityName,
304 1
                    $e->getMessage()
305
                ),
306 1
                $e->getCode(),
307
                $e
308
            );
309
        }
310 1
    }
311
312
    /**
313
     * @throws PersistenceException
314
     */
315 2
    public function beginTransaction(): void
316
    {
317
        try {
318 2
            $this->entityManager->beginTransaction();
319 1
        } catch (\Exception $e) {
320 1
            throw new PersistenceException(
321 1
                sprintf('Failed to start transaction : %s', $e->getMessage()),
322 1
                $e->getCode(),
323
                $e
324
            );
325
        }
326 1
    }
327
328
    /**
329
     * @throws PersistenceException
330
     */
331 2
    public function commitTransaction(): void
332
    {
333
        try {
334 2
            $this->entityManager->commit();
335 1
        } catch (\Exception $e) {
336 1
            throw new PersistenceException(
337 1
                sprintf('Failed to commit transaction : %s', $e->getMessage()),
338 1
                $e->getCode(),
339
                $e
340
            );
341
        }
342 1
    }
343
344
    /**
345
     * @throws PersistenceException
346
     */
347 2
    public function rollbackTransaction(): void
348
    {
349
        try {
350 2
            $this->entityManager->rollback();
351 1
        } catch (\Exception $e) {
352 1
            throw new PersistenceException(
353 1
                sprintf('Failed to rollback transaction : %s', $e->getMessage()),
354 1
                $e->getCode(),
355
                $e
356
            );
357
        }
358 1
    }
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 8
    protected function dispatchEvent(AbstractEntityEvent $event): AbstractEntityEvent
369
    {
370 8
        $result = $this->eventDispatcher->dispatch($event);
371
372 8
        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 8
        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 2
    protected function createEvent(string $eventName, EntityInterface $entity = null, array $params = []): EntityEvent
394
    {
395 2
        $event = new EntityEvent(
396 2
            $eventName,
397
            $this,
398 2
            $this->entityManager,
399 2
            $this->logger,
400
            $params
401
        );
402
403 2
        if (null !== $entity) {
404 2
            $event->setEntity($entity);
405
        }
406
407 2
        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 2
    protected function createErrorEvent(string $eventName, \Throwable $exception, array $params = []): EntityErrorEvent
443
    {
444 2
        return new EntityErrorEvent(
445 2
            $eventName,
446
            $this,
447 2
            $this->entityManager,
448 2
            $this->logger,
449
            $exception,
450
            $params
451
        );
452
    }
453
}
454