Passed
Pull Request — master (#4)
by Alex
08:39
created

PersistService::rollbackTransaction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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