Completed
Push — develop ( 3d10a6...3822de )
by Neomerx
02:23
created

defaultGetResourceUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

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 1
nc 1
nop 3
crap 2
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
        $responses = static::defaultCreateResponses($requestUri, $encoder);
322
        $fullUrl   = static::defaultGetResourceUrl($model, $requestUri, $jsonSchemas);
323
        $response  = $responses->getCreatedResponse($model, $fullUrl);
324
325
        return $response;
326
    }
327
328
    /** @noinspection PhpTooManyParametersInspection
329
     * @param string                     $index
330
     * @param UriInterface               $requestUri
331
     * @param string                     $requestBody
332
     * @param string                     $schemaClass
333
     * @param ModelSchemaInfoInterface   $schemaInfo
334
     * @param JsonApiDataParserInterface $parser
335
     * @param CrudInterface              $crud
336
     * @param EncoderInterface           $encoder
337
     * @param FactoryInterface           $errorFactory
338
     * @param FormatterFactoryInterface  $formatterFactory
339
     * @param string                     $messagesNamespace
340
     * @param string                     $errorMessage
341
     *
342
     * @return ResponseInterface
343
     *
344
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
345
     */
346
    protected static function defaultUpdateHandler(
347
        string $index,
348
        UriInterface $requestUri,
349
        string $requestBody,
350
        string $schemaClass,
351
        ModelSchemaInfoInterface $schemaInfo,
352
        JsonApiDataParserInterface $parser,
353
        CrudInterface $crud,
354
        EncoderInterface $encoder,
355
        FactoryInterface $errorFactory,
356
        FormatterFactoryInterface $formatterFactory,
357
        string $messagesNamespace = S::GENERIC_NAMESPACE,
358
        string $errorMessage = Generic::MSG_ERR_INVALID_ELEMENT
359
    ): ResponseInterface {
360
        // some of the users want to reuse default `update` but have a custom part for responses
361
        // to meet this requirement it is split into two parts.
362
        $updated = static::defaultUpdate(
363
            $index,
364
            $requestBody,
365
            $schemaClass,
366
            $schemaInfo,
367
            $parser,
368
            $crud,
369
            $errorFactory,
370
            $formatterFactory,
371
            $messagesNamespace,
372
            $errorMessage
373
        );
374
375
        return static::defaultUpdateResponse($updated, $index, $requestUri, $crud, $encoder);
376
    }
377
378
    /** @noinspection PhpTooManyParametersInspection
379
     * @param string                     $index
380
     * @param string                     $requestBody
381
     * @param string                     $schemaClass
382
     * @param ModelSchemaInfoInterface   $schemaInfo
383
     * @param JsonApiDataParserInterface $parser
384
     * @param CrudInterface              $crud
385
     * @param FactoryInterface           $errorFactory
386
     * @param FormatterFactoryInterface  $formatterFactory
387
     * @param string                     $messagesNamespace
388
     * @param string                     $errorMessage
389
     *
390
     * @return int
391
     *
392
     * @SuppressWarnings(PHPMD.ElseExpression)
393
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
394
     */
