FormatConstraint::apply()   C
last analyzed

Complexity

Conditions 15
Paths 14

Size

Total Lines 33
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 15

Importance

Changes 0
Metric Value
dl 0
loc 33
ccs 30
cts 30
cp 1
rs 5.0504
c 0
b 0
f 0
cc 15
eloc 22
nc 14
nop 4
crap 15

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the JVal package.
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
namespace JVal\Constraint;
11
12
use JVal\Constraint;
13
use JVal\Context;
14
use JVal\Exception\Constraint\InvalidTypeException;
15
use JVal\Types;
16
use JVal\Walker;
17
use DateTime;
18
use stdClass;
19
20
/**
21
 * Constraint for the "format" keyword.
22
 */
23
class FormatConstraint implements Constraint
24
{
25
    /**
26
     * @see http://stackoverflow.com/a/1420225
27
     */
28
    const HOSTNAME_REGEX = '/^
29
      (?=.{1,255}$)
30
      [0-9a-z]
31
      (([0-9a-z]|-){0,61}[0-9a-z])?
32
      (\.[0-9a-z](?:(?:[0-9a-z]|-){0,61}[0-9a-z])?)*
33
      \.?
34
    $/ix';
35
36
    /**
37
     * @see http://tools.ietf.org/html/rfc3986#appendix-B
38
     *
39
     * Original regex has been modified to reject URI references. It just
40
     * enforces the general structure of the URI (each part, like scheme,
41
     * authority, etc. should be validated separately)
42
     */
43
    const URI_REGEX = '#^(([^:/?\#]+):)?//([^/?\#]*)(\?([^\#]*))?(\#(.*))?#ix';
44
45
    /**
46
     * {@inheritdoc}
47
     */
48 373
    public function keywords()
49
    {
50 373
        return ['format'];
51
    }
52
53
    /**
54
     * {@inheritdoc}
55
     */
56 357
    public function supports($type)
57
    {
58 357
        return $type === Types::TYPE_STRING;
59
    }
60
61
    /**
62
     * {@inheritdoc}
63
     */
64 51
    public function normalize(stdClass $schema, Context $context, Walker $walker)
65
    {
66 51
        if (!is_string($schema->format)) {
67 1
            $context->enterNode('format');
68
69 1
            throw new InvalidTypeException($context, Types::TYPE_STRING);
70
        }
71
72
        // TODO: add option to treat unknown format as a schema error
73 50
    }
74
75
    /**
76
     * {@inheritdoc}
77
     */
78 43
    public function apply($instance, stdClass $schema, Context $context, Walker $walker)
79
    {
80 43
        if (!is_string($instance)) {
81 6
            $context->addViolation('should be a string');
82 43
        } elseif ($schema->format === 'date-time') {
83
            // PHP support for RFC3339 doesn't include fractional time
84
            // (milliseconds) so we must add another check if needed
85 5
            if (!$this->isDateTimeValid($instance, DATE_RFC3339)
86 5
                && !$this->isDateTimeValid($instance, 'Y-m-d\TH:i:s.uP')) {
87 3
                $context->addViolation('should be a valid date-time (RFC3339)');
88 3
            }
89 37
        } elseif ($schema->format === 'email') {
90 4
            if (!filter_var($instance, FILTER_VALIDATE_EMAIL)) {
91 2
                $context->addViolation('should be a valid email');
92 2
            }
93 32
        } elseif ($schema->format === 'hostname') {
94 7
            if (!preg_match(self::HOSTNAME_REGEX, $instance)) {
95 4
                $context->addViolation('should be a valid hostname');
96 4
            }
97 28
        } elseif ($schema->format === 'ipv4') {
98 8
            if (!filter_var($instance, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
99 5
                $context->addViolation('should be a valid IPv4 address');
100 5
            }
101 21
        } elseif ($schema->format === 'ipv6') {
102 6
            if (!filter_var($instance, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
103 4
                $context->addViolation('should be a valid IPv6 address');
104 4
            }
105 13
        } elseif ($schema->format === 'uri') {
106 7
            if (!preg_match(self::URI_REGEX, $instance)) {
107 3
                $context->addViolation('should be a valid URI (RFC3986)');
108 3
            }
109 7
        }
110 43
    }
111
112
    /**
113
     * {@inheritdoc}
114
     */
115 5
    private function isDateTimeValid($date, $format)
116
    {
117 5
        $dateTime = DateTime::createFromFormat($format, $date);
118
119 5
        if (!$dateTime) {
120 4
            return false;
121
        }
122
123 2
        $errors = DateTime::getLastErrors();
124
125 2
        return $errors['warning_count'] === 0 && $errors['error_count'] === 0;
126
    }
127
}
128