Passed
Pull Request — master (#219)
by
unknown
02:48
created

Email::__construct()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 48
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3.1406

Importance

Changes 0
Metric Value
cc 3
eloc 3
nc 2
nop 11
dl 0
loc 48
ccs 3
cts 4
cp 0.75
crap 3.1406
rs 10
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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 90
    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
        private string $message = 'This value is not a valid email address.',
63
        ?FormatterInterface $formatter = null,
64
        bool $skipOnEmpty = false,
65
        bool $skipOnError = false,
66
        $when = null
67
    ) {
68 90
        parent::__construct(formatter: $formatter, skipOnEmpty: $skipOnEmpty, skipOnError: $skipOnError, when: $when);
69
70 90
        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
        }
73
    }
74
75
    /**
76
     * @see $pattern
77
     */
78 1
    public function pattern(string $value): self
79
    {
80 1
        $new = clone $this;
81 1
        $new->pattern = $value;
82
83 1
        return $new;
84
    }
85
86
    /**
87
     * @see $fullPattern
88
     */
89 1
    public function fullPattern(string $value): self
90
    {
91 1
        $new = clone $this;
92 1
        $new->fullPattern = $value;
93
94 1
        return $new;
95
    }
96
97
    /**
98
     * @see $idnEmailPattern
99
     */
100 1
    public function idnEmailPattern(string $value): self
101
    {
102 1
        $new = clone $this;
103 1
        $new->idnEmailPattern = $value;
104
105 1
        return $new;
106
    }
107
108
    /**
109
     * @see $allowName
110
     */
111 1
    public function allowName(bool $value): self
112
    {
113 1
        $new = clone $this;
114 1
        $new->allowName = $value;
115
116 1
        return $new;
117
    }
118
119
    /**
120
     * @see $checkDNS
121
     */
122 1
    public function checkDNS(bool $value): self
123
    {
124 1
        $new = clone $this;
125 1
        $new->checkDNS = $value;
126
127 1
        return $new;
128
    }
129
130
    /**
131
     * @see $enableIDN
132
     */
133 1
    public function enableIDN(bool $value): self
134
    {
135 1
        $new = clone $this;
136 1
        $new->enableIDN = $value;
137
138 1
        return $new;
139
    }
140
141
    /**
142
     * @see $message
143
     */
144 1
    public function message(string $value): self
145
    {
146 1
        $new = clone $this;
147 1
        $new->message = $value;
148
149 1
        return $new;
150
    }
151
152 82
    protected function validateValue($value, ?ValidationContext $context = null): Result
153
    {
154 82
        $originalValue = $value;
155 82
        $result = new Result();
156
157 82
        if (!is_string($value)) {
158 2
            $valid = false;
159 80
        } elseif (!preg_match(
160
            '/^(?P<name>(?:"?([^"]*)"?\s)?)(?:\s+)?((?P<open><?)((?P<local>.+)@(?P<domain>[^>]+))(?P<close>>?))$/i',
161
            $value,
162
            $matches
163
        )) {
164 6
            $valid = false;
165
        } else {
166
            /** @psalm-var array{name:string,local:string,open:string,domain:string,close:string} $matches */
167 74
            if ($this->enableIDN) {
168 27
                $matches['local'] = $this->idnToAscii($matches['local']);
169 27
                $matches['domain'] = $this->idnToAscii($matches['domain']);
170 27
                $value = implode([
171 27
                    $matches['name'],
172 27
                    $matches['open'],
173 27
                    $matches['local'],
174
                    '@',
175 27
                    $matches['domain'],
176 27
                    $matches['close'],
177
                ]);
178
            }
179
180 74
            if (is_string($matches['local']) && strlen($matches['local']) > 64) {
181
                // The maximum total length of a user name or other local-part is 64 octets. RFC 5322 section 4.5.3.1.1
182
                // http://tools.ietf.org/html/rfc5321#section-4.5.3.1.1
183 1
                $valid = false;
184 73
            } elseif (is_string($matches['local']) && strlen($matches['local'] . '@' . $matches['domain']) > 254) {
185
                // There is a restriction in RFC 2821 on the length of an address in MAIL and RCPT commands
186
                // of 254 characters. Since addresses that do not fit in those fields are not normally useful, the
187
                // upper limit on address lengths should normally be considered to be 254.
188
                //
189
                // Dominic Sayers, RFC 3696 erratum 1690
190
                // http://www.rfc-editor.org/errata_search.php?eid=1690
191 1
                $valid = false;
192
            } else {
193 72
                $valid = preg_match($this->pattern, $value) || ($this->allowName && preg_match(
194 47
                    $this->fullPattern,
195
                    $value
196
                ));
197 72
                if ($valid && $this->checkDNS) {
198 4
                    $valid = checkdnsrr($matches['domain'] . '.', 'MX') || checkdnsrr($matches['domain'] . '.', 'A');
199
                }
200
            }
201
        }
202
203 82
        if ($this->enableIDN && $valid === false) {
204 10
            $valid = (bool) preg_match($this->idnEmailPattern, $originalValue);
205
        }
206
207 82
        if ($valid === false) {
208 44
            $result->addError($this->formatMessage($this->message));
209
        }
210
211 82
        return $result;
212
    }
213
214 27
    private function idnToAscii($idn)
215
    {
216 27
        return idn_to_ascii($idn, 0, INTL_IDNA_VARIANT_UTS46);
217
    }
218
219 11
    public function getOptions(): array
220
    {
221 11
        return array_merge(parent::getOptions(), [
222 11
            'pattern' => $this->pattern,
223 11
            'fullPattern' => $this->fullPattern,
224 11
            'idnEmailPattern' => $this->idnEmailPattern,
225 11
            'allowName' => $this->allowName,
226 11
            'checkDNS' => $this->checkDNS,
227 11
            'enableIDN' => $this->enableIDN,
228 11
            'message' => $this->formatMessage($this->message),
229
        ]);
230
    }
231
}
232