Completed
Push — master ( 52a24b...0631b2 )
by Alex
16s queued 13s
created

QueryService::execute()   A

Complexity

Conditions 3
Paths 3

Size

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