DefaultControllerMethodsTrait   B
last analyzed

Complexity

Total Complexity 52

Size/Duplication

Total Lines 848
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 23

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 52
lcom 1
cbo 23
dl 0
loc 848
ccs 238
cts 238
cp 1
rs 7.192
c 0
b 0
f 0

26 Methods

Rating   Name   Duplication   Size   Complexity  
A defaultIndexHandler() 0 19 2
A defaultReadHandler() 0 22 2
A defaultReadRelationshipWithClosureHandler() 0 23 4
A defaultReadRelationshipIdentifiersWithClosureHandler() 0 23 4
A defaultCreateHandler() 0 26 1
A defaultCreate() 0 34 2
A defaultCreateResponse() 0 16 2
A defaultUpdateHandler() 0 27 1
A defaultUpdate() 0 51 4
A defaultUpdateResponse() 0 17 3
A defaultDeleteHandler() 0 15 1
A defaultAddInRelationshipHandler() 0 33 1
A defaultDeleteInRelationshipHandler() 0 33 1
A defaultReplaceInRelationship() 0 36 2
A defaultCreateQueryParser() 0 12 1
A defaultCreateDataParser() 0 12 1
A defaultCreateFormValidator() 0 12 1
A defaultCreateParameterMapper() 0 15 1
A defaultApplyIncludesAndFieldSetsToEncoder() 0 12 3
A defaultCreateApi() 0 10 1
A defaultCreateResponses() 0 12 1
A readJsonFromRequest() 0 15 3
B mapSchemaDataToModelData() 0 36 7
A defaultGetResourceUrl() 0 12 1
A assertClassValueDefined() 0 4 1
A assertClassImplements() 0 7 1

How to fix   Complexity   

Complex Class

Complex classes like DefaultControllerMethodsTrait 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 DefaultControllerMethodsTrait, and based on these observations, apply Extract Interface, too.

1
<?php declare (strict_types = 1);
2
3
namespace Limoncello\Flute\Http\Traits;
4
5
/**
6
 * Copyright 2015-2019 [email protected]
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 * http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
21
use Closure;
22
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
23
use Limoncello\Contracts\Application\ModelInterface;
24
use Limoncello\Contracts\Data\ModelSchemaInfoInterface;
25
use Limoncello\Contracts\Data\RelationshipTypes;
26
use Limoncello\Contracts\L10n\FormatterFactoryInterface;
27
use Limoncello\Flute\Contracts\Api\CrudInterface;
28
use Limoncello\Flute\Contracts\Encoder\EncoderInterface;
29
use Limoncello\Flute\Contracts\FactoryInterface;
30
use Limoncello\Flute\Contracts\Http\Query\ParametersMapperInterface;
31
use Limoncello\Flute\Contracts\Models\PaginatedDataInterface;
32
use Limoncello\Flute\Contracts\Schema\JsonSchemasInterface;
33
use Limoncello\Flute\Contracts\Schema\SchemaInterface;
34
use Limoncello\Flute\Contracts\Validation\FormRulesInterface;
35
use Limoncello\Flute\Contracts\Validation\FormValidatorFactoryInterface;
36
use Limoncello\Flute\Contracts\Validation\FormValidatorInterface;
37
use Limoncello\Flute\Contracts\Validation\JsonApiDataParserInterface;
38
use Limoncello\Flute\Contracts\Validation\JsonApiDataRulesInterface;
39
use Limoncello\Flute\Contracts\Validation\JsonApiParserFactoryInterface;
40
use Limoncello\Flute\Contracts\Validation\JsonApiQueryParserInterface;
41
use Limoncello\Flute\Contracts\Validation\JsonApiQueryRulesInterface;
42
use Limoncello\Flute\Http\JsonApiResponse;
43
use Limoncello\Flute\Http\Responses;
44
use Limoncello\Flute\L10n\Messages;
45
use Limoncello\Flute\Validation\JsonApi\Rules\DefaultQueryValidationRules;
46
use Neomerx\JsonApi\Contracts\Http\Headers\MediaTypeInterface;
47
use Neomerx\JsonApi\Contracts\Http\ResponsesInterface;
48
use Neomerx\JsonApi\Contracts\Schema\DocumentInterface as DI;
49
use Neomerx\JsonApi\Exceptions\JsonApiException;
50
use Neomerx\JsonApi\Http\Headers\MediaType;
51
use Psr\Container\ContainerExceptionInterface;
52
use Psr\Container\ContainerInterface;
53
use Psr\Container\NotFoundExceptionInterface;
54
use Psr\Http\Message\ResponseInterface;
55
use Psr\Http\Message\UriInterface;
56
use function array_key_exists;
57
use function assert;
58
use function call_user_func;
59
use function class_implements;
60
use function is_string;
61
62
/**
63
 * @package Limoncello\Flute
64
 */
