Passed
Pull Request — master (#5)
by Alex
03:06
created

AbstractEntityRepository::deleteCollection()   B

Complexity

Conditions 8
Paths 46

Size

Total Lines 45
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 26
nc 46
nop 2
dl 0
loc 45
rs 8.4444
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Arp\DoctrineEntityRepository;
6
7
use Arp\DoctrineEntityRepository\Constant\EntityEventOption;
8
use Arp\DoctrineEntityRepository\Constant\FlushMode;
9
use Arp\DoctrineEntityRepository\Constant\QueryServiceOption;
10
use Arp\DoctrineEntityRepository\Constant\TransactionMode;
11
use Arp\DoctrineEntityRepository\Exception\EntityNotFoundException;
12
use Arp\DoctrineEntityRepository\Exception\EntityRepositoryException;
13
use Arp\DoctrineEntityRepository\Persistence\PersistServiceInterface;
14
use Arp\DoctrineEntityRepository\Query\QueryServiceInterface;
15
use Arp\Entity\EntityInterface;
16
use Psr\Log\LoggerInterface;
17
18
/**
19
 * @author  Alex Patterson <[email protected]>
20
 * @package Arp\DoctrineEntityRepository
21
 */
22
abstract class AbstractEntityRepository implements EntityRepositoryInterface
23
{
24
    /**
25
     * @var string
26
     */
27
    protected string $entityName;
28
29
    /**
30
     * @var QueryServiceInterface
31
     */
32
    protected QueryServiceInterface $queryService;
33
34
    /**
35
     * @var PersistServiceInterface
36
     */
37
    protected PersistServiceInterface $persistService;
38
39
    /**
40
     * @var LoggerInterface
41
     */
42
    protected LoggerInterface $logger;
43
44
    /**
45
     * @param string                  $entityName
46
     * @param QueryServiceInterface   $queryService
47
     * @param PersistServiceInterface $persistService
48
     * @param LoggerInterface         $logger
49
     */
50
    public function __construct(
51
        string $entityName,
52
        QueryServiceInterface $queryService,
53
        PersistServiceInterface $persistService,
54
        LoggerInterface $logger
55
    ) {
56
        $this->entityName = $entityName;
57
        $this->queryService = $queryService;
58
        $this->persistService = $persistService;
59
        $this->logger = $logger;
60
    }
61
62
    /**
63
     * Return the fully qualified class name of the mapped entity instance.
64
     *
65
     * @return string
66
     */
67
    public function getClassName(): string
68
    {
69
        return $this->entityName;
70
    }
71
72
    /**
73
     * Return a single entity instance matching the provided $id.
74
     *
75
     * @param string $id
76
     *
77
     * @return EntityInterface|null
78
     *
79
     * @throws EntityRepositoryException
80
     */
81
    public function find($id): ?EntityInterface
82
    {
83
        try {
84
            return $this->queryService->findOneById($id);
85
        } catch (\Throwable $e) {
86
            $errorMessage = sprintf('Unable to find entity of type \'%s\': %s', $this->entityName, $e->getMessage());
87
88
            $this->logger->error($errorMessage);
89
90
            throw new EntityRepositoryException($errorMessage, $e->getCode(), $e);
91
        }
92
    }
93
94
    /**
95
     * Return a single entity instance matching the provided $criteria.
96
     *
97
     * @param array $criteria The entity filter criteria.
98
     *
99
     * @return EntityInterface|null
100
     *
101
     * @throws EntityRepositoryException
102
     */
103
    public function findOneBy(array $criteria): ?EntityInterface
104
    {
105
        try {
106
            return $this->queryService->findOne($criteria);
107
        } catch (\Throwable $e) {
108
            $errorMessage = sprintf('Unable to find entity of type \'%s\': %s', $this->entityName, $e->getMessage());
109
110
            $this->logger->error($errorMessage);
111
112
            throw new EntityRepositoryException($errorMessage, $e->getCode(), $e);
113
        }
114
    }
115
116
    /**
117
     * Return all of the entities within the collection.
118
     *
119
     * @return EntityInterface[]
120
     *
121
     * @throws EntityRepositoryException
122
     */
123
    public function findAll(): array
124
    {
125
        return $this->findBy([]);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->findBy(array()) returns the type iterable which is incompatible with the type-hinted return array.
Loading history...
126
    }
127
128
    /**
129
     * Return a collection of entities that match the provided $criteria.
130
     *
131
     * @param array      $criteria
132
     * @param array|null $orderBy
133
     * @param int|null   $limit
134
     * @param int|null   $offset
135
     *
136
     * @return EntityInterface[]|iterable
137
     *
138
     * @throws EntityRepositoryException
139
     */
140
    public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): iterable
141
    {
142
        try {
143
            $options = [
144
                QueryServiceOption::LIMIT    => $limit,
145
                QueryServiceOption::OFFSET   => $offset,
146
                QueryServiceOption::ORDER_BY => $orderBy,
147
            ];
148
149
            return $this->queryService->findMany($criteria, $options);
1 ignored issue
show
Bug Best Practice introduced by
The expression return $this->queryServi...ny($criteria, $options) returns the type iterable which is incompatible with the return type mandated by Doctrine\Persistence\ObjectRepository::findBy() of array<mixed,object>.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
150
        } catch (\Throwable $e) {
151
            $errorMessage = sprintf(
152
                'Unable to return a collection of type \'%s\': %s',
153
                $this->entityName,
154
                $e->getMessage()
155
            );
156
157
            $this->logger->error($errorMessage);
158
159
            throw new EntityRepositoryException($errorMessage, $e->getCode(), $e);
160
        }
161
    }
162
163
    /**
164
     * Save a single entity instance.
165
     *
166
     * @param EntityInterface $entity
167
     * @param array           $options
168
     *
169
     * @return EntityInterface
170
     *
171
     * @throws EntityRepositoryException
172
     */
173
    public function save(EntityInterface $entity, array $options = []): EntityInterface
174
    {
175
        try {
176
            return $this->persistService->save($entity, $options);
177
        } catch (\Throwable $e) {
178
            $errorMessage = sprintf('Unable to save entity of type \'%s\': %s', $this->entityName, $e->getMessage());
179
180
            $this->logger->error($errorMessage);
181
182
            throw new EntityRepositoryException($errorMessage, $e->getCode(), $e);
183
        }
184
    }
185
186
    /**
187
     * Save a collection of entities in a single transaction.
188
     *
189
     * @param iterable|EntityInterface[] $collection The collection of entities that should be saved.
190
     * @param array                      $options    the optional save options.
191
     *
192
     * @return iterable
193
     *
194
     * @throws EntityRepositoryException If the save cannot be completed
195
     */
196
    public function saveCollection(iterable $collection, array $options = []): iterable
197
    {
198
        $flushMode = $options[EntityEventOption::FLUSH_MODE] ?? FlushMode::ENABLED;
199
        $transactionMode = $options[EntityEventOption::TRANSACTION_MODE] ?? TransactionMode::ENABLED;
200
201
        try {
202
            if (TransactionMode::ENABLED === $transactionMode) {
203
                $this->persistService->beginTransaction();
204
            }
205
206
            $entities = [];
207
            $saveOptions = [
208
                EntityEventOption::FLUSH_MODE       => FlushMode::DISABLED,
209
                EntityEventOption::TRANSACTION_MODE => TransactionMode::DISABLED
210
            ];
211
212
            foreach ($collection as $entity) {
213
                $entities[] = $this->save($entity, $saveOptions);
214
            }
215
216
            if (FlushMode::ENABLED === $flushMode) {
217
                $this->persistService->flush();
218
            }
219
            if (TransactionMode::ENABLED === $transactionMode) {
220
                $this->persistService->commitTransaction();
221
            }
222
223
            return $entities;
224
        } catch (\Throwable $e) {
225
            if (TransactionMode::ENABLED === $transactionMode) {
226
                $this->persistService->rollbackTransaction();
227
            }
228
229
            $errorMessage = sprintf(
230
                'Unable to save collection of type \'%s\' : %s',
231
                $this->entityName,
232
                $e->getMessage()
233
            );
234
235
            $this->logger->error($errorMessage);
236
237
            throw new EntityRepositoryException($errorMessage, $e->getCode(), $e);
238
        }
239
    }
240
241
    /**
242
     * Delete an entity.
243
     *
244
     * @param EntityInterface|string $entity
245
     * @param array                  $options
246
     *
247
     * @return bool
248
     *
249
     * @throws EntityRepositoryException
250
     */
251
    public function delete($entity, array $options = []): bool
252
    {
253
        if (!is_string($entity) && !$entity instanceof EntityInterface) {
1 ignored issue
show
introduced by
$entity is always a sub-type of Arp\Entity\EntityInterface.
Loading history...
254
            throw new EntityRepositoryException(
255
                sprintf(
256
                    'The \'entity\' argument must be a \'string\' or an object of type \'%s\'; '
257
                    . '\'%s\' provided in \'%s\'',
258
                    EntityInterface::class,
259
                    (is_object($entity) ? get_class($entity) : gettype($entity)),
260
                    __METHOD__
261
                )
262
            );
263
        }
264
265
        if (is_string($entity)) {
266
            $id = $entity;
267
            $entity = $this->find($id);
268
269
            if (null === $entity) {
270
                $errorMessage = sprintf(
271
                    'Unable to delete entity \'%s::%s\': The entity could not be found',
272
                    $this->entityName,
273
                    $id
274
                );
275
276
                $this->logger->error($errorMessage);
277
278
                throw new EntityNotFoundException($errorMessage);
279
            }
280
        }
281
282
        try {
283
            return $this->persistService->delete($entity, $options);
284
        } catch (\Throwable $e) {
285
            $errorMessage = sprintf(
286
                'Unable to delete entity of type \'%s\': %s',
287
                $this->entityName,
288
                $e->getMessage()
289
            );
290
291
            $this->logger->error($errorMessage);
292
293
            throw new EntityRepositoryException($errorMessage, $e->getCode(), $e);
294
        }
295
    }
296
297
    /**
298
     * Perform a deletion of a collection of entities.
299
     *
300
     * @param iterable|EntityInterface $collection
301
     * @param array                    $options
302
     *
303
     * @return int
304
     *
305
     * @throws EntityRepositoryException
306
     */
307
    public function deleteCollection(iterable $collection, array $options = []): int
308
    {
309
        $flushMode = $options[EntityEventOption::FLUSH_MODE] ?? FlushMode::ENABLED;
310
        $transactionMode = $options[EntityEventOption::TRANSACTION_MODE] ?? TransactionMode::ENABLED;
311
312
        try {
313
            if (TransactionMode::ENABLED === $transactionMode) {
314
                $this->persistService->beginTransaction();
315
            }
316
317
            $deleteOptions = [
318
                EntityEventOption::FLUSH_MODE       => FlushMode::DISABLED,
319
                EntityEventOption::TRANSACTION_MODE => TransactionMode::DISABLED,
320
            ];
321
322
            $deleted = 0;
323
            foreach ($collection as $entity) {
324
                if (true === $this->delete($entity, $deleteOptions)) {
325
                    $deleted++;
326
                }
327
            }
328
329
            if (FlushMode::ENABLED === $flushMode) {
330
                $this->persistService->flush();
331
            }
332
333
            if (TransactionMode::ENABLED === $transactionMode) {
334
                $this->persistService->commitTransaction();
335
            }
336
337
            return $deleted;
338
        } catch (\Throwable $e) {
339
            if (TransactionMode::ENABLED === $transactionMode) {
340
                $this->persistService->rollbackTransaction();
341
            }
342
343
            $errorMessage = sprintf(
344
                'Unable to delete collection of type \'%s\' : %s',
345
                $this->entityName,
346
                $e->getMessage()
347
            );
348
349
            $this->logger->error($errorMessage);
350
351
            throw new EntityRepositoryException($errorMessage, $e->getCode(), $e);
352
        }
353
    }
354
355
    /**
356
     * @throws EntityRepositoryException
357
     */
358
    public function clear(): void
359
    {
360
        try {
361
            $this->persistService->clear();
362
        } catch (\Throwable $e) {
363
            $errorMessage = sprintf(
364
                'Unable to clear entity of type \'%s\': %s',
365
                $this->entityName,
366
                $e->getMessage()
367
            );
368
369
            $this->logger->error($errorMessage);
370
371
            throw new EntityRepositoryException($errorMessage, $e->getCode(), $e);
372
        }
373
    }
374
375
    /**
376
     * @param EntityInterface $entity
377
     *
378
     * @throws EntityRepositoryException
379
     */
380
    public function refresh(EntityInterface $entity): void
381
    {
382
        try {
383
            $this->persistService->refresh($entity);
384
        } catch (\Throwable $e) {
385
            $errorMessage = sprintf(
386
                'Unable to refresh entity of type \'%s\': %s',
387
                $this->entityName,
388
                $e->getMessage()
389
            );
390
391
            $this->logger->error($errorMessage);
392
393
            throw new EntityRepositoryException($errorMessage, $e->getCode(), $e);
394
        }
395
    }
396
}
397