Issues (197)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Http/Traits/DefaultControllerMethodsTrait.php (7 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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

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

Loading history...
260
     */
261
    protected static function defaultCreate(
262
        string $requestBody,
263
        string $schemaClass,
264
        ModelSchemaInfoInterface $schemaInfo,
265 2
        JsonApiDataParserInterface $parser,
266 2
        CrudInterface $crud,
267 2
        FactoryInterface $errorFactory,
268 2
        FormatterFactoryInterface $formatterFactory
269
    ): string {
270
        $jsonData = static::readJsonFromRequest(
271 2
            $requestBody,
272
            $errorFactory,
273 2
            $formatterFactory
274
        );
275
276 2
        $captures = $parser->assert($jsonData)->getCaptures();
277 1
278 1
        list ($index, $attributes, $toMany) = static::mapSchemaDataToModelData($captures, $schemaClass, $schemaInfo);
279 1
280 1
        try {
281 1
            $index = $crud->create($index, $attributes, $toMany);
282 1
        } /** @noinspection PhpRedundantCatchClauseInspection */ catch (UniqueConstraintViolationException $exception) {
283 1
            $errors    = $errorFactory->createErrorCollection();
284
            $formatter = $formatterFactory->createFormatter(Messages::NAMESPACE_NAME);
285 1
            $title     = $formatter->formatMessage(Messages::MSG_ERR_CANNOT_CREATE_NON_UNIQUE_RESOURCE);
286
            $details   = null;
287
            $errorCode = JsonApiResponse::HTTP_CONFLICT;
288 1
            $errors->addDataError($title, $details, (string)$errorCode);
289
290
            throw new JsonApiException($errors);
0 ignored issues
show
$errors is of type object<Neomerx\JsonApi\Schema\ErrorCollection>, but the function expects a object<Neomerx\JsonApi\C...pi\Exceptions\iterable>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
291
        }
292
293
        return $index;
294
    }
295
296
    /**
297
     * @param string               $index
298
     * @param UriInterface         $requestUri
299
     * @param CrudInterface        $crud
300 1
     * @param JsonSchemasInterface $jsonSchemas
301
     * @param EncoderInterface     $encoder
302
     *
303
     * @return ResponseInterface
304
     */
305
    protected static function defaultCreateResponse(
306
        string $index,
307 1
        UriInterface $requestUri,
308 1
        CrudInterface $crud,
309
        JsonSchemasInterface $jsonSchemas,
310 1
        EncoderInterface $encoder
311 1
    ): ResponseInterface {
312 1
        $model = $crud->read($index);
313
        assert($model !== null && !($model instanceof PaginatedDataInterface));
314 1
315
        $responses = static::defaultCreateResponses($requestUri, $encoder);
316
        $fullUrl   = static::defaultGetResourceUrl($model, $requestUri, $jsonSchemas);
317
        $response  = $responses->getCreatedResponse($model, $fullUrl);
318
319
        return $response;
320
    }
321
322
    /** @noinspection PhpTooManyParametersInspection
323
     * @param string                     $index
324
     * @param UriInterface               $requestUri
325
     * @param string                     $requestBody
326
     * @param string                     $schemaClass
327
     * @param ModelSchemaInfoInterface   $schemaInfo
328
     * @param JsonApiDataParserInterface $parser
329
     * @param CrudInterface              $crud
330
     * @param EncoderInterface           $encoder
331
     * @param FactoryInterface           $errorFactory
332
     * @param FormatterFactoryInterface  $formatterFactory
333 6
     *
334
     * @return ResponseInterface
335
     *
336
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
337
     */
338
    protected static function defaultUpdateHandler(
339
        string $index,
340
        UriInterface $requestUri,
341
        string $requestBody,
342
        string $schemaClass,
343
        ModelSchemaInfoInterface $schemaInfo,
344
        JsonApiDataParserInterface $parser,
345
        CrudInterface $crud,
346
        EncoderInterface $encoder,
347 6
        FactoryInterface $errorFactory,
348 6
        FormatterFactoryInterface $formatterFactory
349 6
    ): ResponseInterface {
350 6
        // some of the users want to reuse default `update` but have a custom part for responses
351 6
        // to meet this requirement it is split into two parts.
352 6
        $updated = static::defaultUpdate(
353 6
            $index,
354 6
            $requestBody,
355 6
            $schemaClass,
356
            $schemaInfo,
357
            $parser,
358 2
            $crud,
359
            $errorFactory,
360
            $formatterFactory
361
        );
362
363
        return static::defaultUpdateResponse($updated, $index, $requestUri, $crud, $encoder);
364
    }
365
366
    /** @noinspection PhpTooManyParametersInspection
367
     * @param string                     $index
368
     * @param string                     $requestBody
369
     * @param string                     $schemaClass
370
     * @param ModelSchemaInfoInterface   $schemaInfo
371
     * @param JsonApiDataParserInterface $parser
372
     * @param CrudInterface              $crud
373
     * @param FactoryInterface           $errorFactory
374
     * @param FormatterFactoryInterface  $formatterFactory
375
     *
376 6
     * @return int
377
     *
378
     * @SuppressWarnings(PHPMD.ElseExpression)
379
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
380
     */
381
    protected static function defaultUpdate(
382
        string $index,
383
        string $requestBody,
384
        string $schemaClass,
385
        ModelSchemaInfoInterface $schemaInfo,
386 6
        JsonApiDataParserInterface $parser,
387 6
        CrudInterface $crud,
388 6
        FactoryInterface $errorFactory,
389 6
        FormatterFactoryInterface $formatterFactory
390
    ): int {
391
        $jsonData = static::readJsonFromRequest(
392
            $requestBody,
393 5
            $errorFactory,
394 5
            $formatterFactory
395 4
        );
396 1
397 1
        // check that index in data and URL are identical
398 1
        $indexValue = $jsonData[DI::KEYWORD_DATA][DI::KEYWORD_ID] ?? null;
399
        if (empty($indexValue) === false) {
400 4
            if ($indexValue !== $index) {
401
                $errors    = $errorFactory->createErrorCollection();
402
                $formatter = $formatterFactory->createFormatter(Messages::NAMESPACE_NAME);
403
                $errors->addDataIdError($formatter->formatMessage(Messages::MSG_ERR_INVALID_ARGUMENT));
404 1
405
                throw new JsonApiException($errors);
0 ignored issues
show
$errors is of type object<Neomerx\JsonApi\Schema\ErrorCollection>, but the function expects a object<Neomerx\JsonApi\C...pi\Exceptions\iterable>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
406
            }
407 4
        } else {
408
            // put the index to data for our convenience
409 3
            $jsonData[DI::KEYWORD_DATA][DI::KEYWORD_ID] = $index;
410
        }
411
        // validate the data
412 3
        $captures = $parser->assert($jsonData)->getCaptures();
413 1
414 1
        list ($index, $attributes, $toMany) = static::mapSchemaDataToModelData($captures, $schemaClass, $schemaInfo);
415 1
416
        try {
417 1
            $updated = $crud->update((string)$index, $attributes, $toMany);
418 1
        } /** @noinspection PhpRedundantCatchClauseInspection */ catch (UniqueConstraintViolationException $exception) {
419 1
            $errors    = $errorFactory->createErrorCollection();
420 1
            $formatter = $formatterFactory->createFormatter(Messages::NAMESPACE_NAME);
421
            $title     = $formatter
422 1
                ->formatMessage(Messages::MSG_ERR_CANNOT_UPDATE_WITH_UNIQUE_CONSTRAINT_VIOLATION);
423
            $details   = null;
424
            $errorCode = JsonApiResponse::HTTP_CONFLICT;
425 2
            $errors->addDataError($title, $details, (string)$errorCode);
426
427
            throw new JsonApiException($errors);
0 ignored issues
show
$errors is of type object<Neomerx\JsonApi\Schema\ErrorCollection>, but the function expects a object<Neomerx\JsonApi\C...pi\Exceptions\iterable>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
428
        }
429
430
        return $updated;
431
    }
432
433
    /**
434
     * @param int              $updated
435
     * @param string           $index
436
     * @param UriInterface     $requestUri
437
     * @param CrudInterface    $crud
438
     * @param EncoderInterface $encoder
439 3
     *
440
     * @return ResponseInterface
441
     *
442
     * @SuppressWarnings(PHPMD.ElseExpression)
443
     */
444
    protected static function defaultUpdateResponse(
445
        int $updated,
446 3
        string $index,
447 3
        UriInterface $requestUri,
448 2
        CrudInterface $crud,
449 2
        EncoderInterface $encoder
450
    ): ResponseInterface {
451 1
        $responses = static::defaultCreateResponses($requestUri, $encoder);
452
        if ($updated > 0 && ($model = $crud->read($index)) !== null) {
453
            assert(!($model instanceof PaginatedDataInterface));
454 3
            $response = $responses->getContentResponse($model);
455
        } else {
456
            $response = $responses->getCodeResponse(404);
457
        }
458
459
        return $response;
460
    }
461
462
    /**
463
     * @param string                      $index
464
     * @param UriInterface                $requestUri
465
     * @param JsonApiQueryParserInterface $queryParser
466 1
     * @param CrudInterface               $crud
467
     * @param EncoderInterface            $encoder
468
     *
469
     * @return ResponseInterface
470
     */
471
    protected static function defaultDeleteHandler(
472
        string $index,
473 1
        UriInterface $requestUri,
474 1
        JsonApiQueryParserInterface $queryParser,
475
        CrudInterface $crud,
476 1
        EncoderInterface $encoder
477 1
    ): ResponseInterface {
478
        $validatedIndex = (string)$queryParser->parse($index)->getIdentity();
479 1
        $crud->remove($validatedIndex);
480
481
        $responses = static::defaultCreateResponses($requestUri, $encoder);
482
        $response  = $responses->getCodeResponse(204);
483
484
        return $response;
485
    }
486
487
    /** @noinspection PhpTooManyParametersInspection
488
     * @param string                      $index
489
     * @param string                      $jsonRelName
490
     * @param string                      $modelRelName
491
     * @param UriInterface                $requestUri
492
     * @param string                      $requestBody
493
     * @param string                      $schemaClass
494
     * @param ModelSchemaInfoInterface    $schemaInfo
495
     * @param JsonApiQueryParserInterface $queryParser
496
     * @param JsonApiDataParserInterface  $dataValidator
497
     * @param CrudInterface               $parentCrud
498
     * @param EncoderInterface            $encoder
499
     * @param FactoryInterface            $errorFactory
500
     * @param FormatterFactoryInterface   $formatterFactory
501 2
     *
502
     * @return ResponseInterface
503
     *
504
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
505
     */
506
    protected static function defaultAddInRelationshipHandler(
507
        string $index,
508
        string $jsonRelName,
509
        string $modelRelName,
510
        UriInterface $requestUri,
511
        string $requestBody,
512
        string $schemaClass,
513
        ModelSchemaInfoInterface $schemaInfo,
514
        JsonApiQueryParserInterface $queryParser,
515
        JsonApiDataParserInterface $dataValidator,
516
        CrudInterface $parentCrud,
517 2
        EncoderInterface $encoder,
518 2
        FactoryInterface $errorFactory,
519 2
        FormatterFactoryInterface $formatterFactory
520 2
    ): ResponseInterface {
521
        /** @var SchemaInterface $schemaClass */
522 2
        assert(array_key_exists(SchemaInterface::class, class_implements($schemaClass)) === true);
523 2
        $modelClass = $schemaClass::MODEL;
524 1
        assert($schemaInfo->hasRelationship($modelClass, $modelRelName));
525
        assert($schemaInfo->getRelationshipType($modelClass, $modelRelName) === RelationshipTypes::BELONGS_TO_MANY);
526 1
527 1
        $jsonData = static::readJsonFromRequest($requestBody, $errorFactory, $formatterFactory);
528
        $captures = $dataValidator->assertRelationship($index, $jsonRelName, $jsonData)->getCaptures();
529 1
        $relIds   = $captures[$jsonRelName];
530 1
531
        $validatedIndex = (string)$queryParser->parse($index)->getIdentity();
532 1
        $parentCrud->createInBelongsToManyRelationship($validatedIndex, $modelRelName, $relIds);
533
534
        $responses = static::defaultCreateResponses($requestUri, $encoder);
535
        $response  = $responses->getCodeResponse(204);
536
537
        return $response;
538
    }
539
540
    /** @noinspection PhpTooManyParametersInspection
541
     * @param string                      $index
542
     * @param string                      $jsonRelName
543
     * @param string                      $modelRelName
544
     * @param UriInterface                $requestUri
545
     * @param string                      $requestBody
546
     * @param string                      $schemaClass
547
     * @param ModelSchemaInfoInterface    $schemaInfo
548
     * @param JsonApiQueryParserInterface $queryParser
549
     * @param JsonApiDataParserInterface  $dataValidator
550
     * @param CrudInterface               $parentCrud
551
     * @param EncoderInterface            $encoder
552
     * @param FactoryInterface            $errorFactory
553
     * @param FormatterFactoryInterface   $formatterFactory
554 1
     *
555
     * @return ResponseInterface
556
     *
557
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
558
     */
559
    protected static function defaultDeleteInRelationshipHandler(
560
        string $index,
561
        string $jsonRelName,
562
        string $modelRelName,
563
        UriInterface $requestUri,
564
        string $requestBody,
565
        string $schemaClass,
566
        ModelSchemaInfoInterface $schemaInfo,
567
        JsonApiQueryParserInterface $queryParser,
568
        JsonApiDataParserInterface $dataValidator,
569
        CrudInterface $parentCrud,
570 1
        EncoderInterface $encoder,
571 1
        FactoryInterface $errorFactory,
572 1
        FormatterFactoryInterface $formatterFactory
573 1
    ): ResponseInterface {
574
        /** @var SchemaInterface $schemaClass */
575 1
        assert(array_key_exists(SchemaInterface::class, class_implements($schemaClass)) === true);
576 1
        $modelClass = $schemaClass::MODEL;
577 1
        assert($schemaInfo->hasRelationship($modelClass, $modelRelName));
578
        assert($schemaInfo->getRelationshipType($modelClass, $modelRelName) === RelationshipTypes::BELONGS_TO_MANY);
579 1
580 1
        $jsonData = static::readJsonFromRequest($requestBody, $errorFactory, $formatterFactory);
581
        $captures = $dataValidator->assertRelationship($index, $jsonRelName, $jsonData)->getCaptures();
582 1
        $relIds   = $captures[$jsonRelName];
583 1
584
        $validatedIndex = (string)$queryParser->parse($index)->getIdentity();
585 1
        $parentCrud->removeInBelongsToManyRelationship($validatedIndex, $modelRelName, $relIds);
586
587
        $responses = static::defaultCreateResponses($requestUri, $encoder);
588
        $response  = $responses->getCodeResponse(204);
589
590
        return $response;
591
    }
592
593
    /** @noinspection PhpTooManyParametersInspection
594
     * @param string                      $index
595
     * @param string                      $jsonRelName
596
     * @param string                      $modelRelName
597
     * @param UriInterface                $requestUri
598
     * @param string                      $requestBody
599
     * @param string                      $schemaClass
600
     * @param ModelSchemaInfoInterface    $schemaInfo
601
     * @param JsonApiQueryParserInterface $queryParser
602
     * @param JsonApiDataParserInterface  $dataValidator
603
     * @param CrudInterface               $crud
604
     * @param EncoderInterface            $encoder
605
     * @param FactoryInterface            $errorFactory
606
     * @param FormatterFactoryInterface   $formatterFactory
607 2
     *
608
     * @return ResponseInterface
609
     *
610
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
611
     */
612
    protected static function defaultReplaceInRelationship(
613
        string $index,
614
        string $jsonRelName,
615
        string $modelRelName,
616
        UriInterface $requestUri,
617
        string $requestBody,
618
        string $schemaClass,
619
        ModelSchemaInfoInterface $schemaInfo,
620
        JsonApiQueryParserInterface $queryParser,
621
        JsonApiDataParserInterface $dataValidator,
622
        CrudInterface $crud,
623 2
        EncoderInterface $encoder,
624 2
        FactoryInterface $errorFactory,
625 2
        FormatterFactoryInterface $formatterFactory
626 2
    ): ResponseInterface {
627 2
        /** @var SchemaInterface $schemaClass */
628 2
        assert(array_key_exists(SchemaInterface::class, class_implements($schemaClass)) === true);
629
        $modelClass = $schemaClass::MODEL;
630
        assert($schemaInfo->hasRelationship($modelClass, $modelRelName));
631 2
        assert(
632 2
            ($type =$schemaInfo->getRelationshipType($modelClass, $modelRelName)) === RelationshipTypes::BELONGS_TO ||
633
            $type === RelationshipTypes::BELONGS_TO_MANY
634
        );
635
636 1
        $jsonData = static::readJsonFromRequest($requestBody, $errorFactory, $formatterFactory);
637 1
        $captures = $dataValidator->assertRelationship($index, $jsonRelName, $jsonData)->getCaptures();
638
639 1
        // If we are here then we have something in 'data' section.
640
641 1
        $validatedIndex = (string)$queryParser->parse($index)->getIdentity();
642
        list (, $attributes, $toMany) = static::mapSchemaDataToModelData($captures, $schemaClass, $schemaInfo);
0 ignored issues
show
$schemaClass is of type object<Limoncello\Flute\...Schema\SchemaInterface>, but the function expects a string.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
643
644
        $updated = $crud->update($validatedIndex, $attributes, $toMany);
645
646
        return static::defaultUpdateResponse($updated, $index, $requestUri, $crud, $encoder);
647
    }
648
649
    /**
650
     * @param ContainerInterface $container
651
     * @param string             $rulesClass
652
     *
653 22
     * @return JsonApiQueryParserInterface
654
     *
655
     * @throws ContainerExceptionInterface
656
     * @throws NotFoundExceptionInterface
657 22
     */
658
    protected static function defaultCreateQueryParser(
659
        ContainerInterface $container,
660 22
        string $rulesClass = DefaultQueryValidationRules::class
661 22
    ): JsonApiQueryParserInterface {
662
        static::assertClassImplements($rulesClass, JsonApiQueryRulesInterface::class);
663 22
664
        /** @var JsonApiParserFactoryInterface $factory */
665
        $factory = $container->get(JsonApiParserFactoryInterface::class);
666
        $parser  = $factory->createQueryParser($rulesClass);
667
668
        return $parser;
669
    }
670
671
    /**
672
     * @param ContainerInterface $container
673
     * @param string             $rulesClass
674
     *
675 13
     * @return JsonApiDataParserInterface
676
     *
677
     * @throws ContainerExceptionInterface
678
     * @throws NotFoundExceptionInterface
679 13
     */
680
    protected static function defaultCreateDataParser(
681
        ContainerInterface $container,
682 13
        string $rulesClass
683 13
    ): JsonApiDataParserInterface {
684
        static::assertClassImplements($rulesClass, JsonApiDataRulesInterface::class);
685 13
686
        /** @var JsonApiParserFactoryInterface $factory */
687
        $factory = $container->get(JsonApiParserFactoryInterface::class);
688
        $parser  = $factory->createDataParser($rulesClass);
689
690
        return $parser;
691
    }
692
693
    /**
694
     * @param ContainerInterface $container
695
     * @param string             $rulesClass
696
     *
697 1
     * @return FormValidatorInterface
698
     *
699
     * @throws ContainerExceptionInterface
700
     * @throws NotFoundExceptionInterface
701 1
     */
702
    protected static function defaultCreateFormValidator(
703
        ContainerInterface $container,
704 1
        string $rulesClass
705 1
    ): FormValidatorInterface {
706
        static::assertClassImplements($rulesClass, FormRulesInterface::class);
707 1
708
        /** @var FormValidatorFactoryInterface $factory */
709
        $factory   = $container->get(FormValidatorFactoryInterface::class);
710
        $validator = $factory->createValidator($rulesClass);
711
712
        return $validator;
713
    }
714
715
    /**
716
     * @param ContainerInterface $container
717
     * @param string             $schemaClass
718
     *
719 16
     * @return ParametersMapperInterface
720
     *
721
     * @throws ContainerExceptionInterface
722
     * @throws NotFoundExceptionInterface
723 16
     */
724
    protected static function defaultCreateParameterMapper(
725
        ContainerInterface $container,
726 16
        string $schemaClass
727
    ): ParametersMapperInterface {
728
        static::assertClassImplements($schemaClass, SchemaInterface::class);
729 16
730 16
        /** @var SchemaInterface $schemaClass */
731
        $jsonResourceType = $schemaClass::TYPE;
732 16
733
        /** @var ParametersMapperInterface $mapper */
734
        $mapper = $container->get(ParametersMapperInterface::class);
735
        $mapper->selectRootSchemaByResourceType($jsonResourceType);
736
737
        return $mapper;
738
    }
739
740
    /**
741 14
     * @param JsonApiQueryParserInterface $queryParser
742
     * @param EncoderInterface            $encoder
743
     *
744
     * @return void
745 14
     */
746 3
    protected static function defaultApplyIncludesAndFieldSetsToEncoder(
747 3
        JsonApiQueryParserInterface $queryParser,
748
        EncoderInterface $encoder
749 14
    ): void {
750 1
        if ($queryParser->hasIncludes() === true) {
751
            $paths = array_keys($queryParser->getIncludes());
752
            $encoder->withIncludedPaths($paths);
0 ignored issues
show
$paths is of type array, but the function expects a object<Neomerx\JsonApi\C...racts\Encoder\iterable>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
753
        }
754
        if ($queryParser->hasFields() === true) {
755
            $encoder->withFieldSets($queryParser->getFields());
756
        }
757
    }
758
759
    /**
760
     * @param ContainerInterface $container
761
     * @param string|null        $class
762
     *
763 30
     * @return CrudInterface
764
     *
765 30
     * @throws ContainerExceptionInterface
766
     * @throws NotFoundExceptionInterface
767
     */
768 30
    protected static function defaultCreateApi(ContainerInterface $container, string $class): CrudInterface
769 30
    {
770
        static::assertClassImplements($class, CrudInterface::class);
771 30
772
        /** @var FactoryInterface $factory */
773
        $factory = $container->get(FactoryInterface::class);
774
        $api     = $factory->createApi($class);
775
776
        return $api;
777
    }
778
779
    /**
780 21
     * @param UriInterface     $requestUri
781
     * @param EncoderInterface $encoder
782
     *
783
     * @return ResponsesInterface
784 21
     */
785 21
    protected static function defaultCreateResponses(
786 21
        UriInterface $requestUri,
787 21
        EncoderInterface $encoder
788
    ): ResponsesInterface {
789
        $encoder->forOriginalUri($requestUri);
790 21
        $responses = new Responses(
791
            new MediaType(MediaTypeInterface::JSON_API_TYPE, MediaTypeInterface::JSON_API_SUB_TYPE),
792
            $encoder
793
        );
794
795
        return $responses;
796
    }
797
798
    /**
799
     * Developers can override the method in order to add/remove some data for `create`/`update` inputs.
800
     *
801
     * @param string                    $requestBody
802 13
     * @param FactoryInterface          $errorFactory
803
     * @param FormatterFactoryInterface $formatterFactory
804
     *
805
     * @return array
806
     */
807 13
    protected static function readJsonFromRequest(
808 1
        string $requestBody,
809 1
        FactoryInterface $errorFactory,
810 1
        FormatterFactoryInterface $formatterFactory
811
    ): array {
812 1
        if (empty($requestBody) === true || ($json = json_decode($requestBody, true)) === null) {
813
            $formatter = $formatterFactory->createFormatter(Messages::NAMESPACE_NAME);
814
            $errors    = $errorFactory->createErrorCollection();
815 12
            $errors->addDataError($formatter->formatMessage(Messages::MSG_ERR_INVALID_JSON_DATA_IN_REQUEST));
816
817
            throw new JsonApiException($errors);
0 ignored issues
show
$errors is of type object<Neomerx\JsonApi\Schema\ErrorCollection>, but the function expects a object<Neomerx\JsonApi\C...pi\Exceptions\iterable>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
818
        }
819
820
        return $json;
821
    }
822
823
    /**
824
     * Developers can override the method in order to use custom data mapping from a Schema to Model.
825
     *
826
     * @param iterable                 $captures
827 6
     * @param string                   $schemaClass
828
     * @param ModelSchemaInfoInterface $schemaInfo
829
     *
830
     * @return array
831
     */
832 6
    protected static function mapSchemaDataToModelData(
833
        iterable $captures,
834
        string $schemaClass,
835 6
        ModelSchemaInfoInterface $schemaInfo
836
    ): array {
837 6
        static::assertClassImplements($schemaClass, SchemaInterface::class);
838 6
839 6
        /** @var SchemaInterface $schemaClass */
840 6
        static::assertClassImplements($modelClass = $schemaClass::MODEL, ModelInterface::class);
841 6
842 6
        $index         = null;
843 4
        $fields        = [];
844 6
        $toManyIndexes = [];
845 5
        foreach ($captures as $name => $value) {
846 5
            assert(is_string($name) === true);
847 6
            if ($name === DI::KEYWORD_ID) {
848 3
                $index = $value;
849 3
            } elseif ($schemaClass::hasAttributeMapping($name) === true) {
850 3
                $fieldName          = $schemaClass::getAttributeMapping($name);
851 3
                $fields[$fieldName] = $value;
852 3
            } elseif ($schemaClass::hasRelationshipMapping($name) === true) {
853 2
                $modelRelName = $schemaClass::getRelationshipMapping($name);
854 6
                $relType      = $schemaInfo->getRelationshipType($modelClass, $modelRelName);
855
                if ($relType === RelationshipTypes::BELONGS_TO) {
856
                    $fkName          = $schemaInfo->getForeignKey($modelClass, $modelRelName);
857
                    $fields[$fkName] = $value;
858
                } elseif ($relType === RelationshipTypes::BELONGS_TO_MANY) {
859 6
                    $toManyIndexes[$modelRelName] = $value;
860
                }
861 6
            }
862
        }
863
864
        $result = [$index, $fields, $toManyIndexes];
865
866
        return $result;
867
    }
868
869
    /**
870
     * @param mixed                $model
871 1
     * @param UriInterface         $requestUri
872
     * @param JsonSchemasInterface $jsonSchemas
873
     *
874
     * @return string
875
     */
876 1
    protected static function defaultGetResourceUrl(
877 1
        $model,
878 1
        UriInterface $requestUri,
879 1
        JsonSchemasInterface $jsonSchemas
880
    ): string {
881 1
        $schema    = $jsonSchemas->getSchema($model);
882
        $selfLink  = $schema->getSelfLink($model);
883
        $urlPrefix = (string)$requestUri->withPath('')->withQuery('')->withFragment('');
884
        $fullUrl   = $selfLink->getStringRepresentation($urlPrefix);
885
886
        return $fullUrl;
887
    }
888
889 30
    /**
890
     * @param null|string $value
891 30
     *
892
     * @return void
893
     */
894
    private static function assertClassValueDefined(?string $value): void
895
    {
896
        assert(empty($value) === false, 'Value should be defined in `' . static::class . '`.');
897
    }
898
899
    /**
900 31
     * @param string $class
901
     * @param string $interface
902 31
     *
903 31
     * @return void
904 31
     */
905
    private static function assertClassImplements(string $class, string $interface): void
906
    {
907
        assert(
908
            array_key_exists($interface, class_implements($class)) === true,
909
            "Class `$class` should implement `" . $interface . '` interface.'
910
        );
911
    }
912
}
913