Passed
Pull Request — master (#15)
by mon
03:05
created

Type   F

Complexity

Total Complexity 64

Size/Duplication

Total Lines 536
Duplicated Lines 0 %

Test Coverage

Coverage 99.06%

Importance

Changes 0
Metric Value
eloc 135
dl 0
loc 536
rs 3.28
c 0
b 0
f 0
ccs 105
cts 106
cp 0.9906
wmc 64

25 Methods

Rating   Name   Duplication   Size   Complexity  
A isAlias() 0 9 2
A getDescription() 0 17 5
A getParameter() 0 3 2
A getSubType() 0 3 1
A getExtensions() 0 14 3
A isWildcard() 0 6 4
A removeParameter() 0 3 1
A getMediaComment() 0 3 1
A buildTypesList() 0 27 6
A setSubTypeComment() 0 4 1
A isVendor() 0 6 2
A getDefaultExtension() 0 20 4
B parse() 0 36 7
A setMediaComment() 0 4 1
A hasParameters() 0 3 1
A getParameters() 0 3 1
A getUnaliasedType() 0 8 2
A __construct() 0 3 1
A addParameter() 0 3 1
A getSubTypeComment() 0 3 1
A getMedia() 0 3 1
A getAliases() 0 14 3
A wildcardMatch() 0 15 2
A isExperimental() 0 6 3
B toString() 0 16 8

How to fix   Complexity   

Complex Class

Complex classes like Type often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Type, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace FileEye\MimeMap;
4
5
/**
6
 * Class for working with MIME types
7
 */
8
class Type
9
{
10
    /**
11
     * Short format [e.g. image/jpeg] for strings.
12
     */
13
    const SHORT_TEXT = 0;
14
15
    /**
16
     * Full format [e.g. image/jpeg; p="1"] for strings.
17
     */
18
    const FULL_TEXT = 1;
19
20
    /**
21
     * Full format with comments [e.g. image/jpeg; p="1" (comment)] for strings.
22
     */
23
    const FULL_TEXT_WITH_COMMENTS = 2;
24
25
    /**
26
     * The MIME media type.
27
     *
28
     * @var string
29
     */
30
    protected $media;
31
32
    /**
33
     * The MIME media type comment.
34
     *
35
     * @var string
36
     */
37
    protected $mediaComment;
38
39
    /**
40
     * The MIME media sub-type.
41
     *
42
     * @var string
43
     */
44
    protected $subType;
45
46
    /**
47
     * The MIME media sub-type comment.
48
     *
49
     * @var string
50
     */
51
    protected $subTypeComment;
52
53
    /**
54
     * Optional MIME parameters
55
     *
56
     * @var TypeParameter[]
57
     */
58
    protected $parameters = [];
59
60
    /**
61
     * Constructor.
62
     *
63
     * The type string will be parsed and the appropriate class vars set.
64
     *
65
     * @param string $type MIME type
66
     */
67 54
    public function __construct($type)
68
    {
69 54
        $this->parse($type);
70 47
    }
71
72
    /**
73
     * Parse a mime-type and set the class variables.
74
     *
75
     * @param string $type MIME type to parse
76
     *
77
     * @return void
78
     */
79 54
    protected function parse($type)
80
    {
81
        // Media and SubType are separated by a slash '/'.
82 54
        $media = TypeParser::parseStringPart($type, 0, '/');
83
84 53
        if (!$media['string']) {
85 3
            throw new MalformedTypeException('Media type not found');
86
        }
87 50
        if (!$media['delimiter_matched']) {
88 1
            throw new MalformedTypeException('Slash \'/\' to separate media type and subtype not found');
89
        }
90
91 49
        $this->media = strtolower($media['string']);
0 ignored issues
show
Bug introduced by
$media['string'] of type void is incompatible with the type string expected by parameter $str of strtolower(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

91
        $this->media = strtolower(/** @scrutinizer ignore-type */ $media['string']);
Loading history...
92 49
        $this->mediaComment = $media['comment'];
93
94
        // SubType and Parameters are separated by semicolons ';'.
95 49
        $sub = TypeParser::parseStringPart($type, $media['end_offset'] + 1, ';');
96
97 48
        if (!$sub['string']) {
98 1
            throw new MalformedTypeException('Media subtype not found');
99
        }
100
101 47
        $this->subType = strtolower($sub['string']);
102 47
        $this->subTypeComment = $sub['comment'];
103
104
        // Loops through the parameter.
105 47
        while ($sub['delimiter_matched']) {
106 26
            $sub = TypeParser::parseStringPart($type, $sub['end_offset'] + 1, ';');
107 26
            $tmp = explode('=', $sub['string'], 2);
108 26
            $p_name = trim($tmp[0]);
109 26
            $p_val = trim($tmp[1]);
110 26
            if ($p_val[0] == '"' && $p_val[strlen($p_val) - 1] == '"') {
111
                $p_val = substr($p_val, 1, -1);
112
            }
113 26
            $p_val = str_replace('\\"', '"', $p_val);
114 26
            $this->addParameter($p_name, $p_val, $sub['comment']);
115
        }
116 47
    }
117
118
    /**
119
     * Does this type have any parameters?
120
     *
121
     * @return boolean true if type has parameters, false otherwise
122
     */
123 28
    public function hasParameters()
124
    {
125 28
        return (bool) $this->parameters;
126
    }
127
128
    /**
129
     * Get a MIME type's parameters
130
     *
131
     * @return TypeParameter[] Type's parameters
132
     */
133 29
    public function getParameters()
134
    {
135 29
        return $this->parameters;
136
    }
137
138
    /**
139
     * Get a MIME type's parameter
140
     *
141
     * @param string $name Parameter name
142
     *
143
     * @return TypeParameter|null
144
     */
145 23
    public function getParameter($name)
146
    {
147 23
        return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
148
    }
149
150
    /**
151
     * Gets a MIME type's media.
152
     *
153
     * Note: 'media' refers to the portion before the first slash.
154
     *
155
     * @return string Type's media.
156
     */
157 41
    public function getMedia()
158
    {
159 41
        return $this->media;
160
    }
161
162
    /**
163
     * Gets a MIME type's media comment.
164
     *
165
     * @return string Type's media comment.
166
     */
167 28
    public function getMediaComment()
168
    {
169 28
        return $this->mediaComment;
170
    }
171
172
    /**
173
     * Sets a MIME type's media comment.
174
     *
175
     * @param string Type's media comment.
0 ignored issues
show
Documentation Bug introduced by
The doc comment Type's at position 0 could not be parsed: Unknown type name 'Type's' at position 0 in Type's.
Loading history...
176
     *
177 42
     * @return $this
178
     */
179 42
    public function setMediaComment($comment)
180
    {
181
        $this->mediaComment = $comment;
182
        return $this;
183
    }
184
185
    /**
186
     * Gets a MIME type's subtype.
187 28
     *
188
     * @return string Type's subtype, null if invalid mime type.
189 28
     */
190
    public function getSubType()
191
    {
192
        return $this->subType;
193
    }
194
195
    /**
196
     * Gets a MIME type's subtype comment.
197
     *
198
     * @return string Type's subtype comment, null if invalid mime type.
199
     */
200
    public function getSubTypeComment()
201 41
    {
202
        return $this->subTypeComment;
203 41
    }
204 41
205 2
    /**
206
     * Sets a MIME type's subtype comment.
207 41
     *
208 41
     * @param string Type's subtype comment.
0 ignored issues
show
Documentation Bug introduced by
The doc comment Type's at position 0 could not be parsed: Unknown type name 'Type's' at position 0 in Type's.
Loading history...
209 5
     *
210
     * @return $this
211 41
     */
212 23
    public function setSubTypeComment($comment)
213 23
    {
214
        $this->subTypeComment = $comment;
215
        return $this;
216 41
    }
217
218
    /**
219
     * Create a textual MIME type from object values
220
     *
221
     * This function performs the opposite function of parse().
222
     *
223
     * @param int $format The format of the output string.
224
     *
225
     * @return string MIME type string
226
     */
227 1
    public function toString($format = Type::FULL_TEXT)
228
    {
229 1
        $type = strtolower($this->media);
230 1
        if ($format > Type::FULL_TEXT && isset($this->mediaComment)) {
231
            $type .= ' (' .  $this->mediaComment . ')';
232 1
        }
233
        $type .= '/' . strtolower($this->subType);
234
        if ($format > Type::FULL_TEXT && isset($this->subTypeComment)) {
235
            $type .= ' (' .  $this->subTypeComment . ')';
236
        }
237
        if ($format > Type::SHORT_TEXT && count($this->parameters)) {
238
            foreach ($this->parameters as $parameter) {
239
                $type .= '; ' . $parameter->toString($format);
240
            }
241
        }
242 1
        return $type;
243
    }
244 1
245 1
    /**
246
     * Is this type experimental?
247 1
     *
248
     * Note: Experimental types are denoted by a leading 'x-' in the media or
249
     *       subtype, e.g. text/x-vcard or x-world/x-vrml.
250
     *
251
     * @return boolean true if type is experimental, false otherwise
252
     */
253
    public function isExperimental()
254
    {
255 12
        if (substr($this->getMedia(), 0, 2) == 'x-' || substr($this->getSubType(), 0, 2) == 'x-') {
256
            return true;
257 12
        }
258 6
        return false;
259
    }
260 9
261
    /**
262
     * Is this a vendor MIME type?
263
     *
264
     * Note: Vendor types are denoted with a leading 'vnd. in the subtype.
265
     *
266
     * @return boolean true if type is a vendor type, false otherwise
267
     */
268
    public function isVendor()
269
    {
270
        if (substr($this->getSubType(), 0, 4) == 'vnd.') {
271
            return true;
272
        }
273
        return false;
274 1
    }
275
276 1
    /**
277
     * Is this a wildcard type?
278 1
     *
279 1
     * @return boolean true if type is a wildcard, false otherwise.
280
     */
281
    public function isWildcard()
282 1
    {
283 1
        if (($this->getMedia() === '*' && $this->getSubtype() === '*') || strpos($this->getSubtype(), '*') !== false) {
284
            return true;
285
        }
286 1
        return false;
287
    }
288 1
289
    /**
290
     * Is this an alias?
291
     *
292
     * @return boolean true if type is an alias, false otherwise.
293
     */
294
    public function isAlias()
295
    {
296
        if ($this->isWildcard()) {
297
            return false;
298
        }
299
300 26
        $map = MapHandler::map();
301
        $subject = $this->toString(static::SHORT_TEXT);
302 26
        return $map->hasAlias($subject);
303 26
    }
304
305
    /**
306
     * Perform a wildcard match on a MIME type
307
     *
308
     * Example:
309
     * $type = new Type('image/png');
310
     * $type->wildcardMatch('image/*');
311
     *
312 1
     * @param string $wildcard Wildcard to check against
313
     *
314 1
     * @return boolean true if there was a match, false otherwise
315 1
     */
316
    public function wildcardMatch($wildcard)
317
    {
318
        $wildcard_type = new static($wildcard);
319
320
        if (!$wildcard_type->isWildcard()) {
321
            return false;
322
        }
323
324
        $wildcard_re = strtr($wildcard_type->toString(static::SHORT_TEXT), [
325
            '/' => '\\/',
326
            '*' => '.*',
327
        ]);
328
        $subject = $this->toString(static::SHORT_TEXT);
329 7
330
        return preg_match("/$wildcard_re/", $subject) === 1;
331 7
    }
332 7
333
    /**
334 7
     * Add a parameter to this type
335 4
     *
336
     * @param string $name    Parameter name
337 3
     * @param string $value   Parameter value
338
     * @param string $comment Comment for this parameter
339
     *
340 7
     * @return void
341 5
     */
342 4
    public function addParameter($name, $value, $comment = null)
343
    {
344 1
        $this->parameters[$name] = new TypeParameter($name, $value, $comment);
345
    }
346
347
    /**
348 3
     * Remove a parameter from this type
349
     *
350
     * @param string $name Parameter name
351
     *
352
     * @return void
353
     */
354
    public function removeParameter($name)
355
    {
356
        unset($this->parameters[$name]);
357
    }
358
359
    /**
360
     * Builds a list of MIME types.
361
     *
362
     * If the current type is a wildcard, than all the types matching the
363
     * wildcard will be returned.
364
     *
365
     * @param bool $strict
366 6
     *   (Optional) if true a MappingException is thrown when no type is
367
     *   found, if false it returns an empty array as a default.
368 6
     *   Defaults to true.
369 6
     *
370
     * @throws MappingException if no mapping found and $strict is true.
371
     *
372 6
     * @return string[]
373 6
     */
374 6
    public function buildTypesList($strict = true)
375 6
    {
376
        $map = MapHandler::map();
377
        $subject = $this->toString(static::SHORT_TEXT);
378 1
379 1
        // Find all types.
380
        $types = [];
381
        if (!$this->isWildcard()) {
382
            if ($map->hasType($subject)) {
383
                $types[] = $subject;
384 6
            }
385 3
        } else {
386 1
            foreach ($map->listTypes($subject) as $t) {
387
                $types[] = $t;
388 2
            }
389
        }
390
391
        // No types found, throw exception or return emtpy array.
392
        if (empty($types)) {
393 5
            if ($strict) {
394 5
                throw new MappingException('No MIME type found for ' . $subject . ' in map');
395 5
            } else {
396 5
                return [];
397
            }
398
        }
399
400 5
        return $types;
401
    }
402
403
    /**
404
     * Returns the unaliased MIME type.
405
     *
406
     * @return Type
407
     *   $this if the current type is not an alias, the parent type if the
408
     *   current type is an alias.
409
     */
410
    protected function getUnaliasedType() {
411
        if (!$this->isAlias()) {
412
            return $this;
413
        } else {
414
            $map = MapHandler::map();
415
            $subject = $this->toString(static::SHORT_TEXT);
416
            $types = $map->getAliasTypes($subject);
417
            return new static($types[0]);
418
        }
419
    }
420
421
    /**
422
     * Returns a description for the MIME type, if existing in the map.
423
     *
424
     * @param bool $include_acronym
425
     *   (Optional) if true and an acronym description exists for the type,
426
     *   the returned description will contain the acronym and its description,
427
     *   appended with a comma. Defaults to false.
428
     *
429
     * @return string|null
430
     */
431
    public function getDescription($include_acronym = false)
432
    {
433
        if ($this->isWildcard()) {
434
            return null;
435
        }
436
437
        $map = MapHandler::map();
438
        $subject = $this->getUnaliasedType()->toString(static::SHORT_TEXT);
439
        $descriptions = $map->getTypeDescriptions($subject);
440
        $res = null;
441
        if (isset($descriptions[0])) {
442
            $res = $descriptions[0];
443
        }
444
        if ($include_acronym && isset($descriptions[1])) {
445
            $res .= ', ' . $descriptions[1];
446
        }
447
        return $res;
448
    }
449
450
    /**
451
     * Returns all the aliases related to the MIME type(s).
452
     *
453
     * If the current type is a wildcard, than all aliases of all the
454
     * types matching the wildcard will be returned.
455
     *
456
     * @param bool $strict
457
     *   (Optional) if true a MappingException is thrown when no mapping is
458
     *   found, if false it returns an empty array as a default.
459
     *   Defaults to true.
460
     *
461
     * @throws MappingException if no mapping found and $strict is true.
462
     *
463
     * @return string[]
464
     */
465
    public function getAliases($strict = true)
466
    {
467
        $map = MapHandler::map();
468
        $types = $this->buildTypesList($strict);
469
470
        // Build the array of aliases.
471
        $aliases = [];
472
        foreach ($types as $t) {
473
            foreach ($map->getTypeAliases($t) as $a) {
474
                $aliases[$a] = $a;
475
            }
476
        }
477
478
        return array_keys($aliases);
479
    }
480
481
    /**
482
     * Returns the MIME type's preferred file extension.
483
     *
484
     * @param bool $strict
485
     *   (Optional) if true a MappingException is thrown when no mapping is
486
     *   found, if false it returns null as a default.
487
     *   Defaults to true.
488
     *
489
     * @throws MappingException if no mapping found and $strict is true.
490
     *
491
     * @return string
492
     */
493
    public function getDefaultExtension($strict = true)
494
    {
495
        $map = MapHandler::map();
496
        $subject = $this->toString(static::SHORT_TEXT);
497
498
        if (!$this->isWildcard()) {
499
            $proceed = $map->hasType($subject);
500
        } else {
501
            $proceed = count($map->listTypes($subject)) === 1;
502
        }
503
504
        if (!$proceed) {
505
            if ($strict) {
506
                throw new MappingException('Cannot determine default extension for type: ' . $this->toString(static::SHORT_TEXT));
507
            } else {
508
                return null;
509
            }
510
        }
511
512
        return $this->getExtensions()[0];
513
    }
514
515
    /**
516
     * Returns all the file extensions related to the MIME type(s).
517
     *
518
     * If the current type is a wildcard, than all extensions of all the
519
     * types matching the wildcard will be returned.
520
     *
521
     * @param bool $strict
522
     *   (Optional) if true a MappingException is thrown when no mapping is
523
     *   found, if false it returns an empty array as a default.
524
     *   Defaults to true.
525
     *
526
     * @throws MappingException if no mapping found and $strict is true.
527
     *
528
     * @return string[]
529
     */
530
    public function getExtensions($strict = true)
531
    {
532
        $map = MapHandler::map();
533
        $types = $this->buildTypesList($strict);
534
535
        // Build the array of extensions.
536
        $extensions = [];
537
        foreach ($types as $t) {
538
            foreach ($map->getTypeExtensions($t) as $e) {
539
                $extensions[$e] = $e;
540
            }
541
        }
542
543
        return array_keys($extensions);
544
    }
545
}
546