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

MediaType   B

Complexity

Total Complexity 38

Size/Duplication

Total Lines 270
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 38
c 1
b 0
f 0
lcom 1
cbo 1
dl 0
loc 270
ccs 77
cts 77
cp 1
rs 8.3999

17 Methods

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