65
trait DefaultControllerMethodsTrait
66
{
67
    /** @noinspection PhpTooManyParametersInspection
68
     * @param array                       $queryParams
69
     * @param UriInterface                $requestUri
70
     * @param JsonApiQueryParserInterface $queryParser
71
     * @param ParametersMapperInterface   $mapper
72 12
     * @param CrudInterface               $crud
73
     * @param EncoderInterface            $encoder
74
     *
75
     * @return ResponseInterface
76
     */
77
    protected static function defaultIndexHandler(
78
        array $queryParams,
79
        UriInterface $requestUri,
80 12
        JsonApiQueryParserInterface $queryParser,
81
        ParametersMapperInterface $mapper,
82 11
        CrudInterface $crud,
83
        EncoderInterface $encoder
84 10
    ): ResponseInterface {
85 10
        $queryParser->parse(null, $queryParams);
86 10
87 10
        $models = $mapper->applyQueryParameters($queryParser, $crud)->index();
88
89 10
        static::defaultApplyIncludesAndFieldSetsToEncoder($queryParser, $encoder);
90
        $responses = static::defaultCreateResponses($requestUri, $encoder);
91
        $response  = ($models->getData()) === null ?
92
            $responses->getCodeResponse(404) : $responses->getContentResponse($models);
93
94
        return $response;
95
    }
96
97
    /** @noinspection PhpTooManyParametersInspection
98
     * @param string                      $index
99
     * @param array                       $queryParams
100
     * @param UriInterface                $requestUri
101
     * @param JsonApiQueryParserInterface $queryParser
102
     * @param ParametersMapperInterface   $mapper
103 1
     * @param CrudInterface               $crud
104
     * @param EncoderInterface            $encoder
105
     *
106
     * @return ResponseInterface
107
     */
108
    protected static function defaultReadHandler(
109
        string $index,
110
        array $queryParams,
111
        UriInterface $requestUri,
112 1
        JsonApiQueryParserInterface $queryParser,
113 1
        ParametersMapperInterface $mapper,
114
        CrudInterface $crud,
115 1
        EncoderInterface $encoder
116 1
    ): ResponseInterface {
117
        $queryParser->parse($index, $queryParams);
118 1
        $validatedIndex = (string)$queryParser->getIdentity();
119 1
120 1
        $model = $mapper->applyQueryParameters($queryParser, $crud)->read($validatedIndex);
121 1
        assert(!($model instanceof PaginatedDataInterface));
122
123 1
        static::defaultApplyIncludesAndFieldSetsToEncoder($queryParser, $encoder);
124
        $responses = static::defaultCreateResponses($requestUri, $encoder);
125
        $response  = $model === null ?
126
            $responses->getCodeResponse(404) : $responses->getContentResponse($model);
127
128
        return $response;
129
    }
130
131
    /** @noinspection PhpTooManyParametersInspection
132
     * @param string                      $index
133
     * @param Closure                     $apiHandler
134
     * @param array                       $queryParams
135
     * @param UriInterface                $requestUri
136
     * @param JsonApiQueryParserInterface $queryParser
137
     * @param ParametersMapperInterface   $mapper
138
     * @param CrudInterface               $crud
139
     * @param EncoderInterface            $encoder
140 2
     *
141
     * @return ResponseInterface
142
     *
143
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
144
     */
145
    protected static function defaultReadRelationshipWithClosureHandler(
146
        string $index,
147
        Closure $apiHandler,
148
        array $queryParams,
149
        UriInterface $requestUri,
150 2
        JsonApiQueryParserInterface $queryParser,
151 2
        ParametersMapperInterface $mapper,
152
        CrudInterface $crud,
153 2
        EncoderInterface $encoder
154
    ): ResponseInterface {
155 2
        $queryParser->parse($index, $queryParams);
156 2
        $mapper->applyQueryParameters($queryParser, $crud);
157
158 2
        $relData = call_user_func($apiHandler);
159 2
160
        static::defaultApplyIncludesAndFieldSetsToEncoder($queryParser, $encoder);
161 2
        $responses = static::defaultCreateResponses($requestUri, $encoder);
162
163
        $noData   = $relData === null || ($relData instanceof PaginatedDataInterface && $relData->getData() === null);
164
        $response = $noData === true ? $responses->getCodeResponse(404) : $responses->getContentResponse($relData);
165
166
        return $response;
167
    }
168
169
    /** @noinspection PhpTooManyParametersInspection
170
     * @param string                      $index
171
     * @param Closure                     $apiHandler
172
     * @param array                       $queryParams
173
     * @param UriInterface                $requestUri
174
     * @param JsonApiQueryParserInterface $queryParser
175
     * @param ParametersMapperInterface   $mapper
176
     * @param CrudInterface               $crud
177
     * @param EncoderInterface            $encoder
178 1
     *
179
     * @return ResponseInterface
180
     *
181
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
182
     */
183
    protected static function defaultReadRelationshipIdentifiersWithClosureHandler(
184
        string $index,
185
        Closure $apiHandler,
186
        array $queryParams,
187
        UriInterface $requestUri,
188 1
        JsonApiQueryParserInterface $queryParser,
189 1
        ParametersMapperInterface $mapper,
190
        CrudInterface $crud,
191 1
        EncoderInterface $encoder
192
    ): ResponseInterface {
193 1
        $queryParser->parse($index, $queryParams);
194 1
        $mapper->applyQueryParameters($queryParser, $crud);
195
196 1
        $relData = call_user_func($apiHandler);
197 1
198
        static::defaultApplyIncludesAndFieldSetsToEncoder($queryParser, $encoder);
199 1
        $responses = static::defaultCreateResponses($requestUri, $encoder);
200
201
        $noData   = $relData === null || ($relData instanceof PaginatedDataInterface && $relData->getData() === null);
202
        $response = $noData === true ? $responses->getCodeResponse(404) : $responses->getIdentifiersResponse($relData);
203
204
        return $response;
205
    }
206
207
    /** @noinspection PhpTooManyParametersInspection
208
     * @param UriInterface               $requestUri
209
     * @param string                     $requestBody
210
     * @param string                     $schemaClass
211
     * @param ModelSchemaInfoInterface   $schemaInfo
212
     * @param JsonApiDataParserInterface $parser
213
     * @param CrudInterface              $crud
214
     * @param JsonSchemasInterface       $jsonSchemas
215
     * @param EncoderInterface           $encoder
216
     * @param FactoryInterface           $errorFactory
217
     * @param FormatterFactoryInterface  $formatterFactory
218 2
     *
219
     * @return ResponseInterface
220
     *
221
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
222
     */
223
    protected static function defaultCreateHandler(
224
        UriInterface $requestUri,
225
        string $requestBody,
226
        string $schemaClass,
227
        ModelSchemaInfoInterface $schemaInfo,
228
        JsonApiDataParserInterface $parser,
229
        CrudInterface $crud,
230
        JsonSchemasInterface $jsonSchemas,
231
        EncoderInterface $encoder,
232 2
        FactoryInterface $errorFactory,
233 2
        FormatterFactoryInterface $formatterFactory
234 2
    ): ResponseInterface {
235 2
        // some of the users want to reuse default `create` but have a custom part for responses
236 2
        // to meet this requirement it is split into two parts.
237 2
        $index = static::defaultCreate(
238 2
            $requestBody,
239 2
            $schemaClass,
240
            $schemaInfo,
241
            $parser,
242 1
            $crud,
243
            $errorFactory,
244
            $formatterFactory
245
        );
246
247
        return static::defaultCreateResponse($index, $requestUri, $crud, $jsonSchemas, $encoder);
248
    }
249
250
    /** @noinspection PhpTooManyParametersInspection
251
     * @param string                     $requestBody
252
     * @param string                     $schemaClass
253
     * @param ModelSchemaInfoInterface   $schemaInfo
254
     * @param JsonApiDataParserInterface $parser
255
     * @param CrudInterface              $crud
256 2
     * @param FactoryInterface           $errorFactory
257
     * @param FormatterFactoryInterface  $formatterFactory
258
     *
259
     * @return ResponseInterface
0 ignored issues
show
Documentation introduced by
Should the return type not be string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
260
     */
261
    protected static function defaultCreate(
262
        string $requestBody,
263
        string $schemaClass,
264
        ModelSchemaInfoInterface $schemaInfo,
265 2
        JsonApiDataParserInterface $parser,
266 2
        CrudInterface $crud,
267 2
        FactoryInterface $errorFactory,
268 2
        FormatterFactoryInterface $formatterFactory
269
    ): string {
270
        $jsonData = static::readJsonFromRequest(
271 2
            $requestBody,
272
            $errorFactory,
273 2
            $formatterFactory
274
        );
275
276 2
        $captures = $parser->assert($jsonData)->getCaptures();
277 1
278 1
        list ($index, $attributes, $toMany) = static::mapSchemaDataToModelData($captures, $schemaClass, $schemaInfo);
279 1
280 1
        try {
281 1
            $index = $crud->create($index, $attributes, $toMany);
282 1
        } /** @noinspection PhpRedundantCatchClauseInspection */ catch (UniqueConstraintViolationException $exception) {
283 1
            $errors    = $errorFactory->createErrorCollection();
284
            $formatter = $formatterFactory->createFormatter(Messages::NAMESPACE_NAME);
285 1
            $title     = $formatter->formatMessage(Messages::MSG_ERR_CANNOT_CREATE_NON_UNIQUE_RESOURCE);
286
            $details   = null;
287
            $errorCode = JsonApiResponse::HTTP_CONFLICT;
288 1
            $errors->addDataError($title, $details, (string)$errorCode);
289
290
            throw new JsonApiException($errors);
0 ignored issues
show
Documentation introduced by
$errors is of type object<Neomerx\JsonApi\Schema\ErrorCollection>, but the function expects a object<Neomerx\JsonApi\C...pi\Exceptions\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...
291
        }
292
293
        return $index;
294
    }
295
296
    /**
297
     * @param string               $index
298
     * @param UriInterface         $requestUri
299
     * @param CrudInterface        $crud
300 1
     * @param JsonSchemasInterface $jsonSchemas
301
     * @param EncoderInterface     $encoder
302
     *
303
     * @return ResponseInterface
304
     */
305
    protected static function defaultCreateResponse(
306
        string $index,
307 1
        UriInterface $requestUri,
308 1
        CrudInterface $crud,
309
        JsonSchemasInterface $jsonSchemas,
310 1
        EncoderInterface $encoder
311 1
    ): ResponseInterface {
312 1
        $model = $crud->read($index);
313
        assert($model !== null && !($model instanceof PaginatedDataInterface));
314 1
315
        $responses = static::defaultCreateResponses($requestUri, $encoder);
316
        $fullUrl   = static::defaultGetResourceUrl($model, $requestUri, $jsonSchemas);
317
        $response  = $responses->getCreatedResponse($model, $fullUrl);
318
319
        return $response;
320
    }
321
322
    /** @noinspection PhpTooManyParametersInspection
323
     * @param string                     $index
324
     * @param UriInterface               $requestUri
325
     * @param string                     $requestBody
326
     * @param string                     $schemaClass
327
     * @param ModelSchemaInfoInterface   $schemaInfo
328
     * @param JsonApiDataParserInterface $parser
329
     * @param CrudInterface              $crud
330
     * @param EncoderInterface           $encoder
331
     * @param FactoryInterface           $errorFactory
332
     * @param FormatterFactoryInterface  $formatterFactory
333 6
     *
334
     * @return ResponseInterface
335
     *
336
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
337
     */
338
    protected static function defaultUpdateHandler(
339
        string $index,
340
        UriInterface $requestUri,
341
        string $requestBody,
342
        string $schemaClass,
343
        ModelSchemaInfoInterface $schemaInfo,
344
        JsonApiDataParserInterface $parser,
345
        CrudInterface $crud,
346
        EncoderInterface $encoder,
347 6
        FactoryInterface $errorFactory,
348 6
        FormatterFactoryInterface $formatterFactory
349 6
    ): ResponseInterface {
350 6
        // some of the users want to reuse default `update` but have a custom part for responses
351 6
        // to meet this requirement it is split into two parts.
352 6
        $updated = static::defaultUpdate(
353 6
            $index,
354 6
            $requestBody,
355 6
            $schemaClass,
356
            $schemaInfo,
357
            $parser,
358 2
            $crud,
359
            $errorFactory,
360
            $formatterFactory
361
        );
362
363
        return static::defaultUpdateResponse($updated, $index, $requestUri, $crud, $encoder);
364
    }
365
366
    /** @noinspection PhpTooManyParametersInspection
367
     * @param string                     $index
368
     * @param string                     $requestBody
369
     * @param string                     $schemaClass
370
     * @param ModelSchemaInfoInterface   $schemaInfo
371
     * @param JsonApiDataParserInterface $parser
372
     * @param CrudInterface              $crud
373
     * @param FactoryInterface           $errorFactory
374
     * @param FormatterFactoryInterface  $formatterFactory
375
     *
376 6
     * @return int
377
     *
378
     * @SuppressWarnings(PHPMD.ElseExpression)
379
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
380
     */
381
    protected static function defaultUpdate(
382
        string $index,
383
        string $requestBody,
384
        string $schemaClass,
385
        ModelSchemaInfoInterface $schemaInfo,
386 6
        JsonApiDataParserInterface $parser,
387 6
        CrudInterface $crud,
388 6
        FactoryInterface $errorFactory,
389 6
        FormatterFactoryInterface $formatterFactory
390
    ): int {
391
        $jsonData = static::readJsonFromRequest(
392
            $requestBody,
393 5
            $errorFactory,
394 5
            $formatterFactory
395 4
        );
396 1
397 1
        // check that index in data and URL are identical
398 1
        $indexValue = $jsonData[DI::KEYWORD_DATA][DI::KEYWORD_ID] ?? null;
399
        if (empty($indexValue) === false) {
400 4
            if ($indexValue !== $index) {
401
                $errors    = $errorFactory->createErrorCollection();
402
                $formatter = $formatterFactory->createFormatter(Messages::NAMESPACE_NAME);
403
                $errors->addDataIdError($formatter->formatMessage(Messages::MSG_ERR_INVALID_ARGUMENT));
404 1
405
                throw new JsonApiException($errors);
0 ignored issues
show
Documentation introduced by
$errors is of type object<Neomerx\JsonApi\Schema\ErrorCollection>, but the function expects a object<Neomerx\JsonApi\C...pi\Exceptions\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...
406
            }
407 4
        } else {
408
            // put the index to data for our convenience
409 3
            $jsonData[DI::KEYWORD_DATA][DI::KEYWORD_ID] = $index;
410
        }
411
        // validate the data
412 3
        $captures = $parser->assert($jsonData)->getCaptures();
413 1
414 1
        list ($index, $attributes, $toMany) = static::mapSchemaDataToModelData($captures, $schemaClass, $schemaInfo);
415 1
416
        try {
417 1
            $updated = $crud->update((string)$index, $attributes, $toMany);
418 1
        } /** @noinspection PhpRedundantCatchClauseInspection */ catch (UniqueConstraintViolationException $exception) {
419 1
            $errors    = $errorFactory->createErrorCollection();
420 1
            $formatter = $formatterFactory->createFormatter(Messages::NAMESPACE_NAME);
421
            $title     = $formatter
422 1
                ->formatMessage(Messages::MSG_ERR_CANNOT_UPDATE_WITH_UNIQUE_CONSTRAINT_VIOLATION);
423
            $details   = null;
424
            $errorCode = JsonApiResponse::HTTP_CONFLICT;
425 2
            $errors->addDataError($title, $details, (string)$errorCode);
426
427
            throw new JsonApiException($errors);
0 ignored issues
show
Documentation introduced by
$errors is of type object<Neomerx\JsonApi\Schema\ErrorCollection>, but the function expects a object<Neomerx\JsonApi\C...pi\Exceptions\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...
428
        }
429
430
        return $updated;
431
    }
432
433
    /**
434
     * @param int              $updated
435
     * @param string           $index
436
     * @param UriInterface     $requestUri
437
     * @param CrudInterface    $crud
438
     * @param EncoderInterface $encoder
439 3
     *
440
     * @return ResponseInterface
441
     *
442
     * @SuppressWarnings(PHPMD.ElseExpression)
443
     */
444
    protected static function defaultUpdateResponse(
445
        int $updated,
446 3
        string $index,
447 3
        UriInterface $requestUri,
448 2
        CrudInterface $crud,
449 2
        EncoderInterface $encoder
450
    ): ResponseInterface {
451 1
        $responses = static::defaultCreateResponses($requestUri, $encoder);
452
        if ($updated > 0 && ($model = $crud->read($index)) !== null) {
453
            assert(!($model instanceof PaginatedDataInterface));
454 3
            $response = $responses->getContentResponse($model);
455
        } else {
456
            $response = $responses->getCodeResponse(404);
457
        }
458
459
        return $response;
460
    }
461
462
    /**
463
     * @param string                      $index
464
     * @param UriInterface                $requestUri
465
     * @param JsonApiQueryParserInterface $queryParser
466 1
     * @param CrudInterface               $crud
467
     * @param EncoderInterface            $encoder
468
     *
469
     * @return ResponseInterface
470
     */
471
    protected static function defaultDeleteHandler(
472
        string $index,
473 1
        UriInterface $requestUri,
474 1
        JsonApiQueryParserInterface $queryParser,
475
        CrudInterface $crud,
476 1
        EncoderInterface $encoder
477 1
    ): ResponseInterface {
478
        $validatedIndex = (string)$queryParser->parse($index)->getIdentity();
479 1
        $crud->remove($validatedIndex);
480
481
        $responses = static::defaultCreateResponses($requestUri, $encoder);
482
        $response  = $responses->getCodeResponse(204);
483
484
        return $response;
485
    }
486
487
    /** @noinspection PhpTooManyParametersInspection
488
     * @param string                      $index
489
     * @param string                      $jsonRelName
490
     * @param string                      $modelRelName
491
     * @param UriInterface                $requestUri
492
     * @param string                      $requestBody
493
     * @param string                      $schemaClass
494
     * @param ModelSchemaInfoInterface    $schemaInfo
495
     * @param JsonApiQueryParserInterface $queryParser
496
     * @param JsonApiDataParserInterface  $dataValidator
497
     * @param CrudInterface               $parentCrud
498
     * @param EncoderInterface            $encoder
499
     * @param FactoryInterface            $errorFactory
500
     * @param FormatterFactoryInterface   $formatterFactory
501 2
     *
502
     * @return ResponseInterface
503
     *
504
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
505
     */
506
    protected static function defaultAddInRelationshipHandler(
507
        string $index,
508
        string $jsonRelName,
509
        string $modelRelName,
510
        UriInterface $requestUri,
511
        string $requestBody,
512
        string $schemaClass,
513
        ModelSchemaInfoInterface $schemaInfo,
514
        JsonApiQueryParserInterface $queryParser,
515
        JsonApiDataParserInterface $dataValidator,
516
        CrudInterface $parentCrud,
517 2
        EncoderInterface $encoder,
518 2
        FactoryInterface $errorFactory,
519 2
        FormatterFactoryInterface $formatterFactory
520 2
    ): ResponseInterface {
521
        /** @var SchemaInterface $schemaClass */
522 2
        assert(array_key_exists(SchemaInterface::class, class_implements($schemaClass)) === true);
523 2
        $modelClass = $schemaClass::MODEL;
524 1
        assert($schemaInfo->hasRelationship($modelClass, $modelRelName));
525
        assert($schemaInfo->getRelationshipType($modelClass, $modelRelName) === RelationshipTypes::BELONGS_TO_MANY);
526 1
527 1
        $jsonData = static::readJsonFromRequest($requestBody, $errorFactory, $formatterFactory);
528
        $captures = $dataValidator->assertRelationship($index, $jsonRelName, $jsonData)->getCaptures();
529 1
        $relIds   = $captures[$jsonRelName];
530 1
531
        $validatedIndex = (string)$queryParser->parse($index)->getIdentity();
532 1
        $parentCrud->createInBelongsToManyRelationship($validatedIndex, $modelRelName, $relIds);
533
534
        $responses = static::defaultCreateResponses($requestUri, $encoder);
535
        $response  = $responses->getCodeResponse(204);
536
537
        return $response;
538
    }
539
540
    /** @noinspection PhpTooManyParametersInspection
541
     * @param string                      $index
542
     * @param string                      $jsonRelName
543
     * @param string                      $modelRelName
544
     * @param UriInterface                $requestUri
545
     * @param string                      $requestBody
546
     * @param string                      $schemaClass
547
     * @param ModelSchemaInfoInterface    $schemaInfo
548
     * @param JsonApiQueryParserInterface $queryParser
549
     * @param JsonApiDataParserInterface  $dataValidator
550
     * @param CrudInterface               $parentCrud
551
     * @param EncoderInterface            $encoder
552
     * @param FactoryInterface            $errorFactory
553
     * @param FormatterFactoryInterface   $formatterFactory
554 1
     *
555
     * @return ResponseInterface
556
     *
557
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
558
     */
559
    protected static function defaultDeleteInRelationshipHandler(
560
        string $index,
561
        string $jsonRelName,
562
        string $modelRelName,
563
        UriInterface $requestUri,
564
        string $requestBody,
565
        string $schemaClass,
566
        ModelSchemaInfoInterface $schemaInfo,
567
        JsonApiQueryParserInterface $queryParser,
568
        JsonApiDataParserInterface $dataValidator,
569
        CrudInterface $parentCrud,
570 1
        EncoderInterface $encoder,
571 1
        FactoryInterface $errorFactory,
572 1
        FormatterFactoryInterface $formatterFactory
573 1
    ): ResponseInterface {
574
        /** @var SchemaInterface $schemaClass */
575 1
        assert(array_key_exists(SchemaInterface::class, class_implements($schemaClass)) === true);
576 1
        $modelClass = $schemaClass::MODEL;
577 1
        assert($schemaInfo->hasRelationship($modelClass, $modelRelName));
578
        assert($schemaInfo->getRelationshipType($modelClass, $modelRelName) === RelationshipTypes::BELONGS_TO_MANY);
579 1
580 1
        $jsonData = static::readJsonFromRequest($requestBody, $errorFactory, $formatterFactory);
581
        $captures = $dataValidator->assertRelationship($index, $jsonRelName, $jsonData)->getCaptures();
582 1
        $relIds   = $captures[$jsonRelName];
583 1
584
        $validatedIndex = (string)$queryParser->parse($index)->getIdentity();
585 1
        $parentCrud->removeInBelongsToManyRelationship($validatedIndex, $modelRelName, $relIds);
586
587
        $responses = static::defaultCreateResponses($requestUri, $encoder);
588
        $response  = $responses->getCodeResponse(204);
589
590
        return $response;
591
    }
592
593
    /** @noinspection PhpTooManyParametersInspection
594
     * @param string                      $index
595
     * @param string                      $jsonRelName
596
     * @param string                      $modelRelName
597
     * @param UriInterface                $requestUri
598
     * @param string                      $requestBody
599
     * @param string                      $schemaClass
600
     * @param ModelSchemaInfoInterface    $schemaInfo
601
     * @param JsonApiQueryParserInterface $queryParser
602
     * @param JsonApiDataParserInterface  $dataValidator
603
     * @param CrudInterface               $crud
604
     * @param EncoderInterface            $encoder
605
     * @param FactoryInterface            $errorFactory
606
     * @param FormatterFactoryInterface   $formatterFactory
607 2
     *
608
     * @return ResponseInterface
609
     *
610
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
611
     */
612
    protected static function defaultReplaceInRelationship(
613
        string $index,
614
        string $jsonRelName,
615
        string $modelRelName,
616
        UriInterface $requestUri,
617
        string $requestBody,
618
        string $schemaClass,
619
        ModelSchemaInfoInterface $schemaInfo,
620
        JsonApiQueryParserInterface $queryParser,
621
        JsonApiDataParserInterface $dataValidator,
622
        CrudInterface $crud,
623 2
        EncoderInterface $encoder,
624 2
        FactoryInterface $errorFactory,
625 2
        FormatterFactoryInterface $formatterFactory
626 2
    ): ResponseInterface {
627 2
        /** @var SchemaInterface $schemaClass */
628 2
        assert(array_key_exists(SchemaInterface::class, class_implements($schemaClass)) === true);
629
        $modelClass = $schemaClass::MODEL;
630
        assert($schemaInfo->hasRelationship($modelClass, $modelRelName));
631 2
        assert(
632 2
            ($type =$schemaInfo->getRelationshipType($modelClass, $modelRelName)) === RelationshipTypes::BELONGS_TO ||
633
            $type === RelationshipTypes::BELONGS_TO_MANY
634
        );
635
636 1
        $jsonData = static::readJsonFromRequest($requestBody, $errorFactory, $formatterFactory);
637 1
        $captures = $dataValidator->assertRelationship($index, $jsonRelName, $jsonData)->getCaptures();
638
639 1
        // If we are here then we have something in 'data' section.
640
641 1
        $validatedIndex = (string)$queryParser->parse($index)->getIdentity();
642
        list (, $attributes, $toMany) = static::mapSchemaDataToModelData($captures, $schemaClass, $schemaInfo);
0 ignored issues
show
Documentation introduced by
$schemaClass is of type object<Limoncello\Flute\...Schema\SchemaInterface>, but the function expects a string.

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...
643
644
        $updated = $crud->update($validatedIndex, $attributes, $toMany);
645
646
        return static::defaultUpdateResponse($updated, $index, $requestUri, $crud, $encoder);
647
    }
648
649
    /**
650
     * @param ContainerInterface $container
651
     * @param string             $rulesClass
652
     *
653 22
     * @return JsonApiQueryParserInterface
654
     *
655
     * @throws ContainerExceptionInterface
656
     * @throws NotFoundExceptionInterface
657 22
     */
658
    protected static function defaultCreateQueryParser(
659
        ContainerInterface $container,
660 22
        string $rulesClass = DefaultQueryValidationRules::class
661 22
    ): JsonApiQueryParserInterface {
662
        static::assertClassImplements($rulesClass, JsonApiQueryRulesInterface::class);
663 22
664
        /** @var JsonApiParserFactoryInterface $factory */
665
        $factory = $container->get(JsonApiParserFactoryInterface::class);
666
        $parser  = $factory->createQueryParser($rulesClass);
667
668
        return $parser;
669
    }
670
671
    /**
672
     * @param ContainerInterface $container
673
     * @param string             $rulesClass
674
     *
675 13
     * @return JsonApiDataParserInterface
676
     *
677
     * @throws ContainerExceptionInterface
678
     * @throws NotFoundExceptionInterface
679 13
     */
680
    protected static function defaultCreateDataParser(
681
        ContainerInterface $container,
682 13
        string $rulesClass
683 13
    ): JsonApiDataParserInterface {
684
        static::assertClassImplements($rulesClass, JsonApiDataRulesInterface::class);
685 13
686
        /** @var JsonApiParserFactoryInterface $factory */
687
        $factory = $container->get(JsonApiParserFactoryInterface::class);
688
        $parser  = $factory->createDataParser($rulesClass);
689
690
        return $parser;
691
    }
692
693
    /**
694
     * @param ContainerInterface $container
695
     * @param string             $rulesClass
696
     *
697 1
     * @return FormValidatorInterface
698
     *
699
     * @throws ContainerExceptionInterface
700
     * @throws NotFoundExceptionInterface
701 1
     */
702
    protected static function defaultCreateFormValidator(
703
        ContainerInterface $container,
704 1
        string $rulesClass
705 1
    ): FormValidatorInterface {
706
        static::assertClassImplements($rulesClass, FormRulesInterface::class);
707 1
708
        /** @var FormValidatorFactoryInterface $factory */
709
        $factory   = $container->get(FormValidatorFactoryInterface::class);
710
        $validator = $factory->createValidator($rulesClass);
711
712
        return $validator;
713
    }
714
715
    /**
716
     * @param ContainerInterface $container
717
     * @param string             $schemaClass
718
     *
719 16
     * @return ParametersMapperInterface
720
     *
721
     * @throws ContainerExceptionInterface
722
     * @throws NotFoundExceptionInterface
723 16
     */
724
    protected static function defaultCreateParameterMapper(
725
        ContainerInterface $container,
726 16
        string $schemaClass
727
    ): ParametersMapperInterface {
728
        static::assertClassImplements($schemaClass, SchemaInterface::class);
729 16
730 16
        /** @var SchemaInterface $schemaClass */
731
        $jsonResourceType = $schemaClass::TYPE;
732 16
733
        /** @var ParametersMapperInterface $mapper */
734
        $mapper = $container->get(ParametersMapperInterface::class);
735
        $mapper->selectRootSchemaByResourceType($jsonResourceType);
736
737
        return $mapper;
738
    }
739
740
    /**
741 14
     * @param JsonApiQueryParserInterface $queryParser
742
     * @param EncoderInterface            $encoder
743
     *
744
     * @return void
745 14
     */
746 3
    protected static function defaultApplyIncludesAndFieldSetsToEncoder(
747 3
        JsonApiQueryParserInterface $queryParser,
748
        EncoderInterface $encoder
749 14
    ): void {
750 1
        if ($queryParser->hasIncludes() === true) {
751
            $paths = array_keys($queryParser->getIncludes());
752
            $encoder->withIncludedPaths($paths);
0 ignored issues
show
Documentation introduced by
$paths is of type array, but the function expects a object<Neomerx\JsonApi\C...racts\Encoder\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...
753
        }
754
        if ($queryParser->hasFields() === true) {
755
            $encoder->withFieldSets($queryParser->getFields());
756
        }
757
    }
758
759
    /**
760
     * @param ContainerInterface $container
761
     * @param string|null        $class
762
     *
763 30
     * @return CrudInterface
764
     *
765 30
     * @throws ContainerExceptionInterface
766
     * @throws NotFoundExceptionInterface
767
     */
768 30
    protected static function defaultCreateApi(ContainerInterface $container, string $class): CrudInterface
769 30
    {
770
        static::assertClassImplements($class, CrudInterface::class);
771 30
772
        /** @var FactoryInterface $factory */
773
        $factory = $container->get(FactoryInterface::class);
774
        $api     = $factory->createApi($class);
775
776
        return $api;
777
    }
778
779
    /**
780 21
     * @param UriInterface     $requestUri
781
     * @param EncoderInterface $encoder
782
     *
783
     * @return ResponsesInterface
784 21
     */
785 21
    protected static function defaultCreateResponses(
786 21
        UriInterface $requestUri,
787 21
        EncoderInterface $encoder
788
    ): ResponsesInterface {
789
        $encoder->forOriginalUri($requestUri);
790 21
        $responses = new Responses(
791
            new MediaType(MediaTypeInterface::JSON_API_TYPE, MediaTypeInterface::JSON_API_SUB_TYPE),
792
            $encoder
793
        );
794
795
        return $responses;
796
    }
797
798
    /**
799
     * Developers can override the method in order to add/remove some data for `create`/`update` inputs.
800
     *
801
     * @param string                    $requestBody
802 13
     * @param FactoryInterface          $errorFactory
803
     * @param FormatterFactoryInterface $formatterFactory
804
     *
805
     * @return array
806
     */
807 13
    protected static function readJsonFromRequest(
808 1
        string $requestBody,
809 1
        FactoryInterface $errorFactory,
810 1
        FormatterFactoryInterface $formatterFactory
811
    ): array {
812 1
        if (empty($requestBody) === true || ($json = json_decode($requestBody, true)) === null) {
813
            $formatter = $formatterFactory->createFormatter(Messages::NAMESPACE_NAME);
814
            $errors    = $errorFactory->createErrorCollection();
815 12
            $errors->addDataError($formatter->formatMessage(Messages::MSG_ERR_INVALID_JSON_DATA_IN_REQUEST));
816
817
            throw new JsonApiException($errors);
0 ignored issues
show
Documentation introduced by
$errors is of type object<Neomerx\JsonApi\Schema\ErrorCollection>, but the function expects a object<Neomerx\JsonApi\C...pi\Exceptions\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...
818
        }
819
820
        return $json;
821
    }
822
823
    /**
824
     * Developers can override the method in order to use custom data mapping from a Schema to Model.
825
     *
826
     * @param iterable                 $captures
827 6
     * @param string                   $schemaClass
828
     * @param ModelSchemaInfoInterface $schemaInfo
829
     *
830
     * @return array
831
     */
832 6
    protected static function mapSchemaDataToModelData(
833
        iterable $captures,
834
        string $schemaClass,
835 6
        ModelSchemaInfoInterface $schemaInfo
836
    ): array {
837 6
        static::assertClassImplements($schemaClass, SchemaInterface::class);
838 6
839 6
        /** @var SchemaInterface $schemaClass */
840 6
        static::assertClassImplements($modelClass = $schemaClass::MODEL, ModelInterface::class);
841 6
842 6
        $index         = null;
843 4
        $fields        = [];
844 6
        $toManyIndexes = [];
845 5
        foreach ($captures as $name => $value) {
846 5
            assert(is_string($name) === true);
847 6
            if ($name === DI::KEYWORD_ID) {
848 3
                $index = $value;
849 3
            } elseif ($schemaClass::hasAttributeMapping($name) === true) {
850 3
                $fieldName          = $schemaClass::getAttributeMapping($name);
851 3
                $fields[$fieldName] = $value;
852 3
            } elseif ($schemaClass::hasRelationshipMapping($name) === true) {
853 2
                $modelRelName = $schemaClass::getRelationshipMapping($name);
854 6
                $relType      = $schemaInfo->getRelationshipType($modelClass, $modelRelName);
855
                if ($relType === RelationshipTypes::BELONGS_TO) {
856
                    $fkName          = $schemaInfo->getForeignKey($modelClass, $modelRelName);
857
                    $fields[$fkName] = $value;
858
                } elseif ($relType === RelationshipTypes::BELONGS_TO_MANY) {
859 6
                    $toManyIndexes[$modelRelName] = $value;
860
                }
861 6
            }
862
        }
863
864
        $result = [$index, $fields, $toManyIndexes];
865
866
        return $result;
867
    }
868
869
    /**
870
     * @param mixed                $model
871 1
     * @param UriInterface         $requestUri
872
     * @param JsonSchemasInterface $jsonSchemas
873
     *
874
     * @return string
875
     */
876 1
    protected static function defaultGetResourceUrl(
877 1
        $model,
878 1
        UriInterface $requestUri,
879 1
        JsonSchemasInterface $jsonSchemas
880
    ): string {
881 1
        $schema    = $jsonSchemas->getSchema($model);
882
        $selfLink  = $schema->getSelfLink($model);
883
        $urlPrefix = (string)$requestUri->withPath('')->withQuery('')->withFragment('');
884
        $fullUrl   = $selfLink->getStringRepresentation($urlPrefix);
885
886
        return $fullUrl;
887
    }
888
889 30
    /**
890
     * @param null|string $value
891 30
     *
892
     * @return void
893
     */
894
    private static function assertClassValueDefined(?string $value): void
895
    {
896
        assert(empty($value) === false, 'Value should be defined in `' . static::class . '`.');
897
    }
898
899
    /**
900 31
     * @param string $class
901
     * @param string $interface
902 31
     *
903 31
     * @return void
904 31
     */
905
    private static function assertClassImplements(string $class, string $interface): void
906
    {
907
        assert(
908
            array_key_exists($interface, class_implements($class)) === true,
909
            "Class `$class` should implement `" . $interface . '` interface.'
910
        );
911
    }
912
}
913