Completed
Push — develop ( dcfc04...3d10a6 )
by Neomerx
04:33
created

defaultDeleteInRelationshipHandler()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 33
ccs 0
cts 13
cp 0
rs 9.392
c 0
b 0
f 0
cc 1
nc 1
nop 13
crap 2

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php namespace Limoncello\Flute\Http\Traits;
2
3
/**
4
 * Copyright 2015-2018 [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 Closure;
20
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
21
use Limoncello\Contracts\Application\ModelInterface;
22
use Limoncello\Contracts\Data\ModelSchemaInfoInterface;
23
use Limoncello\Contracts\Data\RelationshipTypes;
24
use Limoncello\Contracts\L10n\FormatterFactoryInterface;
25
use Limoncello\Flute\Contracts\Api\CrudInterface;
26
use Limoncello\Flute\Contracts\Encoder\EncoderInterface;
27
use Limoncello\Flute\Contracts\FactoryInterface;
28
use Limoncello\Flute\Contracts\Http\Query\ParametersMapperInterface;
29
use Limoncello\Flute\Contracts\Models\PaginatedDataInterface;
30
use Limoncello\Flute\Contracts\Schema\JsonSchemasInterface;
31
use Limoncello\Flute\Contracts\Schema\SchemaInterface;
32
use Limoncello\Flute\Contracts\Validation\FormRulesInterface;
33
use Limoncello\Flute\Contracts\Validation\FormValidatorFactoryInterface;
34
use Limoncello\Flute\Contracts\Validation\FormValidatorInterface;
35
use Limoncello\Flute\Contracts\Validation\JsonApiDataParserInterface;
36
use Limoncello\Flute\Contracts\Validation\JsonApiDataRulesInterface;
37
use Limoncello\Flute\Contracts\Validation\JsonApiParserFactoryInterface;
38
use Limoncello\Flute\Contracts\Validation\JsonApiQueryParserInterface;
39
use Limoncello\Flute\Contracts\Validation\JsonApiQueryRulesInterface;
40
use Limoncello\Flute\Http\JsonApiResponse;
41
use Limoncello\Flute\Http\Responses;
42
use Limoncello\Flute\Package\FluteSettings as S;
43
use Limoncello\Flute\Resources\Messages\En\Generic;
44
use Limoncello\Flute\Validation\JsonApi\Rules\DefaultQueryValidationRules;
45
use Neomerx\JsonApi\Contracts\Http\Headers\MediaTypeInterface;
46
use Neomerx\JsonApi\Contracts\Http\ResponsesInterface;
47
use Neomerx\JsonApi\Contracts\Schema\DocumentInterface as DI;
48
use Neomerx\JsonApi\Exceptions\JsonApiException;
49
use Neomerx\JsonApi\Http\Headers\MediaType;
50
use Psr\Container\ContainerExceptionInterface;
51
use Psr\Container\ContainerInterface;
52
use Psr\Container\NotFoundExceptionInterface;
53
use Psr\Http\Message\ResponseInterface;
54
use Psr\Http\Message\UriInterface;
55
56
/**
57
 * @package Limoncello\Flute
58
 */
