AbstractEntityRepository::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 4
c 1
b 0
f 0
dl 0
loc 10
rs 10
cc 1
nc 1
nop 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Arp\DoctrineEntityRepository;
6
7
use Arp\DoctrineEntityRepository\Constant\HydrateMode;
8
use Arp\DoctrineEntityRepository\Constant\QueryServiceOption;
9
use Arp\DoctrineEntityRepository\Exception\EntityNotFoundException;
10
use Arp\DoctrineEntityRepository\Exception\EntityRepositoryException;
11
use Arp\DoctrineEntityRepository\Persistence\Exception\PersistenceException;
12
use Arp\DoctrineEntityRepository\Persistence\PersistServiceInterface;
13
use Arp\DoctrineEntityRepository\Query\Exception\QueryServiceException;
14
use Arp\DoctrineEntityRepository\Query\QueryServiceInterface;
15
use Arp\Entity\EntityInterface;
16
use Doctrine\ORM\AbstractQuery;
17
use Doctrine\ORM\QueryBuilder;
18
use Psr\Log\LoggerInterface;
19
20
/**
21
 * @author  Alex Patterson <[email protected]>
22
 * @package Arp\DoctrineEntityRepository
23
 */
24
abstract class AbstractEntityRepository implements EntityRepositoryInterface
25
{
26
    /**
27
     * @var string
28
     */
29
    protected string $entityName;
30
31
    /**
32
     * @var QueryServiceInterface
33
     */
34
    protected QueryServiceInterface $queryService;
35
36
    /**
37
     * @var PersistServiceInterface
38
     */
39
    protected PersistServiceInterface $persistService;
40
41
    /**
42
     * @var LoggerInterface
43
     */
44
    protected LoggerInterface $logger;
45
46
    /**
47
     * @param string                  $entityName
48
     * @param QueryServiceInterface   $queryService
49
     * @param PersistServiceInterface $persistService
50
     * @param LoggerInterface         $logger
51
     */
52
    public function __construct(
53
        string $entityName,
54
        QueryServiceInterface $queryService,
55
        PersistServiceInterface $persistService,
56
        LoggerInterface $logger
57
    ) {
58
        $this->entityName = $entityName;
59
        $this->queryService = $queryService;
60
        $this->persistService = $persistService;
61
        $this->logger = $logger;
62
    }
63
64
    /**
65
     * Return the fully qualified class name of the mapped entity instance.
66
     *
67
     * @return string
68
     */
69
    public function getClassName(): string
70
    {
71
        return $this->entityName;
72
    }
73
74
    /**
75
     * Return a single entity instance matching the provided $id.
76
     *
77
     * @param string|int $id
78
     *
79
     * @return EntityInterface|null
80
     *
81
     * @throws EntityRepositoryException
82
     */
83
    public function find($id): ?EntityInterface
84
    {
85
        try {
86
            return $this->queryService->findOneById($id);
87
        } catch (QueryServiceException $e) {
88
            $errorMessage = sprintf('Unable to find entity of type \'%s\': %s', $this->entityName, $e->getMessage());
89
90
            $this->logger->error($errorMessage, ['exception' => $e, 'id' => $id]);
91
92
            throw new EntityRepositoryException($errorMessage, $e->getCode(), $e);
93
        }
94
    }
95
96
    /**
97
     * Return a single entity instance matching the provided $criteria.
98
     *
99
     * @param array<mixed> $criteria The entity filter criteria.
100
     *
101
     * @return EntityInterface|null
102
     *
103
     * @throws EntityRepositoryException
104
     */
105
    public function findOneBy(array $criteria): ?EntityInterface
106
    {
107
        try {
108
            return $this->queryService->findOne($criteria);
109
        } catch (QueryServiceException $e) {
110
            $errorMessage = sprintf('Unable to find entity of type \'%s\': %s', $this->entityName, $e->getMessage());
111
112
            $this->logger->error($errorMessage, ['exception' => $e, 'criteria' => $criteria]);
113
114
            throw new EntityRepositoryException($errorMessage, $e->getCode(), $e);
115
        }
116
    }
117
118
    /**
119
     * Return all of the entities within the collection.
120
     *
121
     * @return EntityInterface[]|iterable
122
     *
123
     * @throws EntityRepositoryException
124
     */
125
    public function findAll(): iterable
126
    {
127
        return $this->findBy([]);
128
    }
129
130
    /**
131
     * Return a collection of entities that match the provided $criteria.
132
     *
133
     * @param array<mixed>      $criteria
134
     * @param array<mixed>|null $orderBy
135
     * @param int|null          $limit
136
     * @param int|null          $offset
137
     *
138
     * @return EntityInterface[]|iterable
139
     *
140
     * @throws EntityRepositoryException
141
     */
142
    public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): iterable
