Passed
Pull Request — master (#1138)
by Tarmo
07:31
created

RestResourceBaseMethods   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 325
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 91
c 2
b 0
f 0
dl 0
loc 325
ccs 109
cts 109
cp 1
rs 10
wmc 26

16 Methods

Rating   Name   Duplication   Size   Complexity  
A findOne() 0 16 1
A find() 0 21 1
A findOneBy() 0 20 1
A getIds() 0 15 1
A delete() 0 17 1
A validateEntity() 0 7 4
A update() 0 32 1
A count() 0 14 1
A create() 0 21 1
A checkThatEntityExists() 0 5 3
A patch() 0 32 1
A getEntity() 0 9 2
A persistEntity() 0 11 1
A save() 0 18 1
A validateDto() 0 8 4
A createEntity() 0 12 2
1
<?php
2
declare(strict_types = 1);
3
/**
4
 * /src/Rest/Traits/RestResourceBaseMethods.php
5
 *
6
 * @author TLe, Tarmo Leppänen <[email protected]>
7
 */
8
9
namespace App\Rest\Traits;
10
11
use App\DTO\RestDtoInterface;
12
use App\Entity\Interfaces\EntityInterface;
13
use App\Exception\ValidatorException;
14
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
15
use Symfony\Component\Validator\ConstraintViolationListInterface;
16
use Throwable;
17
use UnexpectedValueException;
18
use function assert;
19
use function get_class;
20
21
/**
22
 * Trait RestResourceBaseMethods
23
 *
24
 * @package App\Rest\Traits
25
 */
26
trait RestResourceBaseMethods
27
{
28
    use RestResourceLifeCycles;
29
30
    /**
31
     * {@inheritdoc}
32
     *
33
     * @return array<int, EntityInterface>
34
     */
35 18
    public function find(
36
        ?array $criteria = null,
37
        ?array $orderBy = null,
38
        ?int $limit = null,
39
        ?int $offset = null,
40
        ?array $search = null
41
    ): array {
42 18
        $criteria ??= [];
43 18
        $orderBy ??= [];
44 18
        $search ??= [];
45
46
        // Before callback method call
47 18
        $this->beforeFind($criteria, $orderBy, $limit, $offset, $search);
48
49
        // Fetch data
50 18
        $entities = $this->getRepository()->findByAdvanced($criteria, $orderBy, $limit, $offset, $search);
0 ignored issues
show
Bug introduced by
It seems like getRepository() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

50
        $entities = $this->/** @scrutinizer ignore-call */ getRepository()->findByAdvanced($criteria, $orderBy, $limit, $offset, $search);
Loading history...
51
52
        // After callback method call
53 18
        $this->afterFind($criteria, $orderBy, $limit, $offset, $search, $entities);
54
55 18
        return $entities;
56
    }
57
58 102
    public function findOne(string $id, ?bool $throwExceptionIfNotFound = null): ?EntityInterface
59
    {
60 102
        $throwExceptionIfNotFound ??= false;
61
62
        // Before callback method call
63 102
        $this->beforeFindOne($id);
64
65
        /** @var EntityInterface|null $entity */
66 102
        $entity = $this->getRepository()->findAdvanced($id);
67
68 102
        $this->checkThatEntityExists($throwExceptionIfNotFound, $entity);
69
70
        // After callback method call
71 100
        $this->afterFindOne($id, $entity);
72
73 95
        return $entity;
74
    }
75
76 6
    public function findOneBy(
77
        array $criteria,
78
        ?array $orderBy = null,
79
        ?bool $throwExceptionIfNotFound = null
80
    ): ?EntityInterface {
81 6
        $orderBy ??= [];
82 6
        $throwExceptionIfNotFound ??= false;
83
84
        // Before callback method call
85 6
        $this->beforeFindOneBy($criteria, $orderBy);
86
87
        /** @var EntityInterface|null $entity */
88 6
        $entity = $this->getRepository()->findOneBy($criteria, $orderBy);
89
90 6
        $this->checkThatEntityExists($throwExceptionIfNotFound, $entity);
91
92
        // After callback method call
93 5
        $this->afterFindOneBy($criteria, $orderBy, $entity);
94
95 5
        return $entity;
96
    }
97
98 9
    public function count(?array $criteria = null, ?array $search = null): int
99
    {
100 9
        $criteria ??= [];
101 9
        $search ??= [];
102
103
        // Before callback method call
104 9
        $this->beforeCount($criteria, $search);
105
106 9
        $count = $this->getRepository()->countAdvanced($criteria, $search);
107
108
        // After callback method call
109 9
        $this->afterCount($criteria, $search, $count);
110
111 9
        return $count;
112
    }
113
114 3
    public function create(RestDtoInterface $dto, ?bool $flush = null, ?bool $skipValidation = null): EntityInterface
115
    {
116 3
        $flush ??= true;
117 3
        $skipValidation ??= false;
118
119
        // Create new entity
120 3
        $entity = $this->createEntity();
121
122
        // Before callback method call
123 3
        $this->beforeCreate($dto, $entity);
124
125
        // Validate DTO
126 3
        $this->validateDto($dto, $skipValidation);
127
128
        // Create or update entity
129 2
        $this->persistEntity($entity, $dto, $flush, $skipValidation);
130
131
        // After callback method call
132 2
        $this->afterCreate($dto, $entity);
133
134 2
        return $entity;
135
    }
136
137 4
    public function update(
138
        string $id,
139
        RestDtoInterface $dto,
140
        ?bool $flush = null,
141
        ?bool $skipValidation = null
142
    ): EntityInterface {
143 4
        $flush ??= true;
144 4
        $skipValidation ??= false;
145
146
        // Fetch entity
147 4
        $entity = $this->getEntity($id);
148
149
        /**
150
         * Determine used dto class and create new instance of that and load
151
         * entity to that. And after that patch that dto with given partial OR
152
         * whole dto class.
153
         */
154 3
        $restDto = $this->getDtoForEntity($id, get_class($dto), $dto);
0 ignored issues
show
Bug introduced by
It seems like getDtoForEntity() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

154
        /** @scrutinizer ignore-call */ 
155
        $restDto = $this->getDtoForEntity($id, get_class($dto), $dto);
Loading history...
155
156
        // Before callback method call
157 3
        $this->beforeUpdate($id, $restDto, $entity);
158
159
        // Validate DTO
160 3
        $this->validateDto($restDto, $skipValidation);
161
162
        // Create or update entity
163 2
        $this->persistEntity($entity, $restDto, $flush, $skipValidation);
164
165
        // After callback method call
166 2
        $this->afterUpdate($id, $restDto, $entity);
167
168 2
        return $entity;
169
    }
170
171 1
    public function patch(
172
        string $id,
173
        RestDtoInterface $dto,
174
        ?bool $flush = null,
175
        ?bool $skipValidation = null
176
    ): EntityInterface {
177 1
        $flush ??= true;
178 1
        $skipValidation ??= false;
179
180
        // Fetch entity
181 1
        $entity = $this->getEntity($id);
182
183
        /**
184
         * Determine used dto class and create new instance of that and load
185
         * entity to that. And after that patch that dto with given partial OR
186
         * whole dto class.
187
         */
188 1
        $restDto = $this->getDtoForEntity($id, get_class($dto), $dto, true);
189
190
        // Before callback method call
191 1
        $this->beforePatch($id, $restDto, $entity);
192
193
        // Validate DTO
194 1
        $this->validateDto($restDto, $skipValidation);
195
196
        // Create or update entity
197 1
        $this->persistEntity($entity, $restDto, $flush, $skipValidation);
198
199
        // After callback method call
200 1
        $this->afterPatch($id, $restDto, $entity);
201
202 1
        return $entity;
203
    }
204
205 4
    public function delete(string $id, ?bool $flush = null): EntityInterface
206
    {
207 4
        $flush ??= true;
208
209
        // Fetch entity
210 4
        $entity = $this->getEntity($id);
211
212
        // Before callback method call
213 3
        $this->beforeDelete($id, $entity);
214
215
        // And remove entity from repo
216 3
        $this->getRepository()->remove($entity, $flush);
217
218
        // After callback method call
219 3
        $this->afterDelete($id, $entity);
220
221 3
        return $entity;
222
    }
223
224
    /**
225
     * {@inheritdoc}
226
     *
227
     * @return array<int, string>
228
     */
229 5
    public function getIds(?array $criteria = null, ?array $search = null): array
230
    {
231 5
        $criteria ??= [];
232 5
        $search ??= [];
233
234
        // Before callback method call
235 5
        $this->beforeIds($criteria, $search);
236
237
        // Fetch data
238 5
        $ids = $this->getRepository()->findIds($criteria, $search);
239
240
        // After callback method call
241 5
        $this->afterIds($ids, $criteria, $search);
242
243 5
        return $ids;
244
    }
245
246 446
    public function save(EntityInterface $entity, ?bool $flush = null, ?bool $skipValidation = null): EntityInterface
247
    {
248 446
        $flush ??= true;
249 446
        $skipValidation ??= false;
250
251
        // Before callback method call
252 446
        $this->beforeSave($entity);
253
254
        // Validate current entity
255 446
        $this->validateEntity($entity, $skipValidation);
256
257
        // Persist on database
258 445
        $this->getRepository()->save($entity, $flush);
259
260
        // After callback method call
261 445
        $this->afterSave($entity);
262
263 445
        return $entity;
264
    }
265
266
    /**
267
     * Helper method to set data to specified entity and store it to database.
268
     *
269
     * @throws Throwable
270
     */
271 5
    protected function persistEntity(
272
        EntityInterface $entity,
273
        RestDtoInterface $dto,
274
        bool $flush,
275
        bool $skipValidation
276
    ): void {
277
        // Update entity according to DTO current state
278 5
        $dto->update($entity);
279
280
        // And save current entity
281 5
        $this->save($entity, $flush, $skipValidation);
282 5
    }
283
284
    /**
285
     * @throws NotFoundHttpException
286
     */
287 11
    protected function getEntity(string $id): EntityInterface
288
    {
289 11
        $entity = $this->getRepository()->find($id);
290
291 11
        if ($entity === null) {
292 3
            throw new NotFoundHttpException('Not found');
293
        }
294
295 8
        return $entity;
296
    }
297
298
    /**
299
     * Helper method to validate given DTO class.
300
     *
301
     * @throws Throwable
302
     */
303 7
    private function validateDto(RestDtoInterface $dto, bool $skipValidation): void
304
    {
305
        /** @var ConstraintViolationListInterface|null $errors */
306 7
        $errors = !$skipValidation ? $this->getValidator()->validate($dto) : null;
0 ignored issues
show
Bug introduced by
It seems like getValidator() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

306
        $errors = !$skipValidation ? $this->/** @scrutinizer ignore-call */ getValidator()->validate($dto) : null;
Loading history...
307
308
        // Oh noes, we have some errors
309 7
        if ($errors !== null && $errors->count() > 0) {
310 2
            throw new ValidatorException(get_class($dto), $errors);
311
        }
312 5
    }
313
314
    /**
315
     * Method to validate specified entity.
316
     *
317
     * @throws Throwable
318
     */
319 446
    private function validateEntity(EntityInterface $entity, bool $skipValidation): void
320
    {
321 446
        $errors = !$skipValidation ? $this->getValidator()->validate($entity) : null;
322
323
        // Oh noes, we have some errors
324 446
        if ($errors !== null && $errors->count() > 0) {
325 1
            throw new ValidatorException(get_class($entity), $errors);
326
        }
327 445
    }
328
329 3
    private function createEntity(): EntityInterface
330
    {
331
        /** @var class-string $entityClass */
332 3
        $entityClass = $this->getRepository()->getEntityName();
333
334 3
        $entity = new $entityClass();
335
336 3
        $exception = new UnexpectedValueException(
337 3
            sprintf('Given `%s` class does not implement `EntityInterface`', $entityClass),
338
        );
339
340 3
        return assert($entity instanceof EntityInterface) ? $entity : throw $exception;
341
    }
342
343
    /**
344
     * @throws NotFoundHttpException
345
     */
346 108
    private function checkThatEntityExists(bool $throwExceptionIfNotFound, ?EntityInterface $entity): void
347
    {
348
        // Entity not found
349 108
        if ($throwExceptionIfNotFound && $entity === null) {
350 3
            throw new NotFoundHttpException('Not found');
351
        }
352 105
    }
353
}
354