GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( f8863b...48fd44 )
by Sergey
04:48
created

JsonApiService::createMatcher()   C

Complexity

Conditions 7
Paths 4

Size

Total Lines 25
Code Lines 14

Duplication

Lines 16
Ratio 64 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 16
loc 25
ccs 0
cts 20
cp 0
rs 6.7272
cc 7
eloc 14
nc 4
nop 1
crap 56
1
<?php
2
/*
3
 * This file is part of the reva2/jsonapi.
4
 *
5
 * (c) Sergey Revenko <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Reva2\JsonApi\Services;
12
13
use Doctrine\Common\Proxy\Exception\InvalidArgumentException;
14
use Neomerx\JsonApi\Contracts\Codec\CodecMatcherInterface;
15
use Neomerx\JsonApi\Contracts\Schema\ContainerInterface;
16
use Neomerx\JsonApi\Document\Error;
17
use Neomerx\JsonApi\Exceptions\JsonApiException;
18
use Reva2\JsonApi\Contracts\Decoders\DataParserInterface;
19
use Reva2\JsonApi\Contracts\Decoders\DecoderInterface;
20
use Reva2\JsonApi\Contracts\Factories\FactoryInterface;
21
use Reva2\JsonApi\Contracts\Http\Query\QueryParametersParserInterface;
22
use Reva2\JsonApi\Contracts\Http\RequestInterface;
23
use Reva2\JsonApi\Contracts\Services\EnvironmentInterface;
24
use Reva2\JsonApi\Contracts\Services\JsonApiRegistryInterface;
25
use Reva2\JsonApi\Contracts\Services\JsonApiServiceInterface;
26
use Reva2\JsonApi\Contracts\Services\ValidationServiceInterface;
27
use Reva2\JsonApi\Http\ResponseFactory;
28
use Symfony\Component\HttpFoundation\Request;
29
use Neomerx\JsonApi\Http\Request as Psr7Request;
30
31
/**
32
 * Service for JSON API requests processing
33
 *
34
 * @package Reva2\JsonApi\Services
35
 * @author Sergey Revenko <[email protected]>
36
 */
37
class JsonApiService implements JsonApiServiceInterface
38
{
39
    /**
40
     * @var FactoryInterface
41
     */
42
    protected $factory;
43
44
    /**
45
     * @var JsonApiRegistryInterface
46
     */
47
    protected $registry;
48
49
    /**
50
     * @var ContainerInterface
51
     */
52
    protected $schemas;
53
54
    /**
55
     * @var ValidationServiceInterface
56
     */
57
    protected $validator;
58
59
    /**
60
     * @var DataParserInterface
61
     */
62
    protected $parser;
63
64
    /**
65
     * Constructor
66
     *
67
     * @param FactoryInterface $factory
68
     * @param JsonApiRegistryInterface $registry
69
     * @param ContainerInterface $schemas
70
     * @param DataParserInterface $parser
71
     * @param ValidationServiceInterface $validator
72
     */
73
    public function __construct(
74
        FactoryInterface $factory,
75
        JsonApiRegistryInterface $registry,
76
        ContainerInterface $schemas,
77
        DataParserInterface $parser,
78
        ValidationServiceInterface $validator
79
    ) {
80
        $this->factory = $factory;
81
        $this->registry = $registry;
82
        $this->parser = $parser;
83
        $this->schemas = $schemas;
84
        $this->validator = $validator;
85
    }
86
87
    /**
88
     * @inheritdoc
89
     */
90
    public function getFactory()
91
    {
92
        return $this->factory;
93
    }
94
95
    /**
96
     * @inheritdoc
97
     */
98
    public function parseRequest(Request $request, EnvironmentInterface $environment = null)
99
    {
100
        if (null === $environment) {
101
            $environment = $this->getRequestEnvironment($request);
102
        }
103
104
        $this->initializeEnvironment($environment, $request);
105
106
        $apiRequest = $this->factory->createRequest($environment);
107
        $apiRequest
108
            ->setQuery($this->parseQuery($request, $environment))
109
            ->setBody($this->parseBody($request, $environment));
110
111
        if (null !== $environment->getValidationGroups()) {
112
            $this->validateRequest($apiRequest);
113
        }
114
115
        return $request;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $request; (Symfony\Component\HttpFoundation\Request) is incompatible with the return type declared by the interface Reva2\JsonApi\Contracts\...Interface::parseRequest of type Reva2\JsonApi\Contracts\Http\RequestInterface.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
116
    }
117
118
    /**
119
     * @inheritdoc
120
     */
121
    public function validateRequest(RequestInterface $request)
122
    {
123
        $validationGroups = $request->getEnvironment()->getValidationGroups();
124
        if (is_bool($validationGroups)) {
125
            if (false == $validationGroups) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
126
                return;
127
            } else {
128
                $validationGroups = null;
129
            }
130
        }
131
132
        $errors = [];
133
134 View Code Duplication
        if (null !== ($query = $request->getQuery())) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
135
            $errors = array_merge($errors, $this->validator->validate($query, $validationGroups));
136
        }