395
    protected static function defaultUpdate(
396
        string $index,
397
        string $requestBody,
398
        string $schemaClass,
399
        ModelSchemaInfoInterface $schemaInfo,
400
        JsonApiDataParserInterface $parser,
401
        CrudInterface $crud,
402
        FactoryInterface $errorFactory,
403
        FormatterFactoryInterface $formatterFactory,
404
        string $messagesNamespace = S::GENERIC_NAMESPACE,
405
        string $errorMessage = Generic::MSG_ERR_INVALID_ELEMENT
406
    ): int {
407
        $jsonData = static::readJsonFromRequest(
408
            $requestBody,
409
            $errorFactory,
410
            $formatterFactory,
411
            $messagesNamespace,
412
            $errorMessage
413
        );
414
415
        // check that index in data and URL are identical
416
        $indexValue = $jsonData[DI::KEYWORD_DATA][DI::KEYWORD_ID] ?? null;
417
        if (empty($indexValue) === false) {
418
            if ($indexValue !== $index) {
419
                $errors    = $errorFactory->createErrorCollection();
420
                $formatter = $formatterFactory->createFormatter($messagesNamespace);
421
                $errors->addDataIdError($formatter->formatMessage($errorMessage));
422
423
                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...
424
            }
425
        } else {
426
            // put the index to data for our convenience
427
            $jsonData[DI::KEYWORD_DATA][DI::KEYWORD_ID] = $index;
428
        }
429
        // validate the data
430
        $captures = $parser->assert($jsonData)->getCaptures();
431
432
        list ($index, $attributes, $toMany) = static::mapSchemaDataToModelData($captures, $schemaClass, $schemaInfo);
433
434
        try {
435
            $updated = $crud->update($index, $attributes, $toMany);
436
        } /** @noinspection PhpRedundantCatchClauseInspection */ catch (UniqueConstraintViolationException $exception) {
437
            $errors    = $errorFactory->createErrorCollection();
438
            $formatter = $formatterFactory->createFormatter($messagesNamespace);
439
            $title     = $formatter->formatMessage($errorMessage);
440
            $details   = null;
441
            $errorCode = JsonApiResponse::HTTP_CONFLICT;
442
            $errors->addDataError($title, $details, $errorCode);
443
444
            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...
445
        }
446
447
        return $updated;
448
    }
449
450
    /**
451
     * @param int              $updated
452
     * @param string           $index
453
     * @param UriInterface     $requestUri
454
     * @param CrudInterface    $crud
455
     * @param EncoderInterface $encoder
456
     *
457
     * @return ResponseInterface
458
     *
459
     * @SuppressWarnings(PHPMD.ElseExpression)
460
     */
461
    protected static function defaultUpdateResponse(
462
        int $updated,
463
        string $index,
464
        UriInterface $requestUri,
465
        CrudInterface $crud,
466
        EncoderInterface $encoder
467
    ): ResponseInterface {
468
        $responses = static::defaultCreateResponses($requestUri, $encoder);
469
        if ($updated > 0 && ($model = $crud->read($index)) !== null) {
470
            assert(!($model instanceof PaginatedDataInterface));
471
            $response = $responses->getContentResponse($model);
472
        } else {
473
            $response = $responses->getCodeResponse(404);
474
        }
475
476
        return $response;
477
    }
478
479
    /**
480
     * @param string                      $index
481
     * @param UriInterface                $requestUri
482
     * @param JsonApiQueryParserInterface $queryParser
483
     * @param CrudInterface               $crud
484
     * @param EncoderInterface            $encoder
485
     *
486
     * @return ResponseInterface
487
     */
488
    protected static function defaultDeleteHandler(
489
        string $index,
490
        UriInterface $requestUri,
491
        JsonApiQueryParserInterface $queryParser,
492
        CrudInterface $crud,
493
        EncoderInterface $encoder
494
    ): ResponseInterface {
495
        $validatedIndex = $queryParser->parse($index)->getIdentity();
496
        $crud->remove($validatedIndex);
497
498
        $responses = static::defaultCreateResponses($requestUri, $encoder);
499
        $response  = $responses->getCodeResponse(204);
500
501
        return $response;
502
    }
503
504
    /** @noinspection PhpTooManyParametersInspection
505
     * @param string                      $index
506
     * @param string                      $jsonRelName
507
     * @param string                      $modelRelName
508
     * @param UriInterface                $requestUri
509
     * @param string                      $requestBody
510
     * @param string                      $schemaClass
511
     * @param ModelSchemaInfoInterface    $schemaInfo
512
     * @param JsonApiQueryParserInterface $queryParser
513
     * @param JsonApiDataParserInterface  $dataValidator
514
     * @param CrudInterface               $parentCrud
515
     * @param EncoderInterface            $encoder
516
     * @param FactoryInterface            $errorFactory
517
     * @param FormatterFactoryInterface   $formatterFactory
518
     *
519
     * @return ResponseInterface
520
     *
521
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
522
     */
