Test Setup Failed
Push — master ( e94fe5...b7ec94 )
by
unknown
02:01
created

Email::validateValue()   C

Complexity

Conditions 16
Paths 96

Size

Total Lines 60
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 17.0485

Importance

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