143
    {
144
        $options = [];
145
146
        try {
147
            if (null !== $orderBy) {
148
                $options[QueryServiceOption::ORDER_BY] = $orderBy;
149
            }
150
151
            if (null !== $limit) {
152
                $options[QueryServiceOption::MAX_RESULTS] = $limit;
153
            }
154
155
            if (null !== $offset) {
156
                $options[QueryServiceOption::FIRST_RESULT] = $offset;
157
            }
158
159
            return $this->queryService->findMany($criteria, $options);
160
        } catch (QueryServiceException $e) {
161
            $errorMessage = sprintf(
162
                'Unable to return a collection of type \'%s\': %s',
163
                $this->entityName,
164
                $e->getMessage()
165
            );
166
167
            $this->logger->error(
168
                $errorMessage,
169
                ['exception' => $e, 'criteria' => $criteria, 'options' => $options]
170
            );
171
172
            throw new EntityRepositoryException($errorMessage, $e->getCode(), $e);
173
        }
174
    }
175
176
    /**
177
     * Save a single entity instance
178
     *
179
     * @param EntityInterface $entity
180
     * @param array<mixed>    $options
181
     *
182
     * @return EntityInterface
183
     *
184
     * @throws EntityRepositoryException
185
     */
186
    public function save(EntityInterface $entity, array $options = []): EntityInterface
187
    {
188
        try {
189
            return $this->persistService->save($entity, $options);
190
        } catch (PersistenceException $e) {
191
            $errorMessage = sprintf('Unable to save entity of type \'%s\': %s', $this->entityName, $e->getMessage());
192
193
            $this->logger->error($errorMessage, ['exception' => $e, 'entity_name' => $this->entityName]);
194
195
            throw new EntityRepositoryException($errorMessage, $e->getCode(), $e);
196
        }
197
    }
198
199
    /**
200
     * Save a collection of entities in a single transaction
201
     *
202
     * @param iterable<EntityInterface> $collection The collection of entities that should be saved.
203
     * @param array<mixed>              $options    the optional save options.
204
     *
205
     * @return iterable<EntityInterface>
206
     *
207
     * @throws EntityRepositoryException If the save cannot be completed
208
     */
209
    public function saveCollection(iterable $collection, array $options = []): iterable
210
    {
211
        try {
212
            return $this->persistService->saveCollection($collection, $options);
213
        } catch (PersistenceException $e) {
214
            $errorMessage = sprintf('Unable to save entity of type \'%s\': %s', $this->entityName, $e->getMessage());
215
216
            $this->logger->error($errorMessage, ['exception' => $e, 'entity_name' => $this->entityName]);
217
218
            throw new EntityRepositoryException($errorMessage, $e->getCode(), $e);
219
        }
220
    }
221
222
    /**
223
     * @param EntityInterface|string|int|mixed $entity
224
     * @param array<mixed>                     $options
225
     *
226
     * @return bool
227
     *
228
     * @throws EntityRepositoryException
229
     */
230
    public function delete($entity, array $options = []): bool
231
    {
232
        if (is_string($entity) || is_int($entity)) {
233
            $id = $entity;
234
            $entity = $this->find($id);
235
236
            if (null === $entity) {
237
                $errorMessage = sprintf(
238
                    'Unable to delete entity \'%s::%s\': The entity could not be found',
239
                    $this->entityName,
240
                    $id
241
                );
242
243
                $this->logger->error($errorMessage);
244
245
                throw new EntityNotFoundException($errorMessage);
246
            }
247
        } elseif (!$entity instanceof EntityInterface) {
248
            $errorMessage = sprintf(
249
                'The \'entity\' argument must be a \'string\' or an object of type \'%s\'; '
250
                . '\'%s\' provided in \'%s::%s\'',
251
                EntityInterface::class,
252
                (is_object($entity) ? get_class($entity) : gettype($entity)),
253
                static::class,
254
                __FUNCTION__
255
            );
256
257
            $this->logger->error($errorMessage);
258
259
            throw new EntityRepositoryException($errorMessage);
260
        }
261
262
263
        try {
264
            return $this->persistService->delete($entity, $options);
265
        } catch (\Exception $e) {
266
            $errorMessage = sprintf(
267
                'Unable to delete entity of type \'%s\': %s',
268
                $this->entityName,
269
                $e->getMessage()
270
            );
271
272
            $this->logger->error($errorMessage, ['exception' => $e, 'entity_name' => $this->entityName]);
273
274
            throw new EntityRepositoryException($errorMessage, $e->getCode(), $e);
275
        }
276
    }
277
278
    /**
279
     * Perform a deletion of a collection of entities
280
     *
281
     * @param iterable<EntityInterface> $collection
282
     * @param array<mixed>              $options
283
     *
284
     * @return int
285
     *
286
     * @throws EntityRepositoryException
287
     */