523
    protected static function defaultAddInRelationshipHandler(
524
        string $index,
525
        string $jsonRelName,
526
        string $modelRelName,
527
        UriInterface $requestUri,
528
        string $requestBody,
529
        string $schemaClass,
530
        ModelSchemaInfoInterface $schemaInfo,
531
        JsonApiQueryParserInterface $queryParser,
532
        JsonApiDataParserInterface $dataValidator,
533
        CrudInterface $parentCrud,
534
        EncoderInterface $encoder,
535
        FactoryInterface $errorFactory,
536
        FormatterFactoryInterface $formatterFactory
537
    ): ResponseInterface {
538
        /** @var SchemaInterface $schemaClass */
539
        assert(array_key_exists(SchemaInterface::class, class_implements($schemaClass)) === true);
540
        $modelClass = $schemaClass::MODEL;
541
        assert($schemaInfo->hasRelationship($modelClass, $modelRelName));
542
        assert($schemaInfo->getRelationshipType($modelClass, $modelRelName) === RelationshipTypes::BELONGS_TO_MANY);
543
544
        $jsonData = static::readJsonFromRequest($requestBody, $errorFactory, $formatterFactory);
545
        $captures = $dataValidator->assertRelationship($index, $jsonRelName, $jsonData)->getCaptures();
546
        $relIds   = $captures[$jsonRelName];
547
548
        $validatedIndex = $queryParser->parse($index)->getIdentity();
549
        $parentCrud->createInBelongsToManyRelationship($validatedIndex, $modelRelName, $relIds);
550
551
        $responses = static::defaultCreateResponses($requestUri, $encoder);
552
        $response  = $responses->getCodeResponse(204);
553
554
        return $response;
555
    }
556
557
    /** @noinspection PhpTooManyParametersInspection
558
     * @param string                      $index
559
     * @param string                      $jsonRelName
560
     * @param string                      $modelRelName
561
     * @param UriInterface                $requestUri
562
     * @param string                      $requestBody
563
     * @param string                      $schemaClass
564
     * @param ModelSchemaInfoInterface    $schemaInfo
565
     * @param JsonApiQueryParserInterface $queryParser
566
     * @param JsonApiDataParserInterface  $dataValidator
567
     * @param CrudInterface               $parentCrud
568
     * @param EncoderInterface            $encoder
569
     * @param FactoryInterface            $errorFactory
570
     * @param FormatterFactoryInterface   $formatterFactory
571
     *
572
     * @return ResponseInterface
573
     *
574
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
575
     */
576
    protected static function defaultDeleteInRelationshipHandler(
577
        string $index,
578
        string $jsonRelName,
579
        string $modelRelName,
580
        UriInterface $requestUri,
581
        string $requestBody,
582
        string $schemaClass,
583
        ModelSchemaInfoInterface $schemaInfo,
584
        JsonApiQueryParserInterface $queryParser,
585
        JsonApiDataParserInterface $dataValidator,
586
        CrudInterface $parentCrud,
587
        EncoderInterface $encoder,
588
        FactoryInterface $errorFactory,
589
        FormatterFactoryInterface $formatterFactory
590
    ): ResponseInterface {
591
        /** @var SchemaInterface $schemaClass */
592
        assert(array_key_exists(SchemaInterface::class, class_implements($schemaClass)) === true);
593
        $modelClass = $schemaClass::MODEL;
594
        assert($schemaInfo->hasRelationship($modelClass, $modelRelName));
595
        assert($schemaInfo->getRelationshipType($modelClass, $modelRelName) === RelationshipTypes::BELONGS_TO_MANY);
596
597
        $jsonData = static::readJsonFromRequest($requestBody, $errorFactory, $formatterFactory);
598
        $captures = $dataValidator->assertRelationship($index, $jsonRelName, $jsonData)->getCaptures();
599
        $relIds   = $captures[$jsonRelName];
600
601
        $validatedIndex = $queryParser->parse($index)->getIdentity();
602
        $parentCrud->removeInBelongsToManyRelationship($validatedIndex, $modelRelName, $relIds);
603
604
        $responses = static::defaultCreateResponses($requestUri, $encoder);
605
        $response  = $responses->getCodeResponse(204);
606
607
        return $response;
608
    }