59
trait DefaultControllerMethodsTrait
60
{
61
    /** @noinspection PhpTooManyParametersInspection
62
     * @param array                       $queryParams
63
     * @param UriInterface                $requestUri
64
     * @param JsonApiQueryParserInterface $queryParser
65
     * @param ParametersMapperInterface   $mapper
66
     * @param CrudInterface               $crud
67
     * @param EncoderInterface            $encoder
68
     *
69
     * @return ResponseInterface
70
     */
71
    protected static function defaultIndexHandler(
72
        array $queryParams,
73
        UriInterface $requestUri,
74
        JsonApiQueryParserInterface $queryParser,
75
        ParametersMapperInterface $mapper,
76
        CrudInterface $crud,
77
        EncoderInterface $encoder
78
    ): ResponseInterface {
79
        $queryParser->parse(null, $queryParams);
80
81
        $models = $mapper->applyQueryParameters($queryParser, $crud)->index();
82
83
        self::defaultApplyIncludesAndFieldSetsToEncoder($queryParser, $encoder);
84
        $responses = static::defaultCreateResponses($requestUri, $encoder);
85
        $response  = ($models->getData()) === null ?
86
            $responses->getCodeResponse(404) : $responses->getContentResponse($models);
87
88
        return $response;
89
    }
90
91
    /** @noinspection PhpTooManyParametersInspection
92
     * @param string                      $index
93
     * @param array                       $queryParams
94
     * @param UriInterface                $requestUri
95
     * @param JsonApiQueryParserInterface $queryParser
96
     * @param ParametersMapperInterface   $mapper
97
     * @param CrudInterface               $crud
98
     * @param EncoderInterface            $encoder
99
     *
100
     * @return ResponseInterface
101
     */
102
    protected static function defaultReadHandler(
103
        string $index,
104
        array $queryParams,
105
        UriInterface $requestUri,
106
        JsonApiQueryParserInterface $queryParser,
107
        ParametersMapperInterface $mapper,
108
        CrudInterface $crud,
109
        EncoderInterface $encoder
110
    ): ResponseInterface {
111
        $queryParser->parse($index, $queryParams);
112
        $validatedIndex = $queryParser->getIdentity();
113
114
        $model = $mapper->applyQueryParameters($queryParser, $crud)->read($validatedIndex);
115
        assert(!($model instanceof PaginatedDataInterface));
116
117
        self::defaultApplyIncludesAndFieldSetsToEncoder($queryParser, $encoder);
118
        $responses = static::defaultCreateResponses($requestUri, $encoder);
119
        $response  = $model === null ?
120
            $responses->getCodeResponse(404) : $responses->getContentResponse($model);
121
122
        return $response;
123
    }
124
125
    /** @noinspection PhpTooManyParametersInspection
126
     * @param string                      $index
127
     * @param Closure                     $apiHandler
128
     * @param array                       $queryParams
129
     * @param UriInterface                $requestUri
130
     * @param JsonApiQueryParserInterface $queryParser
131
     * @param ParametersMapperInterface   $mapper
132
     * @param CrudInterface               $crud
133
     * @param EncoderInterface            $encoder
134
     *
135
     * @return ResponseInterface
136
     *
137
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
138
     */
139
    protected static function defaultReadRelationshipWithClosureHandler(
140
        string $index,
141
        Closure $apiHandler,
142
        array $queryParams,
143
        UriInterface $requestUri,
144
        JsonApiQueryParserInterface $queryParser,
145
        ParametersMapperInterface $mapper,
146
        CrudInterface $crud,
147
        EncoderInterface $encoder
148
    ): ResponseInterface {
149
        $queryParser->parse($index, $queryParams);
150
        $mapper->applyQueryParameters($queryParser, $crud);
151
152
        $relData = call_user_func($apiHandler);
153
154
        self::defaultApplyIncludesAndFieldSetsToEncoder($queryParser, $encoder);
155
        $responses = static::defaultCreateResponses($requestUri, $encoder);
156
157
        $noData   = $relData === null || ($relData instanceof PaginatedDataInterface && $relData->getData() === null);
158
        $response = $noData === true ? $responses->getCodeResponse(404) : $responses->getContentResponse($relData);
159
160
        return $response;
161
    }
162
163
    /** @noinspection PhpTooManyParametersInspection
164
     * @param string                      $index
165
     * @param Closure                     $apiHandler
166
     * @param array                       $queryParams
167
     * @param UriInterface                $requestUri
168
     * @param JsonApiQueryParserInterface $queryParser
169
     * @param ParametersMapperInterface   $mapper
170
     * @param CrudInterface               $crud
171
     * @param EncoderInterface            $encoder
172
     *
173
     * @return ResponseInterface
174
     *
175
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
176
     */
177
    protected static function defaultReadRelationshipIdentifiersWithClosureHandler(
178
        string $index,
179
        Closure $apiHandler,
180
        array $queryParams,
181
        UriInterface $requestUri,
182
        JsonApiQueryParserInterface $queryParser,
183
        ParametersMapperInterface $mapper,
184
        CrudInterface $crud,
185
        EncoderInterface $encoder
186
    ): ResponseInterface {
187
        $queryParser->parse($index, $queryParams);
188
        $mapper->applyQueryParameters($queryParser, $crud);
189
190
        $relData = call_user_func($apiHandler);
191
192
        self::defaultApplyIncludesAndFieldSetsToEncoder($queryParser, $encoder);
193
        $responses = static::defaultCreateResponses($requestUri, $encoder);
194
195
        $noData   = $relData === null || ($relData instanceof PaginatedDataInterface && $relData->getData() === null);
196
        $response = $noData === true ? $responses->getCodeResponse(404) : $responses->getIdentifiersResponse($relData);
197
198
        return $response;
199
    }
200
201
    /** @noinspection PhpTooManyParametersInspection
202
     * @param UriInterface               $requestUri
203
     * @param string                     $requestBody
204
     * @param string                     $schemaClass
205
     * @param ModelSchemaInfoInterface   $schemaInfo
206
     * @param JsonApiDataParserInterface $parser
207
     * @param CrudInterface              $crud
208
     * @param JsonSchemasInterface       $jsonSchemas
209
     * @param EncoderInterface           $encoder
210
     * @param FactoryInterface           $errorFactory
211
     * @param FormatterFactoryInterface  $formatterFactory
212
     * @param string                     $messagesNamespace
213
     * @param string                     $errorMessage
214
     *
215
     * @return ResponseInterface
216
     *
217
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
218
     */
219
    protected static function defaultCreateHandler(
220
        UriInterface $requestUri,
221
        string $requestBody,
222
        string $schemaClass,
223
        ModelSchemaInfoInterface $schemaInfo,
224
        JsonApiDataParserInterface $parser,
225
        CrudInterface $crud,
226
        JsonSchemasInterface $jsonSchemas,
227
        EncoderInterface $encoder,
228
        FactoryInterface $errorFactory,
229
        FormatterFactoryInterface $formatterFactory,
230
        string $messagesNamespace = S::GENERIC_NAMESPACE,
231
        string $errorMessage = Generic::MSG_ERR_INVALID_ELEMENT
232
    ): ResponseInterface {
233
        // some of the users want to reuse default `create` but have a custom part for responses
234
        // to meet this requirement it is split into two parts.
235
        $index = static::defaultCreate(
236
            $requestBody,
237
            $schemaClass,
238
            $schemaInfo,
239
            $parser,
240
            $crud,
241
            $errorFactory,
242
            $formatterFactory,
243
            $messagesNamespace,
244
            $errorMessage
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
     * @param FactoryInterface           $errorFactory
257
     * @param FormatterFactoryInterface  $formatterFactory
258
     * @param string                     $messagesNamespace
259
     * @param string                     $errorMessage
260
     *
261
     * @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...
262
     */
263
    protected static function defaultCreate(
264
        string $requestBody,
265
        string $schemaClass,
266
        ModelSchemaInfoInterface $schemaInfo,
267
        JsonApiDataParserInterface $parser,
268
        CrudInterface $crud,
269
        FactoryInterface $errorFactory,
270
        FormatterFactoryInterface $formatterFactory,
271
        string $messagesNamespace = S::GENERIC_NAMESPACE,
272
        string $errorMessage = Generic::MSG_ERR_INVALID_ELEMENT
273
    ): string {
274
        $jsonData = static::readJsonFromRequest(
275
            $requestBody,
276
            $errorFactory,
277
            $formatterFactory,
278
            $messagesNamespace,
279
            $errorMessage
280
        );
281
282
        $captures = $parser->assert($jsonData)->getCaptures();
283
284
        list ($index, $attributes, $toMany) = static::mapSchemaDataToModelData($captures, $schemaClass, $schemaInfo);
285
286
        try {
287
            $index = $crud->create($index, $attributes, $toMany);
288
        } /** @noinspection PhpRedundantCatchClauseInspection */ catch (UniqueConstraintViolationException $exception) {
289
            $errors    = $errorFactory->createErrorCollection();
290
            $formatter = $formatterFactory->createFormatter($messagesNamespace);
291
            $title     = $formatter->formatMessage($errorMessage);
292
            $details   = null;
293
            $errorCode = JsonApiResponse::HTTP_CONFLICT;
294
            $errors->addDataError($title, $details, $errorCode);
295
296
            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...
297
        }
298
299
        return $index;
300
    }
301
302
    /**
303
     * @param string               $index
304
     * @param UriInterface         $requestUri
305
     * @param CrudInterface        $crud
306
     * @param JsonSchemasInterface $jsonSchemas
307
     * @param EncoderInterface     $encoder
308
     *
309
     * @return ResponseInterface
310
     */
311
    protected static function defaultCreateResponse(
312
        string $index,
313
        UriInterface $requestUri,
314
        CrudInterface $crud,
315
        JsonSchemasInterface $jsonSchemas,
316
        EncoderInterface $encoder
317
    ): ResponseInterface {
318
        $model = $crud->read($index);
319
        assert($model !== null && !($model instanceof PaginatedDataInterface));
320
321
        $schema    = $jsonSchemas->getSchema($model);
322
        $selfLink  = $schema->getSelfLink($model);
323
        $urlPrefix = (string)$requestUri->withPath('')->withQuery('')->withFragment('');
324
        $fullUrl   = $selfLink->getStringRepresentation($urlPrefix);
325
326
        $responses = static::defaultCreateResponses($requestUri, $encoder);
327
        $response  = $responses->getCreatedResponse($model, $fullUrl);
328
329
        return $response;
330
    }
331
332
    /** @noinspection PhpTooManyParametersInspection
333
     * @param string                     $index
334
     * @param UriInterface               $requestUri
335
     * @param string                     $requestBody
336
     * @param string                     $schemaClass
337
     * @param ModelSchemaInfoInterface   $schemaInfo
338
     * @param JsonApiDataParserInterface $parser
339
     * @param CrudInterface              $crud
340
     * @param EncoderInterface           $encoder
341
     * @param FactoryInterface           $errorFactory
342
     * @param FormatterFactoryInterface  $formatterFactory
343
     * @param string                     $messagesNamespace
344
     * @param string                     $errorMessage
345
     *
346
     * @return ResponseInterface
347
     *
348
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
349
     */
350
    protected static function defaultUpdateHandler(
351
        string $index,
352
        UriInterface $requestUri,
353
        string $requestBody,
354
        string $schemaClass,
355
        ModelSchemaInfoInterface $schemaInfo,
356
        JsonApiDataParserInterface $parser,
357
        CrudInterface $crud,
358
        EncoderInterface $encoder,
359
        FactoryInterface $errorFactory,
360
        FormatterFactoryInterface $formatterFactory,
361
        string $messagesNamespace = S::GENERIC_NAMESPACE,
362
        string $errorMessage = Generic::MSG_ERR_INVALID_ELEMENT
363
    ): ResponseInterface {
364
        // some of the users want to reuse default `update` but have a custom part for responses
365
        // to meet this requirement it is split into two parts.
366
        $updated = static::defaultUpdate(
367
            $index,
368
            $requestBody,
369
            $schemaClass,
370
            $schemaInfo,
371
            $parser,
372
            $crud,
373
            $errorFactory,
374
            $formatterFactory,
375
            $messagesNamespace,
376
            $errorMessage
377
        );
378
379
        return static::defaultUpdateResponse($updated, $index, $requestUri, $crud, $encoder);
380
    }
381
382
    /** @noinspection PhpTooManyParametersInspection
383
     * @param string                     $index
384
     * @param string                     $requestBody
385
     * @param string                     $schemaClass
386
     * @param ModelSchemaInfoInterface   $schemaInfo
387
     * @param JsonApiDataParserInterface $parser
388
     * @param CrudInterface              $crud
389
     * @param FactoryInterface           $errorFactory
390
     * @param FormatterFactoryInterface  $formatterFactory
391
     * @param string                     $messagesNamespace
392
     * @param string                     $errorMessage
393
     *
394
     * @return int
395
     *
396
     * @SuppressWarnings(PHPMD.ElseExpression)
397
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
398
     */
399
    protected static function defaultUpdate(
400
        string $index,
401
        string $requestBody,
402
        string $schemaClass,
403
        ModelSchemaInfoInterface $schemaInfo,
404
        JsonApiDataParserInterface $parser,
405
        CrudInterface $crud,
406
        FactoryInterface $errorFactory,
407
        FormatterFactoryInterface $formatterFactory,
408
        string $messagesNamespace = S::GENERIC_NAMESPACE,
409
        string $errorMessage = Generic::MSG_ERR_INVALID_ELEMENT
410
    ): int {
411
        $jsonData = static::readJsonFromRequest(
412
            $requestBody,
413
            $errorFactory,
414
            $formatterFactory,
415
            $messagesNamespace,
416
            $errorMessage
417
        );
418
419
        // check that index in data and URL are identical
420
        $indexValue = $jsonData[DI::KEYWORD_DATA][DI::KEYWORD_ID] ?? null;
421
        if (empty($indexValue) === false) {
422
            if ($indexValue !== $index) {
423
                $errors    = $errorFactory->createErrorCollection();
424
                $formatter = $formatterFactory->createFormatter($messagesNamespace);
425
                $errors->addDataIdError($formatter->formatMessage($errorMessage));
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
        } else {
430
            // put the index to data for our convenience
431
            $jsonData[DI::KEYWORD_DATA][DI::KEYWORD_ID] = $index;
432
        }
433
        // validate the data
434
        $captures = $parser->assert($jsonData)->getCaptures();
435
436
        list ($index, $attributes, $toMany) = static::mapSchemaDataToModelData($captures, $schemaClass, $schemaInfo);
437
438
        try {
439
            $updated = $crud->update($index, $attributes, $toMany);
440
        } /** @noinspection PhpRedundantCatchClauseInspection */ catch (UniqueConstraintViolationException $exception) {
441
            $errors    = $errorFactory->createErrorCollection();
442
            $formatter = $formatterFactory->createFormatter($messagesNamespace);
443
            $title     = $formatter->formatMessage($errorMessage);
444
            $details   = null;
445
            $errorCode = JsonApiResponse::HTTP_CONFLICT;
446
            $errors->addDataError($title, $details, $errorCode);
447
448
            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...
449
        }
450
451
        return $updated;
452
    }
453
454
    /**
455
     * @param int              $updated
456
     * @param string           $index
457
     * @param UriInterface     $requestUri
458
     * @param CrudInterface    $crud
459
     * @param EncoderInterface $encoder
460
     *
461
     * @return ResponseInterface
462
     *
463
     * @SuppressWarnings(PHPMD.ElseExpression)
464
     */
465
    protected static function defaultUpdateResponse(
466
        int $updated,
467
        string $index,
468
        UriInterface $requestUri,
469
        CrudInterface $crud,
470
        EncoderInterface $encoder
471
    ): ResponseInterface {
472
        $responses = static::defaultCreateResponses($requestUri, $encoder);
473
        if ($updated > 0 && ($model = $crud->read($index)) !== null) {
474
            assert(!($model instanceof PaginatedDataInterface));
475
            $response = $responses->getContentResponse($model);
476
        } else {
477
            $response = $responses->getCodeResponse(404);
478
        }
479
480
        return $response;
481
    }
482
483
    /**
484
     * @param string                      $index
485
     * @param UriInterface                $requestUri
486
     * @param JsonApiQueryParserInterface $queryParser
487
     * @param CrudInterface               $crud
488
     * @param EncoderInterface            $encoder
489
     *
490
     * @return ResponseInterface
491
     */
492
    protected static function defaultDeleteHandler(
493
        string $index,
494
        UriInterface $requestUri,
495
        JsonApiQueryParserInterface $queryParser,
496
        CrudInterface $crud,
497
        EncoderInterface $encoder
498
    ): ResponseInterface {
499
        $validatedIndex = $queryParser->parse($index)->getIdentity();
500
        $crud->remove($validatedIndex);
501
502
        $responses = static::defaultCreateResponses($requestUri, $encoder);
503
        $response  = $responses->getCodeResponse(204);
504
505
        return $response;
506
    }
507
508
    /** @noinspection PhpTooManyParametersInspection
509
     * @param string                      $index
510
     * @param string                      $jsonRelName
511
     * @param string                      $modelRelName
512
     * @param UriInterface                $requestUri
513
     * @param string                      $requestBody
514
     * @param string                      $schemaClass
515
     * @param ModelSchemaInfoInterface    $schemaInfo
516
     * @param JsonApiQueryParserInterface $queryParser
517
     * @param JsonApiDataParserInterface  $dataValidator
518
     * @param CrudInterface               $parentCrud
519
     * @param EncoderInterface            $encoder
520
     * @param FactoryInterface            $errorFactory
521
     * @param FormatterFactoryInterface   $formatterFactory
522
     *
523
     * @return ResponseInterface
524
     *
525
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
526
     */
527
    protected static function defaultAddInRelationshipHandler(
528
        string $index,
529
        string $jsonRelName,
530
        string $modelRelName,
531
        UriInterface $requestUri,
532
        string $requestBody,
533
        string $schemaClass,
534
        ModelSchemaInfoInterface $schemaInfo,
535
        JsonApiQueryParserInterface $queryParser,
536
        JsonApiDataParserInterface $dataValidator,
537
        CrudInterface $parentCrud,
538
        EncoderInterface $encoder,
539
        FactoryInterface $errorFactory,
540
        FormatterFactoryInterface $formatterFactory
541
    ): ResponseInterface {
542
        /** @var SchemaInterface $schemaClass */
543
        assert(array_key_exists(SchemaInterface::class, class_implements($schemaClass)) === true);
544
        $modelClass = $schemaClass::MODEL;
545
        assert($schemaInfo->hasRelationship($modelClass, $modelRelName));
546
        assert($schemaInfo->getRelationshipType($modelClass, $modelRelName) === RelationshipTypes::BELONGS_TO_MANY);
547
548
        $jsonData = static::readJsonFromRequest($requestBody, $errorFactory, $formatterFactory);
549
        $captures = $dataValidator->assertRelationship($index, $jsonRelName, $jsonData)->getCaptures();
550
        $relIds   = $captures[$jsonRelName];
551
552
        $validatedIndex = $queryParser->parse($index)->getIdentity();
553
        $parentCrud->createInBelongsToManyRelationship($validatedIndex, $modelRelName, $relIds);
554
555
        $responses = static::defaultCreateResponses($requestUri, $encoder);
556
        $response  = $responses->getCodeResponse(204);
557
558
        return $response;
559
    }
560
561
    /** @noinspection PhpTooManyParametersInspection
562
     * @param string                      $index
563
     * @param string                      $jsonRelName
564
     * @param string                      $modelRelName
565
     * @param UriInterface                $requestUri
566
     * @param string                      $requestBody
567
     * @param string                      $schemaClass
568
     * @param ModelSchemaInfoInterface    $schemaInfo
569
     * @param JsonApiQueryParserInterface $queryParser
570
     * @param JsonApiDataParserInterface  $dataValidator
571
     * @param CrudInterface               $parentCrud
572
     * @param EncoderInterface            $encoder
573
     * @param FactoryInterface            $errorFactory
574
     * @param FormatterFactoryInterface   $formatterFactory
575
     *
576
     * @return ResponseInterface
577
     *
578
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
579
     */
580
    protected static function defaultDeleteInRelationshipHandler(
581
        string $index,
582
        string $jsonRelName,
583
        string $modelRelName,
584
        UriInterface $requestUri,
585
        string $requestBody,
586
        string $schemaClass,
587
        ModelSchemaInfoInterface $schemaInfo,
588
        JsonApiQueryParserInterface $queryParser,
589
        JsonApiDataParserInterface $dataValidator,
590
        CrudInterface $parentCrud,
591
        EncoderInterface $encoder,
592
        FactoryInterface $errorFactory,
593
        FormatterFactoryInterface $formatterFactory
594
    ): ResponseInterface {
595
        /** @var SchemaInterface $schemaClass */
596
        assert(array_key_exists(SchemaInterface::class, class_implements($schemaClass)) === true);
597
        $modelClass = $schemaClass::MODEL;
598
        assert($schemaInfo->hasRelationship($modelClass, $modelRelName));
599
        assert($schemaInfo->getRelationshipType($modelClass, $modelRelName) === RelationshipTypes::BELONGS_TO_MANY);
600
601
        $jsonData = static::readJsonFromRequest($requestBody, $errorFactory, $formatterFactory);
602
        $captures = $dataValidator->assertRelationship($index, $jsonRelName, $jsonData)->getCaptures();
603
        $relIds   = $captures[$jsonRelName];
604
605
        $validatedIndex = $queryParser->parse($index)->getIdentity();
606
        $parentCrud->removeInBelongsToManyRelationship($validatedIndex, $modelRelName, $relIds);
607
608
        $responses = static::defaultCreateResponses($requestUri, $encoder);
609
        $response  = $responses->getCodeResponse(204);
610
611
        return $response;
612
    }
613
614
    /** @noinspection PhpTooManyParametersInspection
615
     * @param string                      $index
616
     * @param string                      $jsonRelName
617
     * @param string                      $modelRelName
618
     * @param UriInterface                $requestUri
619
     * @param string                      $requestBody
620
     * @param string                      $schemaClass
621
     * @param ModelSchemaInfoInterface    $schemaInfo
622
     * @param JsonApiQueryParserInterface $queryParser
623
     * @param JsonApiDataParserInterface  $dataValidator
624
     * @param CrudInterface               $crud
625
     * @param EncoderInterface            $encoder
626
     * @param FactoryInterface            $errorFactory
627
     * @param FormatterFactoryInterface   $formatterFactory
628
     *
629
     * @return ResponseInterface
630
     *
631
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
632
     */
633
    protected static function defaultReplaceInRelationship(
634
        string $index,
635
        string $jsonRelName,
636
        string $modelRelName,
637
        UriInterface $requestUri,
638
        string $requestBody,
639
        string $schemaClass,
640
        ModelSchemaInfoInterface $schemaInfo,
641
        JsonApiQueryParserInterface $queryParser,
642
        JsonApiDataParserInterface $dataValidator,
643
        CrudInterface $crud,
644
        EncoderInterface $encoder,
645
        FactoryInterface $errorFactory,
646
        FormatterFactoryInterface $formatterFactory
647
    ): ResponseInterface {
648
        /** @var SchemaInterface $schemaClass */
649
        assert(array_key_exists(SchemaInterface::class, class_implements($schemaClass)) === true);
650
        $modelClass = $schemaClass::MODEL;
651
        assert($schemaInfo->hasRelationship($modelClass, $modelRelName));
652
        assert(
653
            ($type =$schemaInfo->getRelationshipType($modelClass, $modelRelName)) === RelationshipTypes::BELONGS_TO ||
654
            $type === RelationshipTypes::BELONGS_TO_MANY
655
        );
656
657
        $jsonData = static::readJsonFromRequest($requestBody, $errorFactory, $formatterFactory);
658
        $captures = $dataValidator->assertRelationship($index, $jsonRelName, $jsonData)->getCaptures();
659
660
        // If we are here then we have something in 'data' section.
661
662
        $validatedIndex = $queryParser->parse($index)->getIdentity();
663
        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...
664
665
        $updated = $crud->update($validatedIndex, $attributes, $toMany);
666
667
        return static::defaultUpdateResponse($updated, $index, $requestUri, $crud, $encoder);
668
    }
669
670
    /**
671
     * @param ContainerInterface $container
672
     * @param string             $rulesClass
673
     *
674
     * @return JsonApiQueryParserInterface
675
     *
676
     * @throws ContainerExceptionInterface
677
     * @throws NotFoundExceptionInterface
678
     */
679
    protected static function defaultCreateQueryParser(
680
        ContainerInterface $container,
681
        string $rulesClass = DefaultQueryValidationRules::class
682
    ): JsonApiQueryParserInterface {
683
        static::assertClassImplements($rulesClass, JsonApiQueryRulesInterface::class);
684
685
        /** @var JsonApiParserFactoryInterface $factory */
686
        $factory = $container->get(JsonApiParserFactoryInterface::class);
687
        $parser  = $factory->createQueryParser($rulesClass);
688
689
        return $parser;
690
    }
691
692
    /**
693
     * @param ContainerInterface $container
694
     * @param string             $rulesClass
695
     *
696
     * @return JsonApiDataParserInterface
697
     *
698
     * @throws ContainerExceptionInterface
699
     * @throws NotFoundExceptionInterface
700
     */
701
    protected static function defaultCreateDataParser(
702
        ContainerInterface $container,
703
        string $rulesClass
704
    ): JsonApiDataParserInterface {
705
        static::assertClassImplements($rulesClass, JsonApiDataRulesInterface::class);
706
707
        /** @var JsonApiParserFactoryInterface $factory */
708
        $factory = $container->get(JsonApiParserFactoryInterface::class);
709
        $parser  = $factory->createDataParser($rulesClass);
710
711
        return $parser;
712
    }
713
714
    /**
715
     * @param ContainerInterface $container
716
     * @param string             $rulesClass
717
     *
718
     * @return FormValidatorInterface
719
     *
720
     * @throws ContainerExceptionInterface
721
     * @throws NotFoundExceptionInterface
722
     */
723
    protected static function defaultCreateFormValidator(
724
        ContainerInterface $container,
725
        string $rulesClass
726
    ): FormValidatorInterface {
727
        static::assertClassImplements($rulesClass, FormRulesInterface::class);
728
729
        /** @var FormValidatorFactoryInterface $factory */
730
        $factory   = $container->get(FormValidatorFactoryInterface::class);
731
        $validator = $factory->createValidator($rulesClass);
732
733
        return $validator;
734
    }
735
736
    /**
737
     * @param ContainerInterface $container
738
     * @param string             $schemaClass
739
     *
740
     * @return ParametersMapperInterface
741
     *
742
     * @throws ContainerExceptionInterface
743
     * @throws NotFoundExceptionInterface
744
     */
745
    protected static function defaultCreateParameterMapper(
746
        ContainerInterface $container,
747
        string $schemaClass
748
    ): ParametersMapperInterface {
749
        static::assertClassImplements($schemaClass, SchemaInterface::class);
750
751
        /** @var SchemaInterface $schemaClass */
752
        $jsonResourceType = $schemaClass::TYPE;
753
754
        /** @var ParametersMapperInterface $mapper */
755
        $mapper = $container->get(ParametersMapperInterface::class);
756
        $mapper->selectRootSchemaByResourceType($jsonResourceType);
757
758
        return $mapper;
759
    }
760
761
    /**
762
     * @param JsonApiQueryParserInterface $queryParser
763
     * @param EncoderInterface            $encoder
764
     *
765
     * @return void
766
     */
767
    protected static function defaultApplyIncludesAndFieldSetsToEncoder(
768
        JsonApiQueryParserInterface $queryParser,
769
        EncoderInterface $encoder
770
    ): void {
771
        if ($queryParser->hasIncludes() === true) {
772
            $paths = array_keys($queryParser->getIncludes());
773
            $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...
774
        }
775
        if ($queryParser->hasFields() === true) {
776
            $encoder->withFieldSets($queryParser->getFields());
777
        }
778
    }
779
780
    /**
781
     * @param ContainerInterface $container
782
     * @param string|null        $class
783
     *
784
     * @return CrudInterface
785
     *
786
     * @throws ContainerExceptionInterface
787
     * @throws NotFoundExceptionInterface
788
     */
789
    protected static function defaultCreateApi(ContainerInterface $container, string $class): CrudInterface
790
    {
791
        static::assertClassImplements($class, CrudInterface::class);
792
793
        /** @var FactoryInterface $factory */
794
        $factory = $container->get(FactoryInterface::class);
795
        $api     = $factory->createApi($class);
796
797
        return $api;
798
    }
799
800
    /**
801
     * @param UriInterface     $requestUri
802
     * @param EncoderInterface $encoder
803
     *
804
     * @return ResponsesInterface
805
     */
806
    protected static function defaultCreateResponses(
807
        UriInterface $requestUri,
808
        EncoderInterface $encoder
809
    ): ResponsesInterface {
810
        $encoder->forOriginalUri($requestUri);
811
        $responses = new Responses(
812
            new MediaType(MediaTypeInterface::JSON_API_TYPE, MediaTypeInterface::JSON_API_SUB_TYPE),
813
            $encoder
814
        );
815
816
        return $responses;
817
    }
818
819
    /**
820
     * Developers can override the method in order to add/remove some data for `create`/`update` inputs.
821
     *
822
     * @param string                    $requestBody
823
     * @param FactoryInterface          $errorFactory
824
     * @param FormatterFactoryInterface $formatterFactory
825
     * @param string                    $messagesNamespace
826
     * @param string                    $errorMessage
827
     *
828
     * @return array
829
     */
830
    protected static function readJsonFromRequest(
831
        string $requestBody,
832
        FactoryInterface $errorFactory,
833
        FormatterFactoryInterface $formatterFactory,
834
        string $messagesNamespace = S::GENERIC_NAMESPACE,
835
        string $errorMessage = Generic::MSG_ERR_INVALID_ELEMENT
836
    ): array {
837
        if (empty($requestBody) === true || ($json = json_decode($requestBody, true)) === null) {
838
            $formatter = $formatterFactory->createFormatter($messagesNamespace);
839
            $errors    = $errorFactory->createErrorCollection();
840
            $errors->addDataError($formatter->formatMessage($errorMessage));
841
842
            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...
843
        }
844
845
        return $json;
846
    }
847
848
    /**
849
     * Developers can override the method in order to use custom data mapping from a Schema to Model.
850
     *
851
     * @param iterable                 $captures
852
     * @param string                   $schemaClass
853
     * @param ModelSchemaInfoInterface $schemaInfo
854
     *
855
     * @return array
856
     */
857
    protected static function mapSchemaDataToModelData(
858
        iterable $captures,
859
        string $schemaClass,
860
        ModelSchemaInfoInterface $schemaInfo
861
    ): array {
862
        static::assertClassImplements($schemaClass, SchemaInterface::class);
863
864
        /** @var SchemaInterface $schemaClass */
865
        static::assertClassImplements($modelClass = $schemaClass::MODEL, ModelInterface::class);
866
        /** @var ModelInterface $modelClass */
867
868
        $index         = null;
869
        $fields        = [];
870
        $toManyIndexes = [];
871
        foreach ($captures as $name => $value) {
872
            assert(is_string($name) === true);
873
            if ($name === DI::KEYWORD_ID) {
874
                $index = $value;
875
            } elseif ($schemaClass::hasAttributeMapping($name) === true) {
876
                $fieldName          = $schemaClass::getAttributeMapping($name);
877
                $fields[$fieldName] = $value;
878
            } elseif ($schemaClass::hasRelationshipMapping($name) === true) {
879
                $modelRelName = $schemaClass::getRelationshipMapping($name);
880
                $relType      = $schemaInfo->getRelationshipType($modelClass, $modelRelName);
0 ignored issues
show
Documentation introduced by
$modelClass is of type object<Limoncello\Contra...ication\ModelInterface>, 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...
881
                if ($relType === RelationshipTypes::BELONGS_TO) {
882
                    $fkName          = $schemaInfo->getForeignKey($modelClass, $modelRelName);
0 ignored issues
show
Documentation introduced by
$modelClass is of type object<Limoncello\Contra...ication\ModelInterface>, 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...
883
                    $fields[$fkName] = $value;
884
                } elseif ($relType === RelationshipTypes::BELONGS_TO_MANY) {
885
                    $toManyIndexes[$modelRelName] = $value;
886
                }
887
            }
888
        }
889
890
        $result = [$index, $fields, $toManyIndexes];
891
892
        return $result;
893
    }
894
895
    /**
896
     * @param null|string $value
897
     *
898
     * @return void
899
     */
900
    private static function assertClassValueDefined(?string $value): void
901
    {
902
        assert(empty($value) === false, 'Value should be defined in `' . static::class . '`.');
903
    }
904
905
    /**
906
     * @param string $class
907
     * @param string $interface
908
     *
909
     * @return void
910
     */
911
    private static function assertClassImplements(string $class, string $interface): void
912
    {
913
        assert(
914
            array_key_exists($interface, class_implements($class)) === true,
915
            "Class `$class` should implement `" . $interface . '` interface.'
916
        );
917
    }
918
}
919