Completed
Branch master (85eed3)
by
unknown
10:47
created

ParametersParser::getSortParameters()   B

Complexity

Conditions 5
Paths 2

Size

Total Lines 16
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 16
ccs 14
cts 14
cp 1
rs 8.8571
cc 5
eloc 12
nc 2
nop 1
crap 5
1
<?php namespace Neomerx\JsonApi\Http\Parameters;
2
3
/**
4
 * Copyright 2015 [email protected] (www.neomerx.com)
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 \InvalidArgumentException;
20
use \Psr\Log\LoggerAwareTrait;
21
use \Psr\Log\LoggerAwareInterface;
22
use \Neomerx\JsonApi\Http\Headers\Header;
23
use \Psr\Http\Message\ServerRequestInterface;
24
use \Neomerx\JsonApi\Http\Headers\AcceptHeader;
25
use \Neomerx\JsonApi\Exceptions\JsonApiException as E;
26
use \Neomerx\JsonApi\Contracts\Http\Headers\HeaderInterface;
27
use \Neomerx\JsonApi\Contracts\Http\Headers\MediaTypeInterface;
28
use \Neomerx\JsonApi\Contracts\Http\Parameters\SortParameterInterface;
29
use \Neomerx\JsonApi\Contracts\Http\Parameters\ParametersParserInterface;
30
use \Neomerx\JsonApi\Contracts\Http\Parameters\ParametersFactoryInterface;
31
32
/**
33
 * @package Neomerx\JsonApi
34
 */
