• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JBZoo / Utils / 29770052588

20 Jul 2026 06:55PM UTC coverage: 92.619% (-0.1%) from 92.722%
29770052588

push

github

web-flow
Merge pull request #57 from JBZoo/release/8.0

8.0.0 — PHP 8.3+ floor & lock-step major

15 of 16 new or added lines in 7 files covered. (93.75%)

106 existing lines in 13 files now uncovered.

1669 of 1802 relevant lines covered (92.62%)

41.2 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.37
/src/Email.php
1
<?php
2

3
/**
4
 * JBZoo Toolbox - Utils.
5
 *
6
 * This file is part of the JBZoo Toolbox project.
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @license    MIT
11
 * @copyright  Copyright (C) JBZoo.com, All rights reserved.
12
 * @see        https://github.com/JBZoo/Utils
13
 */
14

15
declare(strict_types=1);
16

17
namespace JBZoo\Utils;
18

19
/**
20
 * @psalm-suppress UnusedClass
21
 */
22
final class Email
23
{
24
    /**
25
     * Create random email.
26
     */
27
    public static function random(int $userNameLength = 10): string
28
    {
29
        return Str::random($userNameLength) . '@' . Str::random(5) . '.com';
6✔
30
    }
31

32
    /**
33
     * Check if email(s) is(are) valid. You can send one or an array of emails.
34
     */
35
    public static function check(mixed $emails): array
36
    {
37
        $emails = (array)$emails;
42✔
38

39
        $result = [];
42✔
40

41
        if (\count($emails) === 0) {
42✔
42
            return $result;
6✔
43
        }
44

45
        $emails = self::handleEmailsInput($emails);
36✔
46

47
        foreach ($emails as $email) {
36✔
48
            if (!self::isValid($email)) {
18✔
49
                continue;
6✔
50
            }
51

52
            if (!\in_array($email, $result, true)) {
18✔
53
                $result[] = $email;
18✔
54
            }
55
        }
56

57
        return $result;
36✔
58
    }
59

60
    /**
61
     * Check for DNS MX records of the email domain.
62
     * Notice that a (temporary) DNS error will have the same result as no records were found.
63
     * Code coverage ignored because this method requires DNS requests that could not be reliable.
64
     */
65
    public static function checkDns(string $email): bool
66
    {
67
        if (!self::isValid($email)) {
6✔
68
            return false;
6✔
69
        }
70

71
        $domain = self::extractDomain($email);
6✔
72

73
        return \checkdnsrr($domain);
6✔
74
    }
75

76
    /**
77
     * Get domains from email addresses. The not valid email addresses will be skipped.
78
     */
79
    public static function getDomain(mixed $emails): array
80
    {
81
        $emails = (array)$emails;
72✔
82

83
        $result = [];
72✔
84

85
        if (\count($emails) === 0) {
72✔
86
            return $result;
6✔
87
        }
88

89
        $emails = self::handleEmailsInput($emails);
66✔
90

91
        foreach ($emails as $email) {
66✔
92
            if (!self::isValid($email)) {
48✔
93
                continue;
12✔
94
            }
95

96
            $domain = self::extractDomain($email);
42✔
97
            if (!isStrEmpty($domain) && !\in_array($domain, $result, true)) {
42✔
98
                $result[] = $domain;
42✔
99
            }
100
        }
101

102
        return $result;
66✔
103
    }
104

105
    /**
106
     * Get domains from email addresses in alphabetical order.
107
     */
108
    public static function getDomainSorted(array $emails): array
109
    {
110
        $domains = self::getDomain($emails);
24✔
111
        \sort($domains, \SORT_STRING);
24✔
112

113
        return $domains;
24✔
114
    }
115

116
    /**
117
     * Generates a Gravatar URL.
118
     *
119
     * Size of the image:
120
     * * The default size is 32px, and it can be anywhere between 1px up to 2048px.
121
     * * If requested any value above the allowed range, then the maximum is applied.
122
     * * If requested any value bellow the minimum, then the default is applied.
123
     *
124
     * Default image:
125
     * * It can be a URL to an image.
126
     * * Or one of built-in options that Gravatar has. See Email::getGravatarBuiltInImages().
127
     * * If none is defined then a built-in default is used. See Email::getGravatarBuiltInDefaultImage().
128
     *
129
     * @see http://en.gravatar.com/site/implement/images/
130
     */
131
    public static function getGravatarUrl(string $email, int $size = 32, string $defaultImage = 'identicon'): ?string
132
    {
133
        if (isStrEmpty($email) || !self::isValid($email)) {
54✔
134
            return null;
6✔
135
        }
136

137
        $hash = \md5(\strtolower(\trim($email)));
48✔
138

139
        $parts = ['scheme' => 'http', 'host' => 'www.gravatar.com'];
48✔
140
        if (Url::isHttps()) {
48✔
141
            $parts = ['scheme' => 'https', 'host' => 'secure.gravatar.com'];
48✔
142
        }
143

144
        // Get size
145
        $size = Vars::limit(Filter::int($size), 32, 2048);
48✔
146

147
        // Prepare default images
148
        $defaultImage = \trim($defaultImage);
48✔
149
        if (\preg_match('/^(http|https)./', $defaultImage) > 0) {
48✔
150
            $defaultImage = \urldecode($defaultImage);
6✔
151
        } else {
152
            $defaultImage = \strtolower($defaultImage);
42✔
153
            if (!\in_array($defaultImage, self::getGravatarBuiltInImages(), true)) {
42✔
UNCOV
154
                $defaultImage = self::getGravatarBuiltInImages()[2];
×
155
            }
156
        }
157

158
        // Build full url
159
        $parts['path']  = '/avatar/' . $hash . '/';
48✔
160
        $parts['query'] = ['s' => $size, 'd' => $defaultImage];
48✔
161

162
        return Url::create($parts);
48✔
163
    }
164

165
    /**
166
     * Returns gravatar supported placeholders.
167
     */
168
    public static function getGravatarBuiltInImages(): array
169
    {
170
        return [
42✔
171
            '404',
42✔
172
            'mm',
42✔
173
            'identicon',
42✔
174
            'monsterid',
42✔
175
            'wavatar',
42✔
176
            'retro',
42✔
177
            'blank',
42✔
178
        ];
42✔
179
    }
180

181
    /**
182
     * Returns true if string has valid email format.
183
     */
184
    public static function isValid(?string $email): bool
185
    {
186
        if ($email === null) {
120✔
UNCOV
187
            return false;
×
188
        }
189

190
        $email = \trim($email);
120✔
191

192
        if (isStrEmpty($email)) {
120✔
UNCOV
193
            return false;
×
194
        }
195

196
        $email = \htmlspecialchars($email);
120✔
197

198
        return !(\filter_var($email, \FILTER_VALIDATE_EMAIL) === false);
120✔
199
    }
200

201
    private static function extractDomain(string $email): string
202
    {
203
        $parts  = \explode('@', $email);
48✔
204
        $domain = \array_pop($parts);
48✔
205

206
        if (Sys::isFunc('idn_to_utf8')) {
48✔
207
            /** @noinspection PhpComposerExtensionStubsInspection */
208
            return (string)\idn_to_ascii($domain, 0, \INTL_IDNA_VARIANT_UTS46);
48✔
209
        }
210

UNCOV
211
        return $domain;
×
212
    }
213

214
    /**
215
     * Transforms strings in array, and remove duplicates.
216
     * Using array_keys array_flip because is faster than array_unique:
217
     * array_unique O(n log n)
218
     * array_flip O(n).
219
     *
220
     * @see http://stackoverflow.com/questions/8321620/array-unique-vs-array-flip
221
     */
222
    private static function handleEmailsInput(array|string $emails): array
223
    {
224
        $emails = (array)$emails;
102✔
225
        $emails = \array_filter($emails);
102✔
226

227
        return \array_keys(\array_flip($emails));
102✔
228
    }
229
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc