Completed
Push — master ( c77a0c...216818 )
by Neomerx
05:30
created

BaseController   F

Complexity

Total Complexity 48

Size/Duplication

Total Lines 684
Duplicated Lines 9.65 %

Coupling/Cohesion

Components 1
Dependencies 17

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 48
lcom 1
cbo 17
dl 66
loc 684
ccs 205
cts 205
cp 1
rs 2.9859
c 0
b 0
f 0

27 Methods

Rating   Name   Duplication   Size   Complexity  
A index() 0 20 2
A create() 0 14 1
A read() 0 21 2
A update() 17 17 2
A delete() 0 11 1
A readRelationship() 0 19 4
A readRelationshipIdentifiers() 0 19 2
A createApi() 0 8 1
A readJsonFromRequest() 0 16 3
B normalizeIndexValueOnUpdate() 0 30 4
A deleteInRelationship() 0 23 2
B updateInRelationship() 31 31 2
A createOnCreateValidator() 9 9 1
A createOnUpdateValidator() 9 9 1
A createJsonApiValidator() 0 10 1
A createQueryParser() 0 4 1
A configureOnIndexParser() 0 4 1
A configureOnReadParser() 0 4 1
A configureOnReadRelationshipParser() 0 6 1
A configureOnReadRelationshipIdentifiersParser() 0 6 1
A createParameterMapper() 0 11 1
C mapSchemeDataToModelData() 0 40 7
A readRelationshipData() 0 15 1
A createImpl() 0 16 1
A updateImpl() 0 21 1
A updateInRelationshipImpl() 0 23 2
A createMessageFormatter() 0 10 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like BaseController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use BaseController, and based on these observations, apply Extract Interface, too.

1
<?php namespace Limoncello\Flute\Http;
2
3
/**
4
 * Copyright 2015-2017 [email protected]
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
use Limoncello\Contracts\Application\ModelInterface;
20
use Limoncello\Contracts\Data\ModelSchemeInfoInterface;
21
use Limoncello\Contracts\Data\RelationshipTypes;
22
use Limoncello\Contracts\L10n\FormatterFactoryInterface;
23
use Limoncello\Contracts\L10n\FormatterInterface;
24
use Limoncello\Flute\Contracts\Api\CrudInterface;
25
use Limoncello\Flute\Contracts\FactoryInterface;
26
use Limoncello\Flute\Contracts\Http\ControllerInterface;
27
use Limoncello\Flute\Contracts\Http\Query\ParametersMapperInterface;
28
use Limoncello\Flute\Contracts\Http\Query\QueryParserInterface;
29
use Limoncello\Flute\Contracts\Models\PaginatedDataInterface;
30
use Limoncello\Flute\Contracts\Schema\SchemaInterface;
31
use Limoncello\Flute\Contracts\Validation\JsonApiValidatorFactoryInterface;
32
use Limoncello\Flute\Contracts\Validation\JsonApiValidatorInterface;
33
use Limoncello\Flute\Http\Traits\CreateResponsesTrait;
34
use Limoncello\Flute\L10n\Messages;
35
use Neomerx\JsonApi\Contracts\Document\DocumentInterface as DI;
36
use Neomerx\JsonApi\Exceptions\JsonApiException;
37
use Psr\Container\ContainerExceptionInterface;
38
use Psr\Container\ContainerInterface;
39
use Psr\Container\NotFoundExceptionInterface;
40
use Psr\Http\Message\ResponseInterface;
41
use Psr\Http\Message\ServerRequestInterface;
42
43
/**
44
 * @package Limoncello\Flute
45
 *
46
 * @SuppressWarnings(PHPMD.TooManyMethods)
47
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
48
 */