137
138 View Code Duplication
        if (null !== ($body = $request->getBody())) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
139
            $errors = array_merge($errors, $this->validator->validate($body, $validationGroups));
140
        }
141
142
        if (0 === count($errors)) {
143
            return;
144
        }
145
146
        $code = null;
147
        foreach ($errors as $error) {
148
            /* @var $error Error */
149
            if (null === $code) {
150
                $code = $error->getStatus();
151
            } elseif ($code !== $error->getStatus()) {
152
                $code = 400;
153
                break;
154
            }
155
        }
156
157
        throw new JsonApiException($errors, $code);
158
    }
159
160
    /**
161
     * @inheritdoc
162
     */
163
    public function getResponseFactory(RequestInterface $request)
164
    {
165
        return new ResponseFactory($this->schemas, $request->getEnvironment(), $request->getQuery());
166
    }
167
168
    /**
169
     * Returns JSON API environment configured in request
170
     *
171
     * @param Request $request
172
     * @return EnvironmentInterface
173
     */
174
    protected function getRequestEnvironment(Request $request)
175
    {
176
        if (false === $request->attributes->has('_jsonapi')) {
177
            throw new \RuntimeException('JSON API environment is not provided');
178
        }
179
180
        $environment = $request->attributes->get('_jsonapi');
181
        if (!$environment instanceof EnvironmentInterface) {
182
            throw new \InvalidArgumentException(sprintf(
183
                "JSON API environment should implement %s interface",
184
                EnvironmentInterface::class
185
            ));
186
        }
187
188
        return $environment;
189
    }
190
191
    /**
192
     * Initialize JSON API environment for specified request
193
     *
194
     * @param EnvironmentInterface $environment
195
     * @param Request $request
196
     */
197
    private function initializeEnvironment(EnvironmentInterface $environment, Request $request)
198
    {
199
        $matcher = $this->createMatcher($environment);
200
201
        $this->parseRequestHeaders($request, $matcher);
202
203
        $environment
204
            ->setDecoder($matcher->getDecoder())
1 ignored issue
show
Bug introduced by
It seems like $matcher->getDecoder() targeting Neomerx\JsonApi\Contract...Interface::getDecoder() can also be of type null; however, Reva2\JsonApi\Contracts\...Interface::setDecoder() does only seem to accept object<Reva2\JsonApi\Con...oders\DecoderInterface>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
205
            ->setEncoder($matcher->getEncoder())
1 ignored issue
show
Bug introduced by
It seems like $matcher->getEncoder() can be null; however, setEncoder() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
206
            ->setEncoderMediaType($matcher->getEncoderRegisteredMatchedType());
1 ignored issue
show
Bug introduced by
It seems like $matcher->getEncoderRegisteredMatchedType() can be null; however, setEncoderMediaType() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
207
    }
208
209
    /**
210
     * Create codec matcher for specified environment
211
     *
212
     * @param EnvironmentInterface $environment
213
     * @return \Neomerx\JsonApi\Contracts\Codec\CodecMatcherInterface
214
     */
215
    private function createMatcher(EnvironmentInterface $environment)
