Format   B
last analyzed

Complexity

Total Complexity 45

Size/Duplication

Total Lines 121
Duplicated Lines 0 %

Test Coverage

Coverage 96.77%

Importance

Changes 2
Bugs 1 Features 0
Metric Value
wmc 45
eloc 90
c 2
b 1
f 0
dl 0
loc 121
ccs 60
cts 62
cp 0.9677
rs 8.8

4 Methods

Rating   Name   Duplication   Size   Complexity  
A jsonPointerError() 0 9 5
D validationError() 0 44 28
A regexError() 0 10 4
B dateTimeError() 0 22 8

How to fix   Complexity   

Complex Class

Complex classes like Format 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 Format, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Swaggest\JsonSchema\Constraint;
4
5
use Swaggest\JsonSchema\Constraint\Format\IdnHostname;
6
use Swaggest\JsonSchema\Constraint\Format\Iri;
7
use Swaggest\JsonSchema\Constraint\Format\Uri;
8
9
class Format
10
{
11
    const DATE_TIME = 'date-time';
12
    const DATE = 'date';
13
    const FULL_DATE = 'full-date';
14
    const TIME = 'time';
15
    const FULL_TIME = 'full-time';
16
    const URI = 'uri';
17
    const IRI = 'iri';
18
    const EMAIL = 'email';
19
    const IDN_EMAIL = 'idn-email';
20
    const IPV4 = 'ipv4';
21
    const IPV6 = 'ipv6';
22
    const HOSTNAME = 'hostname';
23
    const IDN_HOSTNAME = 'idn-hostname';
24
    const REGEX = 'regex';
25
    const JSON_POINTER = 'json-pointer';
26
    const RELATIVE_JSON_POINTER = 'relative-json-pointer';
27
    const URI_REFERENCE = 'uri-reference';
28
    const IRI_REFERENCE = 'iri-reference';
29
    const URI_TEMPLATE = 'uri-template';
30
31
    public static $strictDateTimeValidation = false;
32
33
    private static $dateRegexPart = '(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])';
34
    private static $timeRegexPart = '([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)([01][0-9]|2[0-3]):?([0-5][0-9])?)?';
35
    private static $jsonPointerRegex = '_^(?:/|(?:/[^/#]*)*)$_';
36
    private static $jsonPointerRelativeRegex = '~^(0|[1-9][0-9]*)((?:/[^/#]*)*)(#?)$~';
37 445
    private static $jsonPointerUnescapedTilde = '/~([^01]|$)/';
38
39
    public static function validationError($format, $data)
40 445
    {
41 39
        switch ($format) {
42 406
            case self::DATE_TIME:
43 6
                return self::dateTimeError($data);
44 400
            case self::DATE:
45 9
            case self::FULL_DATE:
46 391
                return preg_match('/^' . self::$dateRegexPart . '$/i', $data) ? null : 'Invalid date';
47 166
            case self::TIME:
48 238
            case self::FULL_TIME:
49 9
                return preg_match('/^' . self::$timeRegexPart . '$/i', $data) ? null : 'Invalid time';
50 229
            case self::URI:
51 11
                return Uri::validationError($data, Uri::IS_SCHEME_REQUIRED);
52 219
            case self::IRI:
53 2
                return Iri::validationError($data);
54 217
            case self::EMAIL:
55 16
                return filter_var($data, FILTER_VALIDATE_EMAIL) ? null : 'Invalid email';
56 201
            case self::IDN_EMAIL:
57 12
                return count(explode('@', $data, 3)) === 2 ? null : 'Invalid email';
58 189
            case self::IPV4:
59 16
                return filter_var($data, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ? null : 'Invalid ipv4';
60 174
            case self::IPV6:
61 4
                return filter_var($data, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? null : 'Invalid ipv6';
62 170
            case self::HOSTNAME:
63 53
                if (strlen(rtrim($data, '.')) >= 254) { // Not sure if it should be 254, higher number fails AJV suite.
64 117
                    return 'Invalid hostname (too long)';
65 70
                }
66 47
                return preg_match(Uri::HOSTNAME_REGEX, $data) ? null : 'Invalid hostname';
67 14
            case self::IDN_HOSTNAME:
68 33
                return IdnHostname::validationError($data);
69 15
            case self::REGEX:
70 18
                return self::regexError($data);
71 7
            case self::JSON_POINTER:
72 11
                return self::jsonPointerError($data);
73 10
            case self::RELATIVE_JSON_POINTER:
74
                return self::jsonPointerError($data, true);
75 1
            case self::URI_REFERENCE:
76
                return Uri::validationError($data, Uri::IS_URI_REFERENCE);
77
            case self::IRI_REFERENCE:
78 39
                return Iri::validationError($data, Uri::IS_URI_REFERENCE);
79
            case self::URI_TEMPLATE:
80 39
                return Uri::validationError($data, Uri::IS_URI_TEMPLATE);
81 14
        }
82
        return null;
83
    }
84 25
85 20
    public static function dateTimeError($data)
86 20
    {
87
        if (!preg_match('/^' . self::$dateRegexPart . 'T' . self::$timeRegexPart . '$/i', $data)) {
88
            return 'Invalid date-time format: ' . $data;
89 20
        }
90 4
91 20
        if (self::$strictDateTimeValidation) {
92
            $dt = date_create($data);
93 20
            if ($dt === false) {
94 20
                return 'Failed to parse date-time: ' . $data;
95 3
            }
96
            $isLeapSecond = '6' === $data[17] && (
97
                    0 === strpos(substr($data, 5, 5), '12-31') ||
98
                    0 === strpos(substr($data, 5, 5), '06-30')
99 22
                );
100
            if (!$isLeapSecond &&
101
                0 !== stripos($dt->format(DATE_RFC3339), substr($data, 0, 19))) {
102 53
                return 'Invalid date-time value: ' . $data;
103
            }
104 53
        }
105 3
106
        return null;
107 50
    }
108
109
    public static function regexError($data)
110
    {
111 50
        if (substr($data, -2) === '\Z') {
112
            return 'Invalid regex: \Z is not supported';
113
        }
114 84
        if (substr($data, 0, 2) === '\A') {
115
            return 'Invalid regex: \A is not supported';
116 84
        }
117 14
118
        return @preg_match('{' . $data . '}', '') === false ? 'Invalid regex: ' . $data : null;
119 70
    }
120 13
121
    public static function jsonPointerError($data, $isRelative = false)
122 57
    {
123
        if (preg_match(self::$jsonPointerUnescapedTilde, $data)) {
124
            return 'Invalid json-pointer: unescaped ~';
125
        }
126
        if ($isRelative) {
127
            return preg_match(self::$jsonPointerRelativeRegex, $data) ? null : 'Invalid relative json-pointer';
128
        } else {
129
            return preg_match(self::$jsonPointerRegex, $data) ? null : 'Invalid json-pointer';
130
        }
131
    }
132
}