49
abstract class BaseController implements ControllerInterface
50
{
51
    use CreateResponsesTrait;
52
53
    /** API class name */
54
    const API_CLASS = null;
55
56
    /** JSON API Schema class name */
57
    const SCHEMA_CLASS = null;
58
59
    /** JSON API validation rules set class */
60
    const ON_CREATE_VALIDATION_RULES_SET_CLASS = null;
61
62
    /** JSON API validation rules set class */
63
    const ON_UPDATE_VALIDATION_RULES_SET_CLASS = null;
64
65
    /**
66
     * @inheritdoc
67
     */
68 12
    public static function index(
69
        array $routeParams,
70
        ContainerInterface $container,
71
        ServerRequestInterface $request
72
    ): ResponseInterface {
73
        // By default no filters, sorts or includes are allowed from query. You can override this method to change it.
74 12
        $parser = static::configureOnIndexParser(
75 12
            static::createQueryParser($container)
76 12
        )->parse($request->getQueryParams());
77 11
        $mapper = static::createParameterMapper($container);
78 11
        $api    = static::createApi($container);
79
80 11
        $models = $mapper->applyQueryParameters($parser, $api)->index();
81
82 11
        $responses = static::createResponses($container, $request, $parser->createEncodingParameters());
83 11
        $response  = ($models->getData()) === null ?
84 11
            $responses->getCodeResponse(404) : $responses->getContentResponse($models);
85
86 11
        return $response;
87
    }
88
89
    /**
90
     * @inheritdoc
91
     */
92 1
    public static function create(
93
        array $routeParams,
94
        ContainerInterface $container,
95
        ServerRequestInterface $request
96
    ): ResponseInterface {
97 1
        list ($index, $api) = static::createImpl($container, $request);
98
99 1
        $data = $api->read($index);
100 1
        assert(!($data instanceof PaginatedDataInterface));
101
102 1
        $response = static::createResponses($container, $request)->getCreatedResponse($data);
103
104 1
        return $response;
105
    }
106
107
    /**
108
     * @inheritdoc
109
     */
110 1
    public static function read(
111
        array $routeParams,
112
        ContainerInterface $container,
113
        ServerRequestInterface $request
114
    ): ResponseInterface {
115
        // By default no filters, sorts or includes are allowed from query. You can override this method to change it.
116 1
        $parser = static::configureOnReadParser(
117 1
            static::createQueryParser($container)
118 1
        )->parse($request->getQueryParams());
119 1
        $mapper = static::createParameterMapper($container);
120
121 1
        $index     = $routeParams[static::ROUTE_KEY_INDEX];
122 1
        $modelData = $mapper->applyQueryParameters($parser, static::createApi($container))->read($index);
123 1
        assert(!($modelData instanceof PaginatedDataInterface));
124
125 1
        $responses = static::createResponses($container, $request, $parser->createEncodingParameters());
126 1
        $response  = $modelData === null ?
127 1
            $responses->getCodeResponse(404) : $responses->getContentResponse($modelData);
128
129 1
        return $response;
130
    }
131
132
    /**
133
     * @inheritdoc
134
     */
135 5 View Code Duplication
    public static function update(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
136
        array $routeParams,
137
        ContainerInterface $container,
138
        ServerRequestInterface $request
139
    ): ResponseInterface {
140 5
        list ($updated, $index, $api) = static::updateImpl($routeParams, $container, $request);
141
142 2
        $responses = static::createResponses($container, $request);
143 2
        if ($updated > 0) {
144 1
            $modelData = $api->read($index);
145 1
            assert(!($modelData instanceof PaginatedDataInterface));
146
147 1
            return $responses->getContentResponse($modelData);
148
        }
149
150 1
        return $responses->getCodeResponse(404);
151
    }
152
153
    /**
154
     * @inheritdoc
155
     */
156 1
    public static function delete(
157
        array $routeParams,
158
        ContainerInterface $container,
159
        ServerRequestInterface $request
160
    ): ResponseInterface {
161 1
        static::createApi($container)->remove($routeParams[static::ROUTE_KEY_INDEX]);
162
163 1
        $response = static::createResponses($container, $request)->getCodeResponse(204);
164
165 1
        return $response;
166
    }
167
168
    /**
169
     * @param string                 $index
170
     * @param string                 $relationshipName
171
     * @param ContainerInterface     $container
172
     * @param ServerRequestInterface $request
173
     *
174
     * @return ResponseInterface
175
     *
176
     * @throws ContainerExceptionInterface
177
     * @throws NotFoundExceptionInterface
178
     */
179 2
    protected static function readRelationship(
180
        string $index,
181
        string $relationshipName,
182
        ContainerInterface $container,
183
        ServerRequestInterface $request
184
    ): ResponseInterface {
185
        // By default no filters, sorts or includes are allowed from query. You can override this method to change it.
186 2
        $parser = static::configureOnReadRelationshipParser(
187 2
            $relationshipName,
188 2
            static::createQueryParser($container)
189 2
        )->parse($request->getQueryParams());
190
191 2
        $relData   = static::readRelationshipData($index, $relationshipName, $container, $parser);
0 ignored issues
show
Bug introduced by
Since readRelationshipData() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of readRelationshipData() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
192 2
        $responses = static::createResponses($container, $request, $parser->createEncodingParameters());
193 2
        $response  = $relData === null || ($relData instanceof PaginatedDataInterface && $relData->getData() === null) ?
194 2
            $responses->getCodeResponse(404) : $responses->getContentResponse($relData);
195
196 2
        return $response;
197
    }
198
199
    /**
200
     * @param string                 $index
201
     * @param string                 $relationshipName
202
     * @param ContainerInterface     $container
203
     * @param ServerRequestInterface $request
204
     *
205
     * @return ResponseInterface
206
     *
207
     * @throws ContainerExceptionInterface
208
     * @throws NotFoundExceptionInterface
209
     */
210 1
    protected static function readRelationshipIdentifiers(
211
        string $index,
212
        string $relationshipName,
213
        ContainerInterface $container,
214
        ServerRequestInterface $request
215
    ): ResponseInterface {
216
        // By default no filters, sorts or includes are allowed from query. You can override this method to change it.
217 1
        $parser = static::configureOnReadRelationshipIdentifiersParser(
218 1
            $relationshipName,
219 1
            static::createQueryParser($container)
220 1
        )->parse($request->getQueryParams());
221
222 1
        $relData   = static::readRelationshipData($index, $relationshipName, $container, $parser);
0 ignored issues
show
Bug introduced by
Since readRelationshipData() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of readRelationshipData() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
223 1
        $responses = static::createResponses($container, $request, $parser->createEncodingParameters());
224 1
        $response  = $relData->getData() === null ?
225 1
            $responses->getCodeResponse(404) : $responses->getIdentifiersResponse($relData);
226
227 1
        return $response;
228
    }
229
230
    /**
231
     * @param ContainerInterface $container
232
     * @param string|null        $class
233
     *
234
     * @return CrudInterface
235
     *
236
     * @throws ContainerExceptionInterface
237
     * @throws NotFoundExceptionInterface
238
     */
239 22
    protected static function createApi(ContainerInterface $container, string $class = null): CrudInterface
240
    {
241
        /** @var FactoryInterface $factory */
242 22
        $factory = $container->get(FactoryInterface::class);
243 22
        $api     = $factory->createApi($class ?? static::API_CLASS);
244
245 22
        return $api;
246
    }
247
248
    /**
249
     * @param ContainerInterface     $container
250
     * @param ServerRequestInterface $request
251
     *
252
     * @return array
253
     *
254
     * @throws ContainerExceptionInterface
255
     * @throws NotFoundExceptionInterface
256
     */
257 8
    protected static function readJsonFromRequest(ContainerInterface $container, ServerRequestInterface $request): array
258
    {
259 8
        $body = (string)$request->getBody();
260 8
        if (empty($body) === true || ($json = json_decode($body, true)) === null) {
261
            /** @var FactoryInterface $factory */
262 1
            $factory = $container->get(FactoryInterface::class);
263 1
            $errors  = $factory->createErrorCollection();
264 1
            $errors->addDataError(
265 1
                static::createMessageFormatter($container)->formatMessage(Messages::MSG_ERR_INVALID_ELEMENT)
266
            );
267
268 1
            throw new JsonApiException($errors);
269
        }
270
271 7
        return $json;
272
    }
273
274
    /**
275
     * @param array              $routeParams
276
     * @param ContainerInterface $container
277
     * @param array              $jsonData
278
     *
279
     * @return array
280
     * @SuppressWarnings(PHPMD.ElseExpression)
281
     *
282
     * @throws ContainerExceptionInterface
283
     * @throws NotFoundExceptionInterface
284
     */
285 4
    protected static function normalizeIndexValueOnUpdate(
286
        array $routeParams,
287
        ContainerInterface $container,
288
        array $jsonData
289
    ): array {
290
        // check that index in data and URL are identical
291 4
        $index         = $routeParams[static::ROUTE_KEY_INDEX];
292 4
        $dataSection   = null;
293
        $hasIndexValue =
294 4
            array_key_exists(DI::KEYWORD_DATA, $jsonData) &&
295 4
            array_key_exists(DI::KEYWORD_ID, ($dataSection = $jsonData[DI::KEYWORD_DATA]));
296 4
        if ($hasIndexValue === true) {
297 3
            assert($dataSection !== null);
298 3
            if ($dataSection[DI::KEYWORD_ID] !== $index) {
299
                /** @var FactoryInterface $factory */
300 1
                $factory = $container->get(FactoryInterface::class);
301 1
                $errors  = $factory->createErrorCollection();
302 1
                $errors->addDataIdError(
303 1
                    static::createMessageFormatter($container)->formatMessage(Messages::MSG_ERR_INVALID_ELEMENT)
304
                );
305
306 3
                throw new JsonApiException($errors);
307
            }
308
        } else {
309
            // put the index to data for our convenience
310 1
            $jsonData[DI::KEYWORD_DATA][DI::KEYWORD_ID] = $index;
311
        }
312
313 3
        return $jsonData;
314
    }
315
316
    /**
317
     * @param int|string             $parentIndex
318
     * @param string                 $relationshipName
319
     * @param int|string             $childIndex
320
     * @param string                 $childApiClass
321
     * @param ContainerInterface     $container
322
     * @param ServerRequestInterface $request
323
     *
324
     * @return ResponseInterface
325
     *
326
     * @throws ContainerExceptionInterface
327
     * @throws NotFoundExceptionInterface
328
     */
329 1
    protected static function deleteInRelationship(
330
        $parentIndex,
331
        string $relationshipName,
332
        $childIndex,
333
        string $childApiClass,
334
        ContainerInterface $container,
335
        ServerRequestInterface $request
336
    ): ResponseInterface {
337
        /** @var SchemaInterface $schemaClass */
338 1
        $schemaClass  = static::SCHEMA_CLASS;
339 1
        $modelRelName = $schemaClass::getRelationshipMapping($relationshipName);
340 1
        $hasChild     = static::createApi($container)->hasInRelationship($parentIndex, $modelRelName, $childIndex);
341 1
        if ($hasChild === false) {
342 1
            return static::createResponses($container, $request)->getCodeResponse(404);
343
        }
344
345 1
        $childApi = static::createApi($container, $childApiClass);
346
347 1
        $childApi->remove($childIndex);
348 1
        $response = static::createResponses($container, $request)->getCodeResponse(204);
349
350 1
        return $response;
351
    }
352
353
    /** @noinspection PhpTooManyParametersInspection
354
     * @param int|string             $parentIndex
355
     * @param string                 $relationshipName
356
     * @param int|string             $childIndex
357
     * @param array                  $attributes
358
     * @param array                  $toMany
359
     * @param string                 $childApiClass
360
     * @param ContainerInterface     $container
361
     * @param ServerRequestInterface $request
362
     *
363
     * @return ResponseInterface
364
     *
365
     * @throws ContainerExceptionInterface
366
     * @throws NotFoundExceptionInterface
367
     */
368 2 View Code Duplication
    protected static function updateInRelationship(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
369
        $parentIndex,
370
        string $relationshipName,
371
        $childIndex,
372
        array $attributes,
373
        array $toMany,
374
        string $childApiClass,
375
        ContainerInterface $container,
376
        ServerRequestInterface $request
377
    ): ResponseInterface {
378
        /** @var CrudInterface $childApi */
379 2
        list ($updated, $childApi) = static::updateInRelationshipImpl(
0 ignored issues
show
Bug introduced by
Since updateInRelationshipImpl() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of updateInRelationshipImpl() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
380 2
            $parentIndex,
381 2
            $relationshipName,
382 2
            $childIndex,
383 2
            $attributes,
384 2
            $toMany,
385 2
            $childApiClass,
386 2
            $container
387
        );
388
389 2
        $responses = static::createResponses($container, $request);
390 2
        if ($updated > 0) {
391 1
            $modelData = $childApi->read($childIndex);
392 1
            assert(!($modelData instanceof PaginatedDataInterface));
393
394 1
            return $responses->getContentResponse($modelData);
395
        }
396
397 1
        return $responses->getCodeResponse(404);
398
    }
399
400
    /**
401
     * @param ContainerInterface $container
402
     *
403
     * @return JsonApiValidatorInterface
404
     *
405
     * @throws ContainerExceptionInterface
406
     * @throws NotFoundExceptionInterface
407
     */
408 1 View Code Duplication
    protected static function createOnCreateValidator(ContainerInterface $container): JsonApiValidatorInterface
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
409
    {
410 1
        assert(
411 1
            empty(static::ON_CREATE_VALIDATION_RULES_SET_CLASS) === false,
412 1
            'Validation rules set should be defined for class ' . static::class . '.'
413
        );
414
415 1
        return static::createJsonApiValidator($container, static::ON_CREATE_VALIDATION_RULES_SET_CLASS);
416
    }
417
418
    /**
419
     * @param ContainerInterface $container
420
     *
421
     * @return JsonApiValidatorInterface
422
     *
423
     * @throws ContainerExceptionInterface
424
     * @throws NotFoundExceptionInterface
425
     */
426 3 View Code Duplication
    protected static function createOnUpdateValidator(ContainerInterface $container): JsonApiValidatorInterface
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
427
    {
428 3
        assert(
429 3
            empty(static::ON_UPDATE_VALIDATION_RULES_SET_CLASS) === false,
430 3
            'Validation rules set should be defined for class ' . static::class . '.'
431
        );
432
433 3
        return static::createJsonApiValidator($container, static::ON_UPDATE_VALIDATION_RULES_SET_CLASS);
434
    }
435
436
    /**
437
     * @param ContainerInterface $container
438
     * @param string             $rulesSetClass
439
     *
440
     * @return JsonApiValidatorInterface
441
     *
442
     * @throws ContainerExceptionInterface
443
     * @throws NotFoundExceptionInterface
444
     */
445 6
    protected static function createJsonApiValidator(
446
        ContainerInterface $container,
447
        string $rulesSetClass
448
    ): JsonApiValidatorInterface {
449
        /** @var JsonApiValidatorFactoryInterface $validatorFactory */
450 6
        $validatorFactory = $container->get(JsonApiValidatorFactoryInterface::class);
451 6
        $validator        = $validatorFactory->createValidator($rulesSetClass);
452
453 6
        return $validator;
454
    }
455
456
    /**
457
     * @param ContainerInterface $container
458
     *
459
     * @return QueryParserInterface
460
     *
461
     * @throws ContainerExceptionInterface
462
     * @throws NotFoundExceptionInterface
463
     */
464 16
    protected static function createQueryParser(ContainerInterface $container): QueryParserInterface
465
    {
466 16
        return $container->get(QueryParserInterface::class);
467
    }
468
469
    /**
470
     * @param QueryParserInterface $parser
471
     *
472
     * @return QueryParserInterface
473
     */
474 12
    protected static function configureOnIndexParser(QueryParserInterface $parser): QueryParserInterface
475
    {
476 12
        return $parser;
477
    }
478
479
    /**
480
     * @param QueryParserInterface $parser
481
     *
482
     * @return QueryParserInterface
483
     */
484 1
    protected static function configureOnReadParser(QueryParserInterface $parser): QueryParserInterface
485
    {
486 1
        return $parser;
487
    }
488
489
    /**
490
     * @param string               $name
491
     * @param QueryParserInterface $parser
492
     *
493
     * @return QueryParserInterface
494
     *
495
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
496
     */
497 2
    protected static function configureOnReadRelationshipParser(
498
        /** @noinspection PhpUnusedParameterInspection */ string $name,
0 ignored issues
show
Unused Code introduced by
The parameter $name is not used and could be removed.

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

Loading history...
499
        QueryParserInterface $parser
500
    ): QueryParserInterface {
501 2
        return $parser;
502
    }
503
504
    /**
505
     * @param string               $name
506
     * @param QueryParserInterface $parser
507
     *
508
     * @return QueryParserInterface
509
     *
510
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
511
     */
512 1
    protected static function configureOnReadRelationshipIdentifiersParser(
513
        /** @noinspection PhpUnusedParameterInspection */ string $name,
0 ignored issues
show
Unused Code introduced by
The parameter $name is not used and could be removed.

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

Loading history...
514
        QueryParserInterface $parser
515
    ): QueryParserInterface {
516 1
        return $parser;
517
    }
518
519
    /**
520
     * @param ContainerInterface $container
521
     *
522
     * @return ParametersMapperInterface
523
     *
524
     * @throws ContainerExceptionInterface
525
     * @throws NotFoundExceptionInterface
526
     */
527 15
    protected static function createParameterMapper(ContainerInterface $container): ParametersMapperInterface
528
    {
529
        /** @var SchemaInterface $schemaClass */
530 15
        $schemaClass = static::SCHEMA_CLASS;
531
532
        /** @var ParametersMapperInterface $mapper */
533 15
        $mapper = $container->get(ParametersMapperInterface::class);
534 15
        $mapper->selectRootSchemeByResourceType($schemaClass::TYPE);
535
536 15
        return $mapper;
537
    }
538
539
    /**
540
     * @param ContainerInterface $container
541
     * @param array              $captures
542
     * @param string             $schemeClass
543
     *
544
     * @return array
545
     *
546
     * @throws ContainerExceptionInterface
547
     * @throws NotFoundExceptionInterface
548
     */
549 5
    protected static function mapSchemeDataToModelData(
550
        ContainerInterface $container,
551
        array $captures,
552
        string $schemeClass
553
    ): array {
554 5
        assert(in_array(SchemaInterface::class, class_implements($schemeClass)));
555
        /** @var SchemaInterface $schemeClass */
556
557 5
        $modelClass = $schemeClass::MODEL;
558 5
        assert(in_array(ModelInterface::class, class_implements($modelClass)));
559
        /** @var ModelInterface $modelClass */
560
561
        /** @var ModelSchemeInfoInterface $schemeInfo */
562 5
        $schemeInfo = $container->get(ModelSchemeInfoInterface::class);
563
564 5
        $index         = null;
565 5
        $fields        = [];
566 5
        $toManyIndexes = [];
567 5
        foreach ($captures as $name => $value) {
568 5
            if ($name === DI::KEYWORD_ID) {
569 5
                $index = $value;
570 5
            } elseif ($schemeClass::hasAttributeMapping($name) === true) {
571 5
                $fieldName          = $schemeClass::getAttributeMapping($name);
572 5
                $fields[$fieldName] = $value;
573 5
            } elseif ($schemeClass::hasRelationshipMapping($name) === true) {
574 2
                $modelRelName = $schemeClass::getRelationshipMapping($name);
575 2
                $relType      = $schemeInfo->getRelationshipType($modelClass, $modelRelName);
576 2
                if ($relType === RelationshipTypes::BELONGS_TO) {
577 2
                    $fkName          = $schemeInfo->getForeignKey($modelClass, $modelRelName);
578 2
                    $fields[$fkName] = $value;
579 2
                } elseif ($relType === RelationshipTypes::BELONGS_TO_MANY) {
580 5
                    $toManyIndexes[$modelRelName] = $value;
581
                }
582
            }
583
        }
584
585 5
        $result = [$index, $fields, $toManyIndexes];
586
587 5
        return $result;
588
    }
589
590
    /**
591
     * @param string               $index
592
     * @param string               $relationshipName
593
     * @param ContainerInterface   $container
594
     * @param QueryParserInterface $parser
595
     *
596
     * @return PaginatedDataInterface|mixed|null
597
     *
598
     * @throws ContainerExceptionInterface
599
     * @throws NotFoundExceptionInterface
600
     */
601 3
    private static function readRelationshipData(
602
        string $index,
603
        string $relationshipName,
604
        ContainerInterface $container,
605
        QueryParserInterface $parser
606
    ) {
607 3
        $mapper = static::createParameterMapper($container);
608 3
        $api    = static::createApi($container);
609
610
        $relData = $mapper
611 3
            ->applyQueryParameters($parser, $api)
612 3
            ->readRelationship($index, $relationshipName);
613
614 3
        return $relData;
615
    }
616
617
    /**
618
     * @param ContainerInterface     $container
619
     * @param ServerRequestInterface $request
620
     *
621
     * @return array
622
     *
623
     * @throws ContainerExceptionInterface
624
     * @throws NotFoundExceptionInterface
625
     */
626 1
    protected static function createImpl(
627
        ContainerInterface $container,
628
        ServerRequestInterface $request
629
    ): array {
630 1
        $jsonData  = static::readJsonFromRequest($container, $request);
631 1
        $validator = static::createOnCreateValidator($container);
632 1
        $captures  = $validator->assert($jsonData)->getJsonApiCaptures();
633
634
        list ($index, $attributes, $toMany) =
635 1
            static::mapSchemeDataToModelData($container, $captures, static::SCHEMA_CLASS);
636
637 1
        $api   = static::createApi($container);
638 1
        $index = $api->create($index, $attributes, $toMany);
639
640 1
        return [$index, $api];
641
    }
642
643
    /**
644
     * @param array                  $routeParams
645
     * @param ContainerInterface     $container
646
     * @param ServerRequestInterface $request
647
     *
648
     * @return array [int $updated, string $index, CrudInterface $api]
649
     *
650
     * @throws ContainerExceptionInterface
651
     * @throws NotFoundExceptionInterface
652
     */
653 5
    protected static function updateImpl(
654
        array $routeParams,
655
        ContainerInterface $container,
656
        ServerRequestInterface $request
657
    ): array {
658 5
        $jsonData  = static::normalizeIndexValueOnUpdate(
659 5
            $routeParams,
660 5
            $container,
661 5
            static::readJsonFromRequest($container, $request)
662
        );
663 3
        $validator = static::createOnUpdateValidator($container);
664 3
        $captures  = $validator->assert($jsonData)->getJsonApiCaptures();
665
666
        list ($index, $attributes, $toMany) =
667 2
            static::mapSchemeDataToModelData($container, $captures, static::SCHEMA_CLASS);
668 2
        $api = static::createApi($container);
669
670 2
        $updated = $api->update($index, $attributes, $toMany);
671
672 2
        return [$updated, $index, $api];
673
    }
674
675
    /**
676
     * @param                        $parentIndex
677
     * @param string                 $relationshipName
678
     * @param                        $childIndex
679
     * @param array                  $attributes
680
     * @param array                  $toMany
681
     * @param string                 $childApiClass
682
     * @param ContainerInterface     $container
683
     *
684
     * @return array
685
     *
686
     * @throws ContainerExceptionInterface
687
     * @throws NotFoundExceptionInterface
688
     */
689 2
    private static function updateInRelationshipImpl(
690
        $parentIndex,
691
        string $relationshipName,
692
        $childIndex,
693
        array $attributes,
694
        array $toMany,
695
        string $childApiClass,
696
        ContainerInterface $container
697
    ): array {
698
        /** @var SchemaInterface $schemaClass */
699 2
        $schemaClass  = static::SCHEMA_CLASS;
700 2
        $modelRelName = $schemaClass::getRelationshipMapping($relationshipName);
701 2
        $hasChild     = static::createApi($container)->hasInRelationship($parentIndex, $modelRelName, $childIndex);
702 2
        if ($hasChild === false) {
703 1
            return [0, null];
704
        }
705
706 1
        $childApi = static::createApi($container, $childApiClass);
707
708 1
        $updated = $childApi->update($childIndex, $attributes, $toMany);
0 ignored issues
show
Documentation introduced by
$attributes is of type array, but the function expects a object<Limoncello\Flute\Contracts\Api\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$toMany is of type array, but the function expects a object<Limoncello\Flute\Contracts\Api\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
709
710 1
        return [$updated, $childApi];
711
    }
712
713
    /**
714
     * @param ContainerInterface $container
715
     * @param string             $namespace
716
     *
717
     * @return FormatterInterface
718
     *
719
     * @throws ContainerExceptionInterface
720
     * @throws NotFoundExceptionInterface
721
     */
722 2
    protected static function createMessageFormatter(
723
        ContainerInterface $container,
724
        string $namespace = Messages::RESOURCES_NAMESPACE
725
    ): FormatterInterface {
726
        /** @var FormatterFactoryInterface $factory */
727 2
        $factory          = $container->get(FormatterFactoryInterface::class);
728 2
        $messageFormatter = $factory->createFormatter($namespace);
729
730 2
        return $messageFormatter;
731
    }
732
}
733