609
610
    /** @noinspection PhpTooManyParametersInspection
611
     * @param string                      $index
612
     * @param string                      $jsonRelName
613
     * @param string                      $modelRelName
614
     * @param UriInterface                $requestUri
615
     * @param string                      $requestBody
616
     * @param string                      $schemaClass
617
     * @param ModelSchemaInfoInterface    $schemaInfo
618
     * @param JsonApiQueryParserInterface $queryParser
619
     * @param JsonApiDataParserInterface  $dataValidator
620
     * @param CrudInterface               $crud
621
     * @param EncoderInterface            $encoder
622
     * @param FactoryInterface            $errorFactory
623
     * @param FormatterFactoryInterface   $formatterFactory
624
     *
625
     * @return ResponseInterface
626
     *
627
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
628
     */
629
    protected static function defaultReplaceInRelationship(
630
        string $index,
631
        string $jsonRelName,
632
        string $modelRelName,
633
        UriInterface $requestUri,
634
        string $requestBody,
635
        string $schemaClass,
636
        ModelSchemaInfoInterface $schemaInfo,
637
        JsonApiQueryParserInterface $queryParser,
638
        JsonApiDataParserInterface $dataValidator,
639
        CrudInterface $crud,
640
        EncoderInterface $encoder,
641
        FactoryInterface $errorFactory,
642
        FormatterFactoryInterface $formatterFactory
643
    ): ResponseInterface {
644
        /** @var SchemaInterface $schemaClass */
645
        assert(array_key_exists(SchemaInterface::class, class_implements($schemaClass)) === true);
646
        $modelClass = $schemaClass::MODEL;
647
        assert($schemaInfo->hasRelationship($modelClass, $modelRelName));
648
        assert(
649
            ($type =$schemaInfo->getRelationshipType($modelClass, $modelRelName)) === RelationshipTypes::BELONGS_TO ||
650
            $type === RelationshipTypes::BELONGS_TO_MANY
651
        );
652
653
        $jsonData = static::readJsonFromRequest($requestBody, $errorFactory, $formatterFactory);
654
        $captures = $dataValidator->assertRelationship($index, $jsonRelName, $jsonData)->getCaptures();
655
656
        // If we are here then we have something in 'data' section.
657
658
        $validatedIndex = $queryParser->parse($index)->getIdentity();
659
        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...
660
661
        $updated = $crud->update($validatedIndex, $attributes, $toMany);
662
663
        return static::defaultUpdateResponse($updated, $index, $requestUri, $crud, $encoder);
664
    }
665
666
    /**
667
     * @param ContainerInterface $container
668
     * @param string             $rulesClass
669
     *
670
     * @return JsonApiQueryParserInterface
671
     *
672
     * @throws ContainerExceptionInterface
673
     * @throws NotFoundExceptionInterface
674
     */
675
    protected static function defaultCreateQueryParser(
676
        ContainerInterface $container,
677
        string $rulesClass = DefaultQueryValidationRules::class
678
    ): JsonApiQueryParserInterface {
679
        static::assertClassImplements($rulesClass, JsonApiQueryRulesInterface::class);
680
681
        /** @var JsonApiParserFactoryInterface $factory */
682
        $factory = $container->get(JsonApiParserFactoryInterface::class);
683
        $parser  = $factory->createQueryParser($rulesClass);
684
685
        return $parser;
686
    }
687
688
    /**
689
     * @param ContainerInterface $container
690
     * @param string             $rulesClass
691
     *
692
     * @return JsonApiDataParserInterface
693
     *
694
     * @throws ContainerExceptionInterface
695
     * @throws NotFoundExceptionInterface
696
     */
697
    protected static function defaultCreateDataParser(
698
        ContainerInterface $container,
699
        string $rulesClass
700
    ): JsonApiDataParserInterface {
701
        static::assertClassImplements($rulesClass, JsonApiDataRulesInterface::class);
702
703
        /** @var JsonApiParserFactoryInterface $factory */
704
        $factory = $container->get(JsonApiParserFactoryInterface::class);
705
        $parser  = $factory->createDataParser($rulesClass);
706
707
        return $parser;
708
    }