288
    public function deleteCollection(iterable $collection, array $options = []): int
289
    {
290
        try {
291
            return $this->persistService->deleteCollection($collection, $options);
292
        } catch (PersistenceException $e) {
293
            $errorMessage = sprintf(
294
                'Unable to delete entity collection of type \'%s\': %s',
295
                $this->entityName,
296
                $e->getMessage()
297
            );
298
299
            $this->logger->error($errorMessage, ['exception' => $e, 'entity_name' => $this->entityName]);
300
301
            throw new EntityRepositoryException($errorMessage, $e->getCode(), $e);
302
        }
303
    }
304
305
    /**
306
     * @throws EntityRepositoryException
307
     */
308
    public function clear(): void
309
    {
310
        try {
311
            $this->persistService->clear();
312
        } catch (PersistenceException $e) {
313
            $errorMessage = sprintf(
314
                'Unable to clear entity of type \'%s\': %s',
315
                $this->entityName,
316
                $e->getMessage()
317
            );
318
319
            $this->logger->error($errorMessage, ['exception' => $e, 'entity_name' => $this->entityName]);
320
321
            throw new EntityRepositoryException($errorMessage, $e->getCode(), $e);
322
        }
323
    }
324
325
    /**
326
     * @param EntityInterface $entity
327
     *
328
     * @throws EntityRepositoryException
329
     */
330
    public function refresh(EntityInterface $entity): void
331
    {
332
        try {
333
            $this->persistService->refresh($entity);
334
        } catch (PersistenceException $e) {
335
            $errorMessage = sprintf(
336
                'Unable to refresh entity of type \'%s\': %s',
337
                $this->entityName,
338
                $e->getMessage()
339
            );
340
341
            $this->logger->error(
342
                $errorMessage,
343
                ['exception' => $e, 'entity_name' => $this->entityName, 'id' => $entity->getId()]
344
            );
345
346
            throw new EntityRepositoryException($errorMessage, $e->getCode(), $e);
347
        }
348
    }
349
350
    /**
351
     * Execute query builder or query instance and return the results.
352
     *
353
     * @param object|QueryBuilder|AbstractQuery $query
354
     * @param array<mixed>                      $options
355
     *
356
     * @return EntityInterface[]|iterable
357
     *
358
     * @throws EntityRepositoryException
359
     */
360
    protected function executeQuery(object $query, array $options = [])
361
    {
362
        try {
363
            return $this->queryService->execute($query, $options);
364
        } catch (QueryServiceException $e) {
365
            $errorMessage = sprintf(
366
                'Failed to perform query for entity type \'%s\': %s',
367
                $this->entityName,
368
                $e->getMessage()
369
            );
370
371
            $this->logger->error($errorMessage, ['exception' => $e]);
372
373
            throw new EntityRepositoryException($errorMessage, $e->getCode(), $e);
374
        }
375
    }
376
377
    /**
378
     * Return a single entity instance. NULL will be returned if the result set contains 0 or more than 1 result.
379
     *
380
     * Optionally control the object hydration with QueryServiceOption::HYDRATE_MODE.
381
     *
382
     * @param object|AbstractQuery|QueryBuilder $query
383
     * @param array<string, mixed>       $options
384
     *
385
     * @return array<mixed>|EntityInterface|null
386
     *
387
     * @throws EntityRepositoryException
388
     */
389
    protected function getSingleResultOrNull(object $query, array $options = [])
390
    {
391
        try {
392
            return $this->queryService->getSingleResultOrNull($query, $options);
393
        } catch (QueryServiceException $e) {
394
            $errorMessage = sprintf(
395
                'Failed to perform query for entity type \'%s\': %s',
396
                $this->entityName,
397
                $e->getMessage()
398
            );
399
400
            $this->logger->error(
401
                $errorMessage,
402
                ['exception' => $e, 'entity_name' => $this->entityName]
403
            );
404
405
            throw new EntityRepositoryException($errorMessage, $e->getCode(), $e);
406
        }
407
    }
408
409
    /**
410
     * Return a result set containing a single array result. NULL will be returned if the result set
411
     * contains 0 or more than 1 result.
412
     *
413
     * @param object       $query
414
     * @param array<mixed> $options
415
     *
416
     * @return array<mixed>|null
417
     *
418
     * @throws EntityRepositoryException
419
     */
420
    protected function getSingleArrayResultOrNull(object $query, array $options = []): ?array
421
    {
422
        $result = $this->getSingleResultOrNull(
423
            $query,
424
            array_replace_recursive(
425
                $options,
426
                [QueryServiceOption::HYDRATION_MODE => HydrateMode::ARRAY]
427
            )
428
        );
429
430
        return is_array($result) ? $result : null;
431
    }
432
}
433