Completed
Branch master (9645b9)
by Neomerx
02:19
created

MediaType::isMediaParametersMatch()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 30
c 0
b 0
f 0
cc 6
nc 6
nop 1
ccs 13
cts 13
cp 1
crap 6
rs 8.8177
1
<?php declare(strict_types=1);
2
3
namespace Neomerx\JsonApi\Http\Headers;
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 Neomerx\JsonApi\Contracts\Http\Headers\MediaTypeInterface;
22
use Neomerx\JsonApi\Exceptions\InvalidArgumentException;
23
24
/**
25
 * @package Neomerx\JsonApi
26
 */
27
class MediaType implements MediaTypeInterface
28
{
29
    /**
30
     * @var string
31
     */
32
    private $type;
33
34
    /**
35
     * @var string
36
     */
37
    private $subType;
38
39
    /**
40
     * @var string?
41
     */
42
    private $mediaType = null;
43
44
    /**
45
     * @var array<string,string>|null
46
     */
47
    private $parameters;
48
49
    /**
50
     * A list of parameter names for case-insensitive compare. Keys must be lower-cased.
51
     *
52
     * @var array
53
     */
54
    protected const PARAMETER_NAMES = [
55
        'charset' => true,
56
    ];
57
58
    /**
59
     * @param string $type
60
     * @param string $subType
61
     * @param array<string,string>|null $parameters
62
     */
63 45
    public function __construct(string $type, string $subType, array $parameters = null)
64
    {
65 45
        $type = trim($type);
66 45
        if (empty($type) === true) {
67 1
            throw new InvalidArgumentException('type');
68
        }
69
70 44
        $subType = trim($subType);
71 44
        if (empty($subType) === true) {
72 1
            throw new InvalidArgumentException('subType');
73
        }
74
75 43
        $this->type       = $type;
76 43
        $this->subType    = $subType;
77 43
        $this->parameters = $parameters;
78 43
    }
79
80
    /**
81
     * @inheritdoc
82
     */
83 41
    public function getType(): string
84
    {
85 41
        return $this->type;
86
    }
87
88
    /**
89
     * @inheritdoc
90
     */
91 41
    public function getSubType(): string
92
    {
93 41
        return $this->subType;
94
    }
95
96
    /**
97
     * @inheritdoc
98
     */
99 35
    public function getMediaType(): string
100
    {
101 35
        if ($this->mediaType === null) {
102 35
            $this->mediaType = $this->getType() . '/' . $this->getSubType();
103
        }
104
105 35
        return $this->mediaType;
106
    }
107
108
    /**
109
     * @inheritdoc
110
     */
111 22
    public function getParameters(): ?array
112
    {
113 22
        return $this->parameters;
114
    }
115
116
    /**
117
     * @inheritdoc
118
     */
119 3
    public function matchesTo(MediaTypeInterface $mediaType): bool
120
    {
121
        return
122 3
            $this->isTypeMatches($mediaType) &&
123 3
            $this->isSubTypeMatches($mediaType) &&
124 3
            $this->isMediaParametersMatch($mediaType);
125
    }
126
127
    /**
128
     * @inheritdoc
129
     */
130 4
    public function equalsTo(MediaTypeInterface $mediaType): bool
131
    {
132
        return
133 4
            $this->isTypeEquals($mediaType) &&
134 4
            $this->isSubTypeEquals($mediaType) &&
135 4
            $this->isMediaParametersEqual($mediaType);
136
    }
137
138
    /**
139
     * @param MediaTypeInterface $mediaType
140
     *
141
     * @return bool
142
     */
143 3
    private function isTypeMatches(MediaTypeInterface $mediaType): bool
144
    {
145 3
        return $mediaType->getType() === '*' || $this->isTypeEquals($mediaType);
146
    }
147
148
    /**
149
     * @param MediaTypeInterface $mediaType
150
     *
151
     * @return bool
152
     */
153 6
    private function isTypeEquals(MediaTypeInterface $mediaType): bool
154
    {
155
        // Type, subtype and param name should be compared case-insensitive
156
        // https://tools.ietf.org/html/rfc7231#section-3.1.1.1
157 6
        return strcasecmp($this->getType(), $mediaType->getType()) === 0;
158
    }
159
160
    /**
161
     * @param MediaTypeInterface $mediaType
162
     *
163
     * @return bool
164
     */
165 3
    private function isSubTypeMatches(MediaTypeInterface $mediaType): bool
166
    {
167 3
        return $mediaType->getSubType() === '*' || $this->isSubTypeEquals($mediaType);
168
    }
169
170
    /**
171
     * @param MediaTypeInterface $mediaType
172
     *
173
     * @return bool
174
     */
175 6
    private function isSubTypeEquals(MediaTypeInterface $mediaType): bool
176
    {
177
        // Type, subtype and param name should be compared case-insensitive
178
        // https://tools.ietf.org/html/rfc7231#section-3.1.1.1
179 6
        return strcasecmp($this->getSubType(), $mediaType->getSubType()) === 0;
180
    }
181
182
    /**
183
     * @param MediaTypeInterface $mediaType
184
     *
185
     * @return bool
186
     */
187 3
    private function isMediaParametersMatch(MediaTypeInterface $mediaType): bool
188
    {
189 3
        if ($this->bothMediaTypeParamsEmpty($mediaType) === true) {
190 1
            return true;
191 2
        } elseif ($this->bothMediaTypeParamsNotEmptyAndEqualInSize($mediaType)) {
192
            // Type, subtype and param name should be compared case-insensitive
193
            // https://tools.ietf.org/html/rfc7231#section-3.1.1.1
194 2
            $ourParameters       = array_change_key_case($this->getParameters());
195 2
            $parametersToCompare = array_change_key_case($mediaType->getParameters());
196
197
            // if at least one name are different they are not equal
198 2
            if (empty(array_diff_key($ourParameters, $parametersToCompare)) === false) {
199 1
                return false;
200
            }
201
202
            // If we are here we have to compare values. Also some of the values should be compared case-insensitive
203
            // according to https://tools.ietf.org/html/rfc7231#section-3.1.1.1
204
            // > 'Parameter values might or might not be case-sensitive, depending on
205
            // the semantics of the parameter name.'
206 2
            foreach ($ourParameters as $name => $value) {
207 2
                if ($this->paramValuesMatch($name, $value, $parametersToCompare[$name]) === false) {
208 2
                    return false;
209
                }
210
            }
211
212 2
            return true;
213
        }
214
215 1
        return false;
216
    }
217
218
    /**
219
     * @param MediaTypeInterface $mediaType
220
     *
221
     * @return bool
222
     */
223 4
    private function isMediaParametersEqual(MediaTypeInterface $mediaType): bool
224
    {
225 4
        if ($this->bothMediaTypeParamsEmpty($mediaType) === true) {
226 1
            return true;
227 3
        } elseif ($this->bothMediaTypeParamsNotEmptyAndEqualInSize($mediaType)) {
228
            // Type, subtype and param name should be compared case-insensitive
229
            // https://tools.ietf.org/html/rfc7231#section-3.1.1.1
230 3
            $ourParameters       = array_change_key_case($this->getParameters());
231 3
            $parametersToCompare = array_change_key_case($mediaType->getParameters());
232
233
            // if at least one name are different they are not equal
234 3
            if (empty(array_diff_key($ourParameters, $parametersToCompare)) === false) {
235 1
                return false;
236
            }
237
238
            // If we are here we have to compare values. Also some of the values should be compared case-insensitive
239
            // according to https://tools.ietf.org/html/rfc7231#section-3.1.1.1
240
            // > 'Parameter values might or might not be case-sensitive, depending on
241
            // the semantics of the parameter name.'
242 3
            foreach ($ourParameters as $name => $value) {
243 3
                if ($this->paramValuesEqual($name, $value, $parametersToCompare[$name]) === false) {
244 3
                    return false;
245
                }
246
            }
247
248 2
            return true;
249
        }
250
251 1
        return false;
252
    }
253
254
    /**
255
     * @param MediaTypeInterface $mediaType
256
     *
257
     * @return bool
258
     */
259 6
    private function bothMediaTypeParamsEmpty(MediaTypeInterface $mediaType): bool
260
    {
261 6
        return $this->getParameters() === null && $mediaType->getParameters() === null;
262
    }
263
264
    /**
265
     * @param MediaTypeInterface $mediaType
266
     *
267
     * @return bool
268
     */
269 4
    private function bothMediaTypeParamsNotEmptyAndEqualInSize(MediaTypeInterface $mediaType): bool
270
    {
271 4
        $pr1 = $this->getParameters();
272 4
        $pr2 = $mediaType->getParameters();
273
274 4
        return (empty($pr1) === false && empty($pr2) === false) && (count($pr1) === count($pr2));
275
    }
276
277
    /**
278
     * @param string $name
279
     *
280
     * @return bool
281
     */
282 4
    private function isParamCaseInsensitive(string $name): bool
283
    {
284 4
        return isset(static::PARAMETER_NAMES[$name]);
285
    }
286
287
    /**
288
     * @param string $name
289
     * @param string $value
290
     * @param string $valueToCompare
291
     *
292
     * @return bool
293
     */
294 4
    private function paramValuesEqual(string $name, string $value, string $valueToCompare): bool
295
    {
296 4
        $valuesEqual = $this->isParamCaseInsensitive($name) === true ?
297 4
            strcasecmp($value, $valueToCompare) === 0 : $value === $valueToCompare;
298
299 4
        return $valuesEqual;
300
    }
301
302
    /**
303
     * @param string $name
304
     * @param string $value
305
     * @param string $valueToCompare
306
     *
307
     * @return bool
308
     */
309 2
    private function paramValuesMatch(string $name, string $value, string $valueToCompare): bool
310
    {
311 2
        $valuesEqual = $valueToCompare === '*' || $this->paramValuesEqual($name, $value, $valueToCompare);
312
313 2
        return $valuesEqual;
314
    }
315
}
316