Passed
Push — master ( 932fc1...a9416d )
by Peter
02:06
created

ApiAbstract::handleListSuccess()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 2
dl 0
loc 13
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace AbterPhp\Framework\Http\Controllers;
6
7
use AbterPhp\Framework\Databases\Queries\FoundRows;
8
use AbterPhp\Framework\Domain\Entities\IStringerEntity;
9
use AbterPhp\Framework\Domain\Entities\IToJsoner;
0 ignored issues
show
Bug introduced by
The type AbterPhp\Framework\Domain\Entities\IToJsoner was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
use AbterPhp\Framework\Http\Service\Execute\RepoServiceAbstract;
11
use Opulence\Http\Requests\UploadedFile;
12
use Opulence\Http\Responses\Response;
13
use Opulence\Http\Responses\ResponseHeaders;
14
use Opulence\Orm\OrmException;
15
use Opulence\Routing\Controller;
16
use Psr\Log\LoggerInterface;
17
18
abstract class ApiAbstract extends Controller
19
{
20
    const LOG_MSG_CREATE_FAILURE = 'Creating %1$s failed.';
21
    const LOG_MSG_UPDATE_FAILURE = 'Updating %1$s with id "%2$s" failed.';
22
    const LOG_MSG_DELETE_FAILURE = 'Deleting %1$s with id "%2$s" failed.';
23
    const LOG_MSG_GET_FAILURE    = 'Retrieving %1$s with id "%2$s" failed.';
24
    const LOG_MSG_LIST_FAILURE   = 'Retrieving %1$s failed.';
25
26
    const LOG_CONTEXT_EXCEPTION  = 'Exception';
27
    const LOG_PREVIOUS_EXCEPTION = 'Previous exception #%d';
28
29
    const ENTITY_SINGULAR = '';
30
    const ENTITY_PLURAL   = '';
31
32
    /** @var LoggerInterface */
33
    protected $logger;
34
35
    /** @var RepoService */
0 ignored issues
show
Bug introduced by
The type AbterPhp\Framework\Http\Controllers\RepoService was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
36
    protected $repoService;
37
38
    /** @var FoundRows */
39
    protected $foundRows;
40
41
    /** @var string */
42
    protected $problemBaseUrl;
43
44
    /**
45
     * ApiAbstract constructor.
46
     *
47
     * @param LoggerInterface     $logger
48
     * @param RepoServiceAbstract $repoService
49
     * @param FoundRows           $foundRows
50
     * @param string              $problemBaseUrl
51
     */
52
    public function __construct(
53
        LoggerInterface $logger,
54
        RepoServiceAbstract $repoService,
55
        FoundRows $foundRows,
56
        string $problemBaseUrl
57
    ) {
58
        $this->logger         = $logger;
59
        $this->repoService    = $repoService;
0 ignored issues
show
Documentation Bug introduced by
It seems like $repoService of type AbterPhp\Framework\Http\...ute\RepoServiceAbstract is incompatible with the declared type AbterPhp\Framework\Http\Controllers\RepoService of property $repoService.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
60
        $this->foundRows      = $foundRows;
61
        $this->problemBaseUrl = $problemBaseUrl;
62
    }
63
64
    /**
65
     * @param string $entityId
66
     *
67
     * @return Response
68
     */
69
    public function get(string $entityId): Response
70
    {
71
        try {
72
            $entity = $this->repoService->retrieveEntity($entityId);
73
        } catch (\Exception $e) {
74
            $msg = sprintf(static::LOG_MSG_GET_FAILURE, static::ENTITY_SINGULAR);
75
76
            return $this->handleException($msg, $e);
77
        }
78
79
        return $this->handleGetSuccess($entity);
80
    }
81
82
    /**
83
     * @return Response
84
     */
85
    public function list(): Response
86
    {
87
        $query = $this->request->getQuery();
88
89
        $offset = (int)$query->get('offset', 0);
90
        $limit  = (int)$query->get('limit', 100);
91
92
        try {
93
            $entities = $this->repoService->retrieveList($offset, $limit, [], [], []);
94
        } catch (\Exception $e) {
95
            $msg = sprintf(static::LOG_MSG_LIST_FAILURE, static::ENTITY_PLURAL);
96
97
            return $this->handleException($msg, $e);
98
        }
99
100
        $maxCount = $this->foundRows->get();
101
102
        return $this->handleListSuccess($entities, $maxCount);
103
    }
104
105
    /**
106
     * @return Response
107
     */
108
    public function create(): Response
109
    {
110
        try {
111
            $data = $this->getCreateData();
112
113
            $errors = $this->repoService->validateForm($data);
114
115
            if (count($errors) > 0) {
116
                $msg = sprintf(static::LOG_MSG_CREATE_FAILURE, static::ENTITY_SINGULAR);
117
118
                return $this->handleErrors($msg, $errors);
119
            }
120
121
            $fileData = $this->getFileData($data);
122
            $entity   = $this->repoService->create($data, $fileData);
123
        } catch (\Exception $e) {
124
            $msg = sprintf(static::LOG_MSG_CREATE_FAILURE, static::ENTITY_SINGULAR);
125
126
            return $this->handleException($msg, $e);
127
        }
128
129
        return $this->handleCreateSuccess($entity);
130
    }
131
132
    /**
133
     * @param string $entityId
134
     *
135
     * @return Response
136
     */
137
    public function update(string $entityId): Response
138
    {
139
        try {
140
            $data = $this->getUpdateData();
141
142
            $errors = $this->repoService->validateForm($data);
143
144
            if (count($errors) > 0) {
145
                $msg = sprintf(static::LOG_MSG_UPDATE_FAILURE, static::ENTITY_SINGULAR, $entityId);
146
147
                return $this->handleErrors($msg, $errors);
148
            }
149
150
            $fileData = $this->getFileData($data);
151
            $entity   = $this->repoService->retrieveEntity($entityId);
152
            $this->repoService->update($entity, $data, $fileData);
153
        } catch (\Exception $e) {
154
            if ($this->isEntityNotFound($e)) {
155
                return $this->handleNotFound();
156
            }
157
158
            $msg = sprintf(static::LOG_MSG_UPDATE_FAILURE, static::ENTITY_SINGULAR, $entityId);
159
160
            return $this->handleException($msg, $e);
161
        }
162
163
        return $this->handleUpdateSuccess($entity);
164
    }
165
166
    /**
167
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
168
     *
169
     * @param array $data
170
     *
171
     * @return UploadedFile[]
172
     */
173
    protected function getFileData(array $data): array
0 ignored issues
show
Unused Code introduced by
The parameter $data is not used and could be removed. ( Ignorable by Annotation )

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

173
    protected function getFileData(/** @scrutinizer ignore-unused */ array $data): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
174
    {
175
        return [];
176
    }
177
178
    /**
179
     * @param string $entityId
180
     *
181
     * @return Response
182
     */
183
    public function delete(string $entityId): Response
184
    {
185
        try {
186
            $entity = $this->repoService->retrieveEntity($entityId);
187
            $this->repoService->delete($entity);
188
        } catch (\Exception $e) {
189
            if ($this->isEntityNotFound($e)) {
190
                return $this->handleNotFound();
191
            }
192
193
            $msg = sprintf(static::LOG_MSG_DELETE_FAILURE, static::ENTITY_SINGULAR, $entityId);
194
195
            return $this->handleException($msg, $e);
196
        }
197
198
        return $this->handleDeleteSuccess();
199
    }
200
201
    /**
202
     * @param \Exception $e
203
     *
204
     * @return bool
205
     */
206
    protected function isEntityNotFound(\Exception $e): bool
207
    {
208
        if (!($e instanceof OrmException)) {
209
            return false;
210
        }
211
212
        return $e->getMessage() === 'Failed to find entity';
213
    }
214
215
    /**
216
     * @return array
217
     */
218
    public function getCreateData(): array
219
    {
220
        return $this->getSharedData();
221
    }
222
223
    /**
224
     * @return array
225
     */
226
    public function getUpdateData(): array
227
    {
228
        return $this->getSharedData();
229
    }
230
231
    /**
232
     * @return array
233
     */
234
    public function getSharedData(): array
235
    {
236
        return $this->request->getJsonBody();
237
    }
238
239
    /**
240
     * @param string $msg
241
     * @param array  $errors
242
     *
243
     * @return Response
244
     */
245
    protected function handleErrors(string $msg, array $errors): Response
246
    {
247
        $this->logger->debug($msg);
248
249
        $detail = [];
250
        foreach ($errors as $key => $keyErrors) {
251
            foreach ($keyErrors as $keyError) {
252
                $detail[] = sprintf('%s: %s', $key, $keyError);
253
            }
254
        }
255
256
        $status  = ResponseHeaders::HTTP_BAD_REQUEST;
257
        $content = [
258
            'type'   => sprintf('%sbad-request', $this->problemUrlBase),
0 ignored issues
show
Bug introduced by
The property problemUrlBase does not exist on AbterPhp\Framework\Http\Controllers\ApiAbstract. Did you mean problemBaseUrl?
Loading history...
259
            'title'  => 'Bad Request',
260
            'status' => $status,
261
            'detail' => implode("\n", $detail),
262
        ];
263
264
        $response = new Response();
265
        $response->setStatusCode($status);
266
        $response->setContent(json_encode($content));
267
268
        return $response;
269
    }
270
271
    /**
272
     * @param string     $msg
273
     * @param \Exception $exception
274
     *
275
     * @return Response
276
     */
277
    protected function handleException(string $msg, \Exception $exception): Response
278
    {
279
        $this->logger->error($msg, $this->getExceptionContext($exception));
280
281
        $status  = ResponseHeaders::HTTP_INTERNAL_SERVER_ERROR;
282
        $content = [
283
            'type'   => sprintf('%sinternal-server-error', $this->problemUrlBase),
0 ignored issues
show
Bug introduced by
The property problemUrlBase does not exist on AbterPhp\Framework\Http\Controllers\ApiAbstract. Did you mean problemBaseUrl?
Loading history...
284
            'title'  => 'Internal Server Error',
285
            'status' => $status,
286
            'detail' => $exception->getMessage(),
287
        ];
288
289
        $response = new Response();
290
        $response->setStatusCode($status);
291
        $response->setContent(json_encode($content));
292
293
        return $response;
294
    }
295
296
    /**
297
     * @param \Exception $exception
298
     *
299
     * @return array
300
     */
301
    protected function getExceptionContext(\Exception $exception): array
302
    {
303
        $result = [static::LOG_CONTEXT_EXCEPTION => $exception->getMessage()];
304
305
        $i = 1;
306
        while ($exception = $exception->getPrevious()) {
307
            $result[sprintf(static::LOG_PREVIOUS_EXCEPTION, $i++)] = $exception->getMessage();
308
        }
309
310
        return $result;
311
    }
312
313
    /**
314
     * @param IStringerEntity $entity
315
     *
316
     * @return Response
317
     */
318
    protected function handleGetSuccess(IStringerEntity $entity): Response
319
    {
320
        $response = new Response();
321
        $response->setStatusCode(ResponseHeaders::HTTP_OK);
322
        $response->setContent($entity->toJSON());
323
324
        return $response;
325
    }
326
327
    /**
328
     * @param array $entities
329
     * @param int   $total
330
     *
331
     * @return Response
332
     */
333
    protected function handleListSuccess(array $entities, int $total): Response
334
    {
335
        $data = [];
336
        foreach ($entities as $entity) {
337
            $data[] = $entity->toJSON();
338
        }
339
        $content = sprintf('{"total":%d,"data":[%s]}', $total, implode(',', $data));
340
341
        $response = new Response();
342
        $response->setStatusCode(ResponseHeaders::HTTP_OK);
343
        $response->setContent($content);
344
345
        return $response;
346
    }
347
348
    /**
349
     * @param IStringerEntity $entity
350
     *
351
     * @return Response
352
     */
353
    protected function handleCreateSuccess(IStringerEntity $entity): Response
354
    {
355
        $response = new Response();
356
        $response->setStatusCode(ResponseHeaders::HTTP_CREATED);
357
        $response->setContent($entity->toJSON());
358
359
        return $response;
360
    }
361
362
    /**
363
     * @param IStringerEntity $entity
364
     *
365
     * @return Response
366
     */
367
    protected function handleUpdateSuccess(IStringerEntity $entity): Response
368
    {
369
        $response = new Response();
370
        $response->setStatusCode(ResponseHeaders::HTTP_OK);
371
        $response->setContent($entity->toJSON());
372
373
        return $response;
374
    }
375
376
    /**
377
     * @return Response
378
     */
379
    protected function handleDeleteSuccess(): Response
380
    {
381
        $response = new Response();
382
        $response->setStatusCode(ResponseHeaders::HTTP_NO_CONTENT);
383
384
        return $response;
385
    }
386
387
    /**
388
     * @return Response
389
     */
390
    protected function handleNotFound(): Response
391
    {
392
        $response = new Response();
393
        $response->setStatusCode(ResponseHeaders::HTTP_NOT_FOUND);
394
395
        return $response;
396
    }
397
}
398