35
class ParametersParser implements ParametersParserInterface, LoggerAwareInterface
36
{
37
    use LoggerAwareTrait;
38
39
    /**
40
     * @var ParametersFactoryInterface
41
     */
42
    private $factory;
43
44
    /**
45
     * @param ParametersFactoryInterface $factory
46
     */
47 33
    public function __construct(ParametersFactoryInterface $factory)
48
    {
49 33
        $this->factory = $factory;
50 33
    }
51
52
    /**
53
     * @inheritdoc
54
     */
55 32
    public function parse(ServerRequestInterface $request)
56
    {
57 32
        $acceptHeader      = null;
58 32
        $contentTypeHeader = null;
59
60
        try {
61 32
            $contentType = $request->getHeader(HeaderInterface::HEADER_CONTENT_TYPE);
62 32
            $contentTypeHeader = Header::parse(
63 32
                empty($contentType) === true ? MediaTypeInterface::JSON_API_MEDIA_TYPE : $contentType,
64
                HeaderInterface::HEADER_CONTENT_TYPE
65 32
            );
66 32
        } catch (InvalidArgumentException $exception) {
67 1
            E::throwException(new E([], E::HTTP_CODE_BAD_REQUEST, $exception));
68
        }
69
70
        try {
71 31
            $headerString = $request->getHeader(HeaderInterface::HEADER_ACCEPT);
72 31
            if (empty($headerString) === false) {
73 30
                $acceptHeader = AcceptHeader::parse($headerString);
74 29
            } else {
75 1
                $jsonMediaType = $this->factory->createAcceptMediaType(
76 1
                    0,
77 1
                    MediaTypeInterface::JSON_API_TYPE,
78
                    MediaTypeInterface::JSON_API_SUB_TYPE
79 1
                );
80 1
                $acceptHeader = $this->factory->createAcceptHeader([$jsonMediaType]);
81
            }
82 31
        } catch (InvalidArgumentException $exception) {
83 1
            E::throwException(new E([], E::HTTP_CODE_BAD_REQUEST, $exception));
84
        }
85
86 30
        $parameters = $request->getQueryParams();
87
88 30
        return $this->factory->createParameters(
89 30
            $contentTypeHeader,
1 ignored issue
show
Bug introduced by
It seems like $contentTypeHeader defined by null on line 58 can be null; however, Neomerx\JsonApi\Contract...ace::createParameters() 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...
90 30
            $acceptHeader,
1 ignored issue
show
Bug introduced by
It seems like $acceptHeader can also be of type null or object<Neomerx\JsonApi\Http\Headers\Header>; however, Neomerx\JsonApi\Contract...ace::createParameters() does only seem to accept object<Neomerx\JsonApi\C...\AcceptHeaderInterface>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
91 30
            $this->getIncludePaths($parameters),
0 ignored issues
show
Bug introduced by
It seems like $this->getIncludePaths($parameters) targeting Neomerx\JsonApi\Http\Par...rser::getIncludePaths() can also be of type array; however, Neomerx\JsonApi\Contract...ace::createParameters() does only seem to accept array<integer,string>|null, 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...
92 29
            $this->getFieldSets($parameters),
93 28
            $this->getSortParameters($parameters),
94 26
            $this->getPagingParameters($parameters),
95 25
            $this->getFilteringParameters($parameters),
96 24
            $this->getUnrecognizedParameters($parameters)
97 24
        );
98
    }
99
100
    /**
101
     * @param array $parameters
102
     *
103
     * @return array|null
104
     */
105 30
    private function getIncludePaths(array $parameters)
106
    {
107 30
        $paths  = $this->getStringParamOrNull($parameters, self::PARAM_INCLUDE);
108 29
        $result = empty($paths) === false ? explode(',', rtrim($paths, ',')) : null;
109
110 29
        return $result;
111
    }
112
113
    /**
114
     * @param array $parameters
115
     *
116
     * @return array|null
117
     */
118 29
    private function getFieldSets(array $parameters)
119
    {
120 29
        $result    = [];
121 29
        $fieldSets = $this->getParamOrNull($parameters, self::PARAM_FIELDS);
122 29
        if (empty($fieldSets) === false && is_array($fieldSets)) {
123 18
            foreach ($fieldSets as $type => $fields) {
124
                // We expect fields to be comma separated or empty strings. Multi-dimension arrays are not allowed.
125 18
                is_string($fields) ?: E::throwException(new E([], E::HTTP_CODE_BAD_REQUEST));
126 17
                $result[$type] = (empty($fields) === true ? [] : explode(',', $fields));
127 17
            }
128 17
        } else {
129 11
            $result = null;
130
        }
131
132 28
        return $result;
133
    }
134
135
    /**
136
     * @param array $parameters
137
     *
138
     * @return SortParameterInterface[]|null
139
     */
140 28
    protected function getSortParameters(array $parameters)
141
    {
142 28
        $sortParams = null;
143 28
        $sortParam  = $this->getStringParamOrNull($parameters, self::PARAM_SORT);
144 27
        if ($sortParam !== null) {
145 17
            foreach (explode(',', $sortParam) as $param) {
146 17
                $isDesc = false;
147 17
                empty($param) === false ?
148 17
                    $isDesc = ($param[0] === '-') : E::throwException(new E([], E::HTTP_CODE_BAD_REQUEST));
149 17
                $sortField = ltrim($param, '+-');
150 17
                empty($sortField) === false ?: E::throwException(new E([], E::HTTP_CODE_BAD_REQUEST));
151 17
                $sortParams[] = $this->factory->createSortParam($sortField, $isDesc === false);
152 17
            }
153 16
        }
154 26
        return $sortParams;
155
    }
156
157
    /**
158
     * @param array $parameters
159
     *
160
     * @return array|null
161
     */
162 26
    private function getPagingParameters(array $parameters)
163
    {
164 26
        return $this->getArrayParamOrNull($parameters, self::PARAM_PAGE);
165
    }
166
167
    /**
168
     * @param array $parameters
169
     *
170
     * @return array|null
171
     */
172 25
    private function getFilteringParameters(array $parameters)
173
    {
174 25
        return $this->getArrayParamOrNull($parameters, self::PARAM_FILTER);
175
    }
176
177
    /**
178
     * @param array $parameters
179
     *
180
     * @return array|null
181
     */
182 24
    private function getUnrecognizedParameters(array $parameters)
183
    {
184
        $supported = [
185 24
            self::PARAM_INCLUDE => 0,
186 24
            self::PARAM_FIELDS  => 0,
187 24
            self::PARAM_PAGE    => 0,
188 24
            self::PARAM_FILTER  => 0,
189 24
            self::PARAM_SORT    => 0,
190 24
        ];
191 24
        $unrecognized = array_diff_key($parameters, $supported);
192 24
        return empty($unrecognized) === true ? null : $unrecognized;
193
    }
194
195
    /**
196
     * @param array $parameters
197
     * @param string $name
198
     *
199
     * @return array|null
200
     */
201 26 View Code Duplication
    private function getArrayParamOrNull(array $parameters, $name)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in 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...
202
    {
203 26
        $value = $this->getParamOrNull($parameters, $name);
204
205 26
        $isArrayOrNull = ($value === null || is_array($value) === true);
206 26
        $isArrayOrNull === true ?: E::throwException(new E([], E::HTTP_CODE_BAD_REQUEST));
207
208 25
        return $value;
209
    }
210
211
    /**
212
     * @param array $parameters
213
     * @param string $name
214
     *
215
     * @return string|null
216
     */
217 30 View Code Duplication
    private function getStringParamOrNull(array $parameters, $name)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in 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...
218
    {
219 30
        $value = $this->getParamOrNull($parameters, $name);
220
221 30
        $isStringOrNull = ($value === null || is_string($value) === true);
222 30
        $isStringOrNull === true ?: E::throwException(new E([], E::HTTP_CODE_BAD_REQUEST));
223
224 29
        return $value;
225
    }
226
227
    /**
228
     * @param array  $parameters
229
     * @param string $name
230
     *
231
     * @return mixed
232
     */
233 30
    private function getParamOrNull(array $parameters, $name)
234
    {
235 30
        return isset($parameters[$name]) === true ? $parameters[$name] : null;
236
    }
237
}
238