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

defaultApplyIncludesAndFieldSetsToEncoder()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 0
cts 6
cp 0
rs 9.8666
c 0
b 0
f 0
cc 3
nc 4
nop 2
crap 12
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