216
    {
217
        $matcher = $this->factory->createCodecMatcher();
218
219
        $config = $environment->getMatcherConfiguration();
220 View Code Duplication
        if ((array_key_exists('decoders', $config)) && (is_array($config['decoders']))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
221
            foreach ($config['decoders'] as $mediaTypeStr => $decoderType) {
222
                $matcher->registerDecoder(
223
                    $this->parseMediaTypeString($mediaTypeStr),
224
                    $this->registry->getDecoder($decoderType)
225
                );
226
            }
227
        }
228
229 View Code Duplication
        if ((array_key_exists('encoders', $config)) && (is_array($config['encoders']))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
230
            foreach ($config['encoders'] as $mediaTypeStr => $encoderType) {
231
                $matcher->registerEncoder(
232
                    $this->parseMediaTypeString($mediaTypeStr),
233
                    $this->registry->getEncoder($encoderType)
234
                );
235
            }
236
        }
237
238
        return $matcher;
239
    }
240
241
    /**
242
     * Convert media type string to media type object
243
     *
244
     * @param string $type
245
     * @return \Neomerx\JsonApi\Contracts\Http\Headers\MediaTypeInterface
246
     */
247
    private function parseMediaTypeString($type)
248
    {
249
        $parts = explode('/', $type);
250
        if (2 !== $parts) {
251
            throw new InvalidArgumentException(sprintf("Invalid media type '%s' specified", $type));
252
        }
253
254
        return $this->factory->createMediaType($parts[0], $parts[1]);
255
    }
256
257
    /**
258
     * Parse request headers and detect appropriate decoder and encoder
259
     *
260
     * @param Request $request
261
     * @param CodecMatcherInterface $matcher
262
     */
263
    private function parseRequestHeaders(Request $request, CodecMatcherInterface $matcher)
264
    {
265
        $psr7Request = $this->createPsr7Request($request);
266
        $headers = $this->factory->createHeaderParametersParser()->parse($psr7Request);
267
        $checker = $this->factory->createHeadersChecker($matcher);
268
269
        $checker->checkHeaders($headers);
270
    }
271
272
    /**
273
     * Create PSR7 request from symfony http foundation request
274
     *
275
     * @param Request $request
276
     * @return Psr7Request
277
     */
278
    private function createPsr7Request(Request $request)
279
    {
280
        return new Psr7Request(
281
            function () use ($request) {
282
                return $request->getMethod();
283
            },
284
            function ($name) use ($request) {
285
                return $request->headers->get($name);
286
            },
287
            function () use ($request) {
288
                return $request->query->all();
289
            }
290
        );
291
    }
292
293
    /**
294
     * Parse request query parameters
295
     *
296
     * @param Request $request
297
     * @param EnvironmentInterface $environment
298
     * @return \Neomerx\JsonApi\Contracts\Encoder\Parameters\EncodingParametersInterface|null
299
     */
300
    private function parseQuery(Request $request, EnvironmentInterface $environment)
301
    {
302
        if (null === $environment->getQueryType()) {
303
            return null;
304
        }
305
306
        $queryParser = $this->factory->createQueryParametersParser();
307
        if ($queryParser instanceof QueryParametersParserInterface) {
308
            $queryParser
309
                ->setDataParser($this->parser)
310
                ->setQueryType($environment->getQueryType());
311
        }
312
313
        return $queryParser->parse($this->createPsr7Request($request));
314
    }
315
316
    /**
317
     * Parse request body
318
     *
319
     * @param Request $request
320
     * @param EnvironmentInterface $environment
321
     * @return mixed|null
322
     */
323
    private function parseBody(Request $request, EnvironmentInterface $environment)
324
    {
325
        if (null === $environment->getBodyType()) {
326
            return null;
327
        }
328
329
        $decoder = $environment->getDecoder();
330
        if ($decoder instanceof DecoderInterface) {
331
            $decoder->setContentType($environment->getBodyType());
332
        }
333
334
        return $decoder->decode($request->getContent());
1 ignored issue
show
Bug introduced by
It seems like $request->getContent() targeting Symfony\Component\HttpFo...n\Request::getContent() can also be of type resource; however, Neomerx\JsonApi\Contract...oderInterface::decode() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
335
    }
336
}
337