Test Failed
Pull Request — master (#8)
by Alex
03:38
created

QueryService::findMany()   A

Complexity

Conditions 2
Paths 3

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 11
c 1
b 0
f 0
dl 0
loc 17
rs 9.9
cc 2
nc 3
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Arp\DoctrineEntityRepository\Query;
6
7
use Arp\DoctrineEntityRepository\Constant\QueryServiceOption;
8
use Arp\DoctrineEntityRepository\Query\Exception\QueryServiceException;
9
use Arp\Entity\EntityInterface;
10
use Doctrine\ORM\AbstractQuery;
11
use Doctrine\ORM\EntityManagerInterface;
12
use Doctrine\ORM\Query;
13
use Doctrine\ORM\QueryBuilder;
14
use Psr\Log\LoggerInterface;
15
16
/**
17
 * @author  Alex Patterson <[email protected]>
18
 * @package Arp\DoctrineEntityRepository\Query
19
 */
20
class QueryService implements QueryServiceInterface
21
{
22
    /**
23
     * @var string
24
     */
25
    protected string $entityName;
26
27
    /**
28
     * @var EntityManagerInterface
29
     */
30
    protected EntityManagerInterface $entityManager;
31
32
    /**
33
     * @var LoggerInterface
34
     */
35
    protected LoggerInterface $logger;
36
37
    /**
38
     * @param string                 $entityName
39
     * @param EntityManagerInterface $entityManager
40
     * @param LoggerInterface        $logger
41
     */
42
    public function __construct(string $entityName, EntityManagerInterface $entityManager, LoggerInterface $logger)
43
    {
44
        $this->entityName = $entityName;
45
        $this->entityManager = $entityManager;
46
        $this->logger = $logger;
47
    }
48
49
    /**
50
     * @return string
51
     */
52
    public function getEntityName(): string
53
    {
54
        return $this->entityName;
55
    }
56
57
    /**
58
     * Return a new query builder instance.
59
     *
60
     * @param string|null $alias The optional query builder alias.
61
     *
62
     * @return QueryBuilder
63
     */
64
    public function createQueryBuilder(string $alias = null): QueryBuilder
65
    {
66
        $queryBuilder = $this->entityManager->createQueryBuilder();
67
68
        if (null !== $alias) {
69
            $queryBuilder->select($alias)->from($this->entityName, $alias);
70
        }
71
72
        return $queryBuilder;
73
    }
74
75
    /**
76
     * @param AbstractQuery|QueryBuilder $queryOrBuilder
77
     * @param array<string, mixed>       $options
78
     *
79
     * @return EntityInterface|null|array<mixed>
80
     *
81
     * @throws QueryServiceException
82
     */
83
    public function getSingleResultOrNull($queryOrBuilder, array $options = [])
84
    {
85
        $result = $this->execute($queryOrBuilder, $options);
86
87
        if (empty($result)) {
88
            return null;
89
        }
90
91
        if (!is_array($result)) {
92
            return $result;
93
        }
94
95
        if (count($result) > 1) {
96
            return null;
97
        }
98
99
        return array_shift($result);
100
    }
101
102
    /**
103
     * Construct and execute the query.
104
     *
105
     * @param AbstractQuery|QueryBuilder|mixed $queryOrBuilder
106
     * @param array<string, mixed>             $options
107
     *
108
     * @return mixed
109
     *
110
     * @throws QueryServiceException
111
     */
112
    public function execute($queryOrBuilder, array $options = [])
113
    {
114
        if ($queryOrBuilder instanceof QueryBuilder) {
115
            $this->prepareQueryBuilder($queryOrBuilder, $options);
116
117
            $queryOrBuilder = $queryOrBuilder->getQuery();
118
        }
119
120
        if (!$queryOrBuilder instanceof AbstractQuery) {
121
            throw new QueryServiceException(
122
                sprintf(
123
                    'Query provided must be of type \'%s\'; \'%s\' provided in \'%s\'.',
124
                    AbstractQuery::class,
125
                    (is_object($queryOrBuilder) ? get_class($queryOrBuilder) : gettype($queryOrBuilder)),
126
                    __METHOD__
127
                )
128
            );
129
        }
130
131
        try {
132
            $query = $this->prepareQuery($queryOrBuilder, $options);
133
134
            return $query->execute();
135
        } catch (\Throwable $e) {
136
            $message = sprintf('Failed to execute query : %s', $e->getMessage());
137
138
            $this->logger->error($message, ['exception' => $e]);
139
140
            throw new QueryServiceException($message, $e->getCode(), $e);
141
        }
142
    }
143
144
    /**
145
     * Find a single entity matching the provided identity.
146
     *
147
     * @param mixed                $id      The identity of the entity to match.
148
     * @param array<string, mixed> $options The optional query options.
149
     *
150
     * @return EntityInterface|null
151
     *
152
     * @throws QueryServiceException
153
     */
154
    public function findOneById($id, array $options = []): ?EntityInterface
155
    {
156
        return $this->findOne(compact('id'), $options);
157
    }
158
159
    /**
160
     * Find a single entity matching the provided criteria.
161
     *
162
     * @param array<string, mixed> $criteria The search criteria that should be matched on.
163
     * @param array<string, mixed> $options  The optional query options.
164
     *
165
     * @return EntityInterface|null
166
     *
167
     * @throws QueryServiceException
168
     */
169
    public function findOne(array $criteria, array $options = []): ?EntityInterface
170
    {
171
        try {
172
            $persist = $this->entityManager->getUnitOfWork()->getEntityPersister($this->entityName);
173
174
            $entity = $persist->load(
175
                $criteria,
176
                $options[QueryServiceOption::ENTITY] ?? null,
177
                $options[QueryServiceOption::ASSOCIATION] ?? null,
178
                $options[QueryServiceOption::HINTS] ?? [],
179
                $options[QueryServiceOption::LOCK_MODE] ?? null,
180
                1,
181
                $options[QueryServiceOption::ORDER_BY] ?? null
182
            );
183
184
            return ($entity instanceof EntityInterface) ? $entity : null;
185
        } catch (\Throwable $e) {
186
            $message = sprintf('Failed to execute \'findOne\' query: %s', $e->getMessage());
187
188
            $this->logger->error($message, ['exception' => $e, 'criteria' => $criteria, 'options' => $options]);
189
190
            throw new QueryServiceException($message, $e->getCode(), $e);
191
        }
192
    }
193
194
    /**
195
     * Find a collection of entities that match the provided criteria.
196
     *
197
     * @param array<string, mixed> $criteria The search criteria that should be matched on.
198
     * @param array<string, mixed> $options  The optional query options.
199
     *
200
     * @return iterable<EntityInterface>
201
     *
202
     * @throws QueryServiceException
203
     */
204
    public function findMany(array $criteria, array $options = []): iterable
205
    {
206
        try {
207
            $persister = $this->entityManager->getUnitOfWork()->getEntityPersister($this->entityName);
208
209
            return $persister->loadAll(
210
                $criteria,
211
                $options[QueryServiceOption::ORDER_BY] ?? null,
212
                $options[QueryServiceOption::LIMIT] ?? null,
213
                $options[QueryServiceOption::OFFSET] ?? null
214
            );
215
        } catch (\Throwable $e) {
216
            $message = sprintf('Failed to execute \'findMany\' query: %s', $e->getMessage());
217
218
            $this->logger->error($message, ['exception' => $e, 'criteria' => $criteria, 'options' => $options]);
219
220
            throw new QueryServiceException($message, $e->getCode(), $e);
221
        }
222
    }
223
224
    /**
225
     * Return the result set count.
226
     *
227
     * @param array<string, mixed> $criteria
228
     *
229
     * @return mixed
230
     */
231
    public function count(array $criteria)
232
    {
233
        $unitOfWork = $this->entityManager->getUnitOfWork();
234
235
        return $unitOfWork->getEntityPersister($this->entityName)->count($criteria);
236
    }
237
238
    /**
239
     * Set the query builder options.
240
     *
241
     * @param QueryBuilder         $queryBuilder The query builder to update.
242
     * @param array<string, mixed> $options      The query builder options to set.
243
     *
244
     * @return QueryBuilder
245
     */
246
    protected function prepareQueryBuilder(QueryBuilder $queryBuilder, array $options = []): QueryBuilder
247
    {
248
        if (array_key_exists(QueryServiceOption::FIRST_RESULT, $options)) {
249
            $queryBuilder->setFirstResult($options[QueryServiceOption::FIRST_RESULT]);
250
        }
251
252
        if (array_key_exists(QueryServiceOption::MAX_RESULTS, $options)) {
253
            $queryBuilder->setMaxResults($options[QueryServiceOption::MAX_RESULTS]);
254
        }
255
256
        if (
257
            array_key_exists(QueryServiceOption::ORDER_BY, $options)
258
            && is_array($options[QueryServiceOption::ORDER_BY])
259
        ) {
260
            foreach ($options[QueryServiceOption::ORDER_BY] as $fieldName => $orderDirection) {
261
                $queryBuilder->addOrderBy(
262
                    $fieldName,
263
                    ('DESC' === strtoupper($orderDirection) ? 'DESC' : 'ASC')
264
                );
265
            }
266
        }
267
268
        return $queryBuilder;
269
    }
270
271
    /**
272
     * Prepare the provided query by setting the $options.
273
     *
274
     * @param AbstractQuery        $query
275
     * @param array<string, mixed> $options
276
     *
277
     * @return AbstractQuery
278
     *
279
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
280
     * @todo Reduce cyclomatic complexity
281
     */
282
    protected function prepareQuery(AbstractQuery $query, array $options = []): AbstractQuery
283
    {
284
        if (array_key_exists('params', $options)) {
285
            $query->setParameters($options['params']);
286
        }
287
288
        if (array_key_exists(QueryServiceOption::HYDRATION_MODE, $options)) {
289
            $query->setHydrationMode($options[QueryServiceOption::HYDRATION_MODE]);
290
        }
291
292
        if (array_key_exists('result_set_mapping', $options)) {
293
            $query->setResultSetMapping($options['result_set_mapping']);
294
        }
295
296
        if (isset($options[QueryServiceOption::HINTS]) && is_array($options[QueryServiceOption::HINTS])) {
297
            foreach ($options[QueryServiceOption::HINTS] as $hint => $hintValue) {
298
                $query->setHint($hint, $hintValue);
299
            }
300
        }
301
302
        if (!empty($options[QueryServiceOption::DQL]) && $query instanceof Query) {
303
            $query->setDQL($options[QueryServiceOption::DQL]);
304
        }
305
306
        return $query;
307
    }
308
}
309