Passed
Pull Request — master (#94)
by
unknown
02:00
created

Email::validateValue()   C

Complexity

Conditions 16
Paths 96

Size

Total Lines 52
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 16.0163

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 16
eloc 29
c 4
b 0
f 0
nc 96
nop 2
dl 0
loc 52
ccs 24
cts 25
cp 0.96
crap 16.0163
rs 5.5666

How to fix   Long Method    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
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\Rule;
6
7
use Yiisoft\Validator\DataSetInterface;
8
use Yiisoft\Validator\HasValidationErrorMessage;
9
use Yiisoft\Validator\Result;
10
use Yiisoft\Validator\Rule;
11
12
/**
13
 * EmailValidator validates that the attribute value is a valid email address.
14
 */
15
class Email extends Rule
16
{
17
    use HasValidationErrorMessage;
18
19
    /**
20
     * @var string the regular expression used to validateValue the attribute value.
21
     *
22
     * @see http://www.regular-expressions.info/email.html
23
     */
24
    private string $pattern = '/^[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$/';
25
    /**
26
     * @var string the regular expression used to validateValue email addresses with the name part.
27
     * This property is used only when [[allowName]] is true.
28
     *
29
     * @see allowName
30
     */
31
    private string $fullPattern = '/^[^@]*<[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?>$/';
32
    /**
33
     * @var string the regular expression used to validate complex emails when idn is enabled.
34
     */
35
    private string $patternIdnEmail = '/^([a-zA-Z0-9._%+-]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/';
36
    /**
37
     * @var bool whether to allow name in the email address (e.g. "John Smith <[email protected]>"). Defaults to false.
38
     *
39
     * @see fullPattern
40
     */
41
    private bool $allowName = false;
42
    /**
43
     * @var bool whether to check whether the email's domain exists and has either an A or MX record.
44
     * Be aware that this check can fail due to temporary DNS problems even if the email address is
45
     * valid and an email would be deliverable. Defaults to false.
46
     */
47
    private bool $checkDNS = false;
48
    /**
49
     * @var bool whether validation process should take into account IDN (internationalized domain
50
     * names). Defaults to false meaning that validation of emails containing IDN will always fail.
51
     * Note that in order to use IDN validation you have to install and enable `intl` PHP extension,
52
     * otherwise an exception would be thrown.
53
     */
54
    private bool $enableIDN = false;
55
56
    private string $message = 'This value is not a valid email address.';
57
58 37
    protected function validateValue($value, DataSetInterface $dataSet = null): Result
59
    {
60 37
        $originalValue = $value;
61 37
        $result = new Result();
62
63 37
        if (!is_string($value)) {
64 1
            $valid = false;
65
        } elseif (!preg_match(
66 37
            '/^(?P<name>(?:"?([^"]*)"?\s)?)(?:\s+)?(?:(?P<open><?)((?P<local>.+)@(?P<domain>[^>]+))(?P<close>>?))$/i',
67
            $value,
68
            $matches
69
        )) {
70 2
            $valid = false;
71
        } else {
72 37
            if ($this->enableIDN) {
73 35
                $matches['local'] = $this->idnToAscii($matches['local']);
74 35
                $matches['domain'] = $this->idnToAscii($matches['domain']);
75 35
                $value = $matches['name'] . $matches['open'] . $matches['local'] . '@' . $matches['domain'] . $matches['close'];
76
            }
77
78 37
            if (is_string($matches['local']) && strlen($matches['local']) > 64) {
79
                // The maximum total length of a user name or other local-part is 64 octets. RFC 5322 section 4.5.3.1.1
80
                // http://tools.ietf.org/html/rfc5321#section-4.5.3.1.1
81 1
                $valid = false;
82 37
            } elseif (is_string($matches['local']) && strlen($matches['local'] . '@' . $matches['domain']) > 254) {
83
                // There is a restriction in RFC 2821 on the length of an address in MAIL and RCPT commands
84
                // of 254 characters. Since addresses that do not fit in those fields are not normally useful, the
85
                // upper limit on address lengths should normally be considered to be 254.
86
                //
87
                // Dominic Sayers, RFC 3696 erratum 1690
88
                // http://www.rfc-editor.org/errata_search.php?eid=1690
89 1
                $valid = false;
90
            } else {
91 37
                $valid = preg_match($this->pattern, $value) || ($this->allowName && preg_match(
92 37
                    $this->fullPattern,
93
                    $value
94
                ));
95 37
                if ($valid && $this->checkDNS) {
96 1
                    $valid = checkdnsrr($matches['domain'] . '.', 'MX') || checkdnsrr($matches['domain'] . '.', 'A');
97
                }
98
            }
99
        }
100
101 37
        if ($this->enableIDN && $valid === false) {
102 35
            $valid = (bool) preg_match($this->patternIdnEmail, $originalValue);
103
        }
104
105 37
        if ($valid === false) {
106 37
            $result->addError($this->translateMessage($this->message));
107
        }
108
109 37
        return $result;
110
    }
111
112 35
    private function idnToAscii($idn)
113
    {
114 35
        return idn_to_ascii($idn, 0, INTL_IDNA_VARIANT_UTS46);
115
    }
116
117
    public function patternIdnEmail(string $patternIdnEmail): self
118
    {
119
        $new = clone $this;
120
        $new->patternIdnEmail = $patternIdnEmail;
121
        return $new;
122
    }
123
124 3
    public function allowName(bool $allowName): self
125
    {
126 3
        $new = clone $this;
127 3
        $new->allowName = $allowName;
128 3
        return $new;
129
    }
130
131 1
    public function checkDNS(bool $checkDNS): self
132
    {
133 1
        $new = clone $this;
134 1
        $new->checkDNS = $checkDNS;
135 1
        return $new;
136
    }
137
138 35
    public function enableIDN(bool $enableIDN): self
139
    {
140 35
        if ($enableIDN && !function_exists('idn_to_ascii')) {
141
            throw new \RuntimeException('In order to use IDN validation intl extension must be installed and enabled.');
142
        }
143
144 35
        $new = clone $this;
145 35
        $new->enableIDN = $enableIDN;
146 35
        return $new;
147
    }
148
149 4
    public function getOptions(): array
150
    {
151 4
        return array_merge(
152
            parent::getOptions(),
153
            [
154 4
                'allowName' => $this->allowName,
155 4
                'checkDNS' => $this->checkDNS,
156 4
                'enableIDN' => $this->enableIDN,
157 4
                'message' => $this->translateMessage($this->message),
158
            ],
159
        );
160
    }
161
}
162