709
710
    /**
711
     * @param ContainerInterface $container
712
     * @param string             $rulesClass
713
     *
714
     * @return FormValidatorInterface
715
     *
716
     * @throws ContainerExceptionInterface
717
     * @throws NotFoundExceptionInterface
718
     */
719
    protected static function defaultCreateFormValidator(
720
        ContainerInterface $container,
721
        string $rulesClass
722
    ): FormValidatorInterface {
723
        static::assertClassImplements($rulesClass, FormRulesInterface::class);
724
725
        /** @var FormValidatorFactoryInterface $factory */
726
        $factory   = $container->get(FormValidatorFactoryInterface::class);
727
        $validator = $factory->createValidator($rulesClass);
728
729
        return $validator;
730
    }
731
732
    /**
733
     * @param ContainerInterface $container
734
     * @param string             $schemaClass
735
     *
736
     * @return ParametersMapperInterface
737
     *
738
     * @throws ContainerExceptionInterface
739
     * @throws NotFoundExceptionInterface
740
     */
741
    protected static function defaultCreateParameterMapper(
742
        ContainerInterface $container,
743
        string $schemaClass
744
    ): ParametersMapperInterface {
745
        static::assertClassImplements($schemaClass, SchemaInterface::class);
746
747
        /** @var SchemaInterface $schemaClass */
748
        $jsonResourceType = $schemaClass::TYPE;
749
750
        /** @var ParametersMapperInterface $mapper */
751
        $mapper = $container->get(ParametersMapperInterface::class);
752
        $mapper->selectRootSchemaByResourceType($jsonResourceType);
753
754
        return $mapper;
755
    }
756
757
    /**
758
     * @param JsonApiQueryParserInterface $queryParser
759
     * @param EncoderInterface            $encoder
760
     *
761
     * @return void
762
     */
763
    protected static function defaultApplyIncludesAndFieldSetsToEncoder(
764
        JsonApiQueryParserInterface $queryParser,
765
        EncoderInterface $encoder
766
    ): void {
767
        if ($queryParser->hasIncludes() === true) {
768
            $paths = array_keys($queryParser->getIncludes());
769
            $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...
770
        }
771
        if ($queryParser->hasFields() === true) {
772
            $encoder->withFieldSets($queryParser->getFields());
773
        }
774
    }
775
776
    /**
777
     * @param ContainerInterface $container
778
     * @param string|null        $class
779
     *
780
     * @return CrudInterface
781
     *
782
     * @throws ContainerExceptionInterface
783
     * @throws NotFoundExceptionInterface
784
     */
785
    protected static function defaultCreateApi(ContainerInterface $container, string $class): CrudInterface
786
    {
787
        static::assertClassImplements($class, CrudInterface::class);
788
789
        /** @var FactoryInterface $factory */
790
        $factory = $container->get(FactoryInterface::class);
791
        $api     = $factory->createApi($class);
792
793
        return $api;
794
    }
795
796
    /**
797
     * @param UriInterface     $requestUri
798
     * @param EncoderInterface $encoder
799
     *
800
     * @return ResponsesInterface
801
     */
802
    protected static function defaultCreateResponses(
803
        UriInterface $requestUri,
804
        EncoderInterface $encoder
805
    ): ResponsesInterface {
806
        $encoder->forOriginalUri($requestUri);
807
        $responses = new Responses(
808
            new MediaType(MediaTypeInterface::JSON_API_TYPE, MediaTypeInterface::JSON_API_SUB_TYPE),
809
            $encoder
810
        );
811
812
        return $responses;
813
    }
814
815
    /**
816
     * Developers can override the method in order to add/remove some data for `create`/`update` inputs.
817
     *
818
     * @param string                    $requestBody
819
     * @param FactoryInterface          $errorFactory
820
     * @param FormatterFactoryInterface $formatterFactory
821
     * @param string                    $messagesNamespace
822
     * @param string                    $errorMessage
823
     *
824
     * @return array
825
     */
826
    protected static function readJsonFromRequest(
827
        string $requestBody,
828
        FactoryInterface $errorFactory,
829
        FormatterFactoryInterface $formatterFactory,
830
        string $messagesNamespace = S::GENERIC_NAMESPACE,
831
        string $errorMessage = Generic::MSG_ERR_INVALID_ELEMENT
832
    ): array {
833
        if (empty($requestBody) === true || ($json = json_decode($requestBody, true)) === null) {
834
            $formatter = $formatterFactory->createFormatter($messagesNamespace);
835
            $errors    = $errorFactory->createErrorCollection();
836
            $errors->addDataError($formatter->formatMessage($errorMessage));
837
838
            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...
839
        }
840
841
        return $json;
842
    }
843
844
    /**
845
     * Developers can override the method in order to use custom data mapping from a Schema to Model.
846
     *
847
     * @param iterable                 $captures
848
     * @param string                   $schemaClass
849
     * @param ModelSchemaInfoInterface $schemaInfo
850
     *
851
     * @return array
852
     */
853
    protected static function mapSchemaDataToModelData(
854
        iterable $captures,
855
        string $schemaClass,
856
        ModelSchemaInfoInterface $schemaInfo
857
    ): array {
858
        static::assertClassImplements($schemaClass, SchemaInterface::class);
859
860
        /** @var SchemaInterface $schemaClass */
861
        static::assertClassImplements($modelClass = $schemaClass::MODEL, ModelInterface::class);
862
        /** @var ModelInterface $modelClass */
863
864
        $index         = null;
865
        $fields        = [];
866
        $toManyIndexes = [];
867
        foreach ($captures as $name => $value) {
868
            assert(is_string($name) === true);
869
            if ($name === DI::KEYWORD_ID) {
870
                $index = $value;
871
            } elseif ($schemaClass::hasAttributeMapping($name) === true) {
872
                $fieldName          = $schemaClass::getAttributeMapping($name);
873
                $fields[$fieldName] = $value;
874
            } elseif ($schemaClass::hasRelationshipMapping($name) === true) {
875
                $modelRelName = $schemaClass::getRelationshipMapping($name);
876
                $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...
877
                if ($relType === RelationshipTypes::BELONGS_TO) {
878
                    $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...
879
                    $fields[$fkName] = $value;
880
                } elseif ($relType === RelationshipTypes::BELONGS_TO_MANY) {
881
                    $toManyIndexes[$modelRelName] = $value;
882
                }
883
            }
884
        }
885
886
        $result = [$index, $fields, $toManyIndexes];
887
888
        return $result;
889
    }
890
891
    /**
892
     * @param mixed                $model
893
     * @param UriInterface         $requestUri
894
     * @param JsonSchemasInterface $jsonSchemas
895
     *
896
     * @return string
897
     */
898
    protected static function defaultGetResourceUrl(
899
        $model,
900
        UriInterface $requestUri,
901
        JsonSchemasInterface $jsonSchemas
902
    ): string {
903
        $schema    = $jsonSchemas->getSchema($model);
904
        $selfLink  = $schema->getSelfLink($model);
905
        $urlPrefix = (string)$requestUri->withPath('')->withQuery('')->withFragment('');
906
        $fullUrl   = $selfLink->getStringRepresentation($urlPrefix);
907
908
        return $fullUrl;
909
    }
910
911
    /**
912
     * @param null|string $value
913
     *
914
     * @return void
915
     */
916
    private static function assertClassValueDefined(?string $value): void
917
    {
918
        assert(empty($value) === false, 'Value should be defined in `' . static::class . '`.');
919
    }
920
921
    /**
922
     * @param string $class
923
     * @param string $interface
924
     *
925
     * @return void
926
     */
927
    private static function assertClassImplements(string $class, string $interface): void
928
    {
929
        assert(
930
            array_key_exists($interface, class_implements($class)) === true,
931
            "Class `$class` should implement `" . $interface . '` interface.'
932
        );
933
    }
934
}
935