├── .gitignore ├── src ├── Validations │ ├── ValidatorInterface.php │ ├── ValidFormatValidator.php │ ├── FreeEmailServiceValidator.php │ ├── RoleBasedEmailValidator.php │ ├── DisposableEmailValidator.php │ ├── EmailHostValidator.php │ ├── MxRecordsValidator.php │ ├── Validator.php │ └── MisspelledEmailValidator.php ├── EmailDataProviderInterface.php ├── ValidationResults.php ├── EmailDataProvider.php ├── EmailValidatorFactory.php ├── EmailAddress.php ├── EmailValidator.php └── data │ ├── role-based-email-prefixes.php │ ├── top-level-domains.php │ └── email-providers.php ├── index.php ├── docker-compose.yml ├── Dockerfile ├── Docker └── nginx │ └── default.conf ├── .github └── workflows │ └── php.yml ├── phpunit.xml ├── tests ├── Validations │ ├── EmailHostValidatorTest.php │ ├── DisposableEmailValidatorTest.php │ ├── FreeEmailProviderValidatorTest.php │ ├── RoleBasedEmailValidatorTest.php │ ├── MxRecordsValidatorTest.php │ ├── MisspelledEmailValidatorTest.php │ └── ValidFormatValidatorTest.php ├── ValidationResultsTest.php ├── EmailDataProviderTest.php ├── EmailAddressTest.php └── EmailValidationTest.php ├── composer.json ├── LICENSE ├── scripts ├── update-disposable-email-providers.php └── update-top-level-domains.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | coverage 4 | .idea 5 | coverage.xml -------------------------------------------------------------------------------- /src/Validations/ValidatorInterface.php: -------------------------------------------------------------------------------- 1 | getValidationResults()->asJson(); 11 | -------------------------------------------------------------------------------- /src/EmailDataProviderInterface.php: -------------------------------------------------------------------------------- 1 | composer-setup.php \ 4 | && php composer-setup.php \ 5 | && mv composer.phar /usr/local/bin/composer \ 6 | && rm composer-setup.php 7 | 8 | # Install git 9 | RUN apt-get update \ 10 | && apt-get -y install git \ 11 | && apt-get clean; rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* 12 | 13 | WORKDIR /web 14 | ADD . /web 15 | 16 | RUN /usr/local/bin/composer install --prefer-dist --no-dev 17 | -------------------------------------------------------------------------------- /src/Validations/ValidFormatValidator.php: -------------------------------------------------------------------------------- 1 | getEmailAddress()->isValidEmailAddressFormat(); 17 | } 18 | } -------------------------------------------------------------------------------- /Docker/nginx/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | index index.php index.html; 4 | root /www; 5 | 6 | location / { 7 | try_files $uri /index.php?$args; 8 | } 9 | 10 | location ~ \.php$ { 11 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 12 | fastcgi_pass php:9000; 13 | fastcgi_index index.php; 14 | include fastcgi_params; 15 | fastcgi_param SCRIPT_FILENAME /web/$fastcgi_script_name; 16 | fastcgi_param PATH_INFO $fastcgi_path_info; 17 | } 18 | } -------------------------------------------------------------------------------- /src/Validations/FreeEmailServiceValidator.php: -------------------------------------------------------------------------------- 1 | getEmailAddress()->getHostPart(), 18 | $this->getEmailDataProvider()->getEmailProviders(), 19 | true 20 | ); 21 | } 22 | } -------------------------------------------------------------------------------- /src/Validations/RoleBasedEmailValidator.php: -------------------------------------------------------------------------------- 1 | getEmailAddress()->getNamePart(), 18 | $this->getEmailDataProvider()->getRoleEmailPrefixes(), 19 | true 20 | ); 21 | } 22 | } -------------------------------------------------------------------------------- /src/Validations/DisposableEmailValidator.php: -------------------------------------------------------------------------------- 1 | getEmailAddress()->getHostPart(), 18 | $this->getEmailDataProvider()->getDisposableEmailProviders(), 19 | true 20 | ); 21 | } 22 | } -------------------------------------------------------------------------------- /src/ValidationResults.php: -------------------------------------------------------------------------------- 1 | results[$resultName] = $resultValue; 14 | } 15 | 16 | public function asArray(): array 17 | { 18 | return $this->results; 19 | } 20 | 21 | public function asJson(): string 22 | { 23 | return json_encode($this->results); 24 | } 25 | 26 | public function hasResults(): bool 27 | { 28 | return !empty($this->results); 29 | } 30 | } -------------------------------------------------------------------------------- /src/Validations/EmailHostValidator.php: -------------------------------------------------------------------------------- 1 | getEmailAddress()->getHostPart(); 17 | if ($hostName) { 18 | return ($this->getHostByName($hostName) !== $hostName); 19 | } 20 | 21 | return false; // @codeCoverageIgnore 22 | } 23 | 24 | protected function getHostByName(string $hostName): string 25 | { 26 | return gethostbyname($hostName); // @codeCoverageIgnore 27 | } 28 | } -------------------------------------------------------------------------------- /.github/workflows/php.yml: -------------------------------------------------------------------------------- 1 | name: PHP Composer 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | strategy: 13 | matrix: 14 | php-version: ['7.4', '8.0'] 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | 21 | - name: "Install PHP with extensions" 22 | uses: shivammathur/setup-php@v2 23 | with: 24 | coverage: "none" 25 | php-version: ${{ matrix.php-version }} 26 | 27 | - name: Validate composer.json and composer.lock 28 | run: composer validate 29 | 30 | - name: Install dependencies 31 | run: composer install --prefer-dist --no-progress --no-suggest 32 | 33 | - name: Run test suite 34 | run: ./vendor/bin/phpunit 35 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | tests 13 | 14 | 15 | 16 | 17 | src 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tests/Validations/EmailHostValidatorTest.php: -------------------------------------------------------------------------------- 1 | hostValidator->shouldReceive('getHostByName')->with('gmail.com'); 21 | $this->hostValidator->getResultResponse(); 22 | } 23 | 24 | protected function setUp(): void 25 | { 26 | $this->hostValidator = Mockery::mock(EmailHostValidator::class, [ 27 | new EmailAddress('dave@gmail.com'), 28 | ]) 29 | ->shouldAllowMockingProtectedMethods() 30 | ->makePartial(); 31 | } 32 | } -------------------------------------------------------------------------------- /src/EmailDataProvider.php: -------------------------------------------------------------------------------- 1 | =7.4", 14 | "ext-json": "*" 15 | }, 16 | "require-dev": { 17 | "mockery/mockery": "^1.3", 18 | "phpunit/phpunit": "^9.3" 19 | }, 20 | "license": "MIT", 21 | "autoload": { 22 | "psr-4": { 23 | "EmailValidation\\": "src/", 24 | "EmailValidation\\Tests\\": "tests" 25 | } 26 | }, 27 | "authors": [ 28 | { 29 | "name": "Dave Earley", 30 | "email": "dave@earley.email" 31 | } 32 | ], 33 | "scripts": { 34 | "update-data-files": [ 35 | "(cd scripts && ./update-disposable-email-providers.php)", 36 | "(cd scripts && ./update-top-level-domains.php)" 37 | ], 38 | "post-install-cmd": "@update-data-files", 39 | "post-update-cmd": "@update-data-files" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Dave Earley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Validations/MxRecordsValidator.php: -------------------------------------------------------------------------------- 1 | getEmailAddress()->isValidEmailAddressFormat()) { 19 | return false; // @codeCoverageIgnore 20 | } 21 | 22 | $results = []; 23 | foreach (self::VALID_DNS_RECORDS as $dns) { 24 | $results[] = $this->checkDns($this->getEmailAddress()->getHostPart(true), $dns); 25 | } 26 | 27 | // To be considered valid we needs an NS record and at least one MX, A or AAA record 28 | return count(array_filter($results)) >= 2; 29 | } 30 | 31 | protected function checkDns(string $host, string $type = null): bool 32 | { 33 | return checkdnsrr($host, $type); 34 | } 35 | } -------------------------------------------------------------------------------- /tests/Validations/DisposableEmailValidatorTest.php: -------------------------------------------------------------------------------- 1 | assertSame($expectedResult, $disposableEmailValidation->getResultResponse()); 23 | } 24 | 25 | public function disposableEmailsDataProvider(): array 26 | { 27 | return [ 28 | ['dave@gmail.com', false], 29 | ['dave@yahoo.com', false], 30 | ['dave@something.com', false], 31 | ['dave@bestvpn.top', true], 32 | ['dave@bel.kr', true], 33 | ['dave@10minutemail.de', true] 34 | ]; 35 | } 36 | } -------------------------------------------------------------------------------- /tests/Validations/FreeEmailProviderValidatorTest.php: -------------------------------------------------------------------------------- 1 | assertSame($expectedResult, $freeEmailServiceValidator->getResultResponse()); 23 | } 24 | 25 | public function freeEmailsDataProvider(): array 26 | { 27 | return [ 28 | ['dave@gmail.com', true], 29 | ['dave@yahoo.com', true], 30 | ['dave@hotmail.com', true], 31 | ['dave@something.com', false], 32 | ['dave@anonfreeemailservice.com', false], 33 | ['dave@reddit.com', false], 34 | ]; 35 | } 36 | } -------------------------------------------------------------------------------- /scripts/update-disposable-email-providers.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 3 | assertSame($expectedResult, $roleBasedEmailValidator->getResultResponse()); 23 | } 24 | 25 | public function rolesDataProvider(): array 26 | { 27 | return [ 28 | ['info@email.com', true], 29 | ['support@yahoo.com', true], 30 | ['contact@hotmail.com', true], 31 | ['accounts@apple.com', true], 32 | ['brian.mcgee@something.com', false], 33 | ['john.johnson@anonfreeemailservice.com', false], 34 | ['somerandom.name@reddit.com', false], 35 | ]; 36 | } 37 | } -------------------------------------------------------------------------------- /src/Validations/Validator.php: -------------------------------------------------------------------------------- 1 | emailAddress = $emailAddress; 19 | $this->emailDataProvider = $emailDataProvider; 20 | } 21 | 22 | public function getEmailAddress(): EmailAddress 23 | { 24 | return $this->emailAddress; 25 | } 26 | 27 | public function setEmailAddress(EmailAddress $emailAddress): Validator 28 | { 29 | $this->emailAddress = $emailAddress; 30 | return $this; 31 | } 32 | 33 | public function getEmailDataProvider(): EmailDataProviderInterface 34 | { 35 | return $this->emailDataProvider; 36 | } 37 | 38 | public function setEmailDataProvider(EmailDataProviderInterface $emailDataProvider): Validator 39 | { 40 | $this->emailDataProvider = $emailDataProvider; 41 | return $this; 42 | } 43 | } -------------------------------------------------------------------------------- /tests/Validations/MxRecordsValidatorTest.php: -------------------------------------------------------------------------------- 1 | mxValidator 21 | ->shouldReceive('checkDns') 22 | ->with('gmail.com.', $dns); 23 | } 24 | 25 | $this->mxValidator->getResultResponse(); 26 | } 27 | 28 | protected function setUp(): void 29 | { 30 | $this->mxValidator = Mockery::mock(MxRecordsValidator::class, [ 31 | new EmailAddress('dave@gmail.com'), 32 | ]) 33 | ->shouldAllowMockingProtectedMethods() 34 | ->shouldReceive('getResultResponse') 35 | ->passthru() 36 | ->getMock() 37 | ->shouldReceive('getEmailAddress') 38 | ->passthru() 39 | ->getMock(); 40 | } 41 | } -------------------------------------------------------------------------------- /scripts/update-top-level-domains.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 3 | assertSame($expectedResult, $misspelledEmailValidator->getResultResponse()); 23 | } 24 | 25 | public function emailsDataProvider(): array 26 | { 27 | return [ 28 | ['dave@gmail.con', 'dave@gmail.com'], 29 | ['dave@gmaal.com', 'dave@gmail.com'], 30 | ['dave@gmail.xom', 'dave@gmail.com'], 31 | ['dave@yahoo.oe', 'dave@yahoo.de'], 32 | ['dave@a-made-up-domain.infi', 'dave@a-made-up-domain.info'], 33 | ['info@iroland.cim', 'info@ireland.com'], 34 | ['info@gmail.com', ''] 35 | ]; 36 | } 37 | } -------------------------------------------------------------------------------- /tests/ValidationResultsTest.php: -------------------------------------------------------------------------------- 1 | validationResults->addResult('a-key', 'a-value'); 15 | $actual = $this->validationResults->asArray(); 16 | $expected = [ 17 | 'a-key' => 'a-value' 18 | ]; 19 | 20 | $this->assertSame($expected, $actual); 21 | } 22 | 23 | public function testReturnsJson() 24 | { 25 | $this->validationResults->addResult('a-key', 'a-value'); 26 | $actual = $this->validationResults->asJson(); 27 | $expected = json_encode([ 28 | 'a-key' => 'a-value' 29 | ]); 30 | 31 | $this->assertSame($expected, $actual); 32 | } 33 | 34 | public function testHasResults(): void 35 | { 36 | $validationResults = new ValidationResults(); 37 | $this->assertFalse($validationResults->hasResults()); 38 | 39 | $validationResults->addResult('a-key', 'a-value'); 40 | $this->assertTrue($validationResults->hasResults()); 41 | } 42 | 43 | protected function setUp(): void 44 | { 45 | $this->validationResults = new ValidationResults(); 46 | } 47 | } -------------------------------------------------------------------------------- /tests/EmailDataProviderTest.php: -------------------------------------------------------------------------------- 1 | emailDataProvider->getEmailProviders(); 15 | $this->assertIsArray($emailProviders); 16 | $this->assertContains('gmail.com', $emailProviders); 17 | } 18 | 19 | public function testGetDisposableEmailProviders(): void 20 | { 21 | $emailProviders = $this->emailDataProvider->getDisposableEmailProviders(); 22 | $this->assertIsArray($emailProviders); 23 | $this->assertContains('banit.club', $emailProviders); 24 | } 25 | 26 | public function testGetRoleBasesPrefixes(): void 27 | { 28 | $prefixes = $this->emailDataProvider->getRoleEmailPrefixes(); 29 | $this->assertIsArray($prefixes); 30 | $this->assertContains('ceo', $prefixes); 31 | } 32 | 33 | public function testGetTopLevelDomains(): void 34 | { 35 | $tlds = $this->emailDataProvider->getTopLevelDomains(); 36 | $this->assertIsArray($tlds); 37 | $this->assertContains('aero', $tlds); 38 | } 39 | 40 | protected function setUp(): void 41 | { 42 | $this->emailDataProvider = new EmailDataProvider(); 43 | } 44 | } -------------------------------------------------------------------------------- /tests/Validations/ValidFormatValidatorTest.php: -------------------------------------------------------------------------------- 1 | assertSame($expectedResult, $disposableEmailValidation->getResultResponse()); 23 | } 24 | 25 | public function emailsDataProvider(): array 26 | { 27 | return [ 28 | ['dave@gmail.com', true], 29 | ['dave.earley@yahoo.com', true], 30 | ['dave@something.ie', true], 31 | ['john.doe@buy.tickets', true], 32 | ['firstname+lastname@example.com', true], 33 | ['firstname-lastname@[127.0.0.1]', true], 34 | ['_______@example.com', true], 35 | ['someone@example.com.com.jp', true], 36 | ['dave', false], 37 | ['172.123.2.4', false], 38 | ['hello', false], 39 | [true, false], 40 | [12, false], 41 | [-99, false], 42 | ]; 43 | } 44 | } -------------------------------------------------------------------------------- /src/EmailValidatorFactory.php: -------------------------------------------------------------------------------- 1 | registerValidator(new $validator); 38 | } 39 | 40 | return $emailValidator; 41 | } 42 | } -------------------------------------------------------------------------------- /src/EmailAddress.php: -------------------------------------------------------------------------------- 1 | emailAddress = $emailAddress; 17 | } 18 | 19 | public function getNamePart(): ?string 20 | { 21 | if ($this->isValidEmailAddressFormat()) { 22 | return $this->getEmailPart(self::EMAIL_NAME_PART); 23 | } 24 | 25 | return null; 26 | } 27 | 28 | public function isValidEmailAddressFormat(): bool 29 | { 30 | return filter_var($this->emailAddress, FILTER_VALIDATE_EMAIL) !== false; 31 | } 32 | 33 | private function getEmailPart(int $partNumber): string 34 | { 35 | return (explode('@', $this->emailAddress))[$partNumber]; 36 | } 37 | 38 | public function getHostPart(bool $returnFqdn = false): ?string 39 | { 40 | if ($this->isValidEmailAddressFormat()) { 41 | return $this->getEmailPart(self::EMAIL_HOST_PART) . ($returnFqdn ? '.' : ''); 42 | } 43 | 44 | return null; 45 | } 46 | 47 | public function getTopLevelDomainPart(): ?string 48 | { 49 | if ($this->isValidEmailAddressFormat()) { 50 | return explode('.', $this->getEmailPart(self::EMAIL_HOST_PART))[1]; 51 | } 52 | 53 | return null; 54 | } 55 | 56 | public function asString(): string 57 | { 58 | return $this->emailAddress; 59 | } 60 | } -------------------------------------------------------------------------------- /tests/EmailAddressTest.php: -------------------------------------------------------------------------------- 1 | assertSame(self::VALID_TEST_EMAIL, $this->validEmail->asString()); 20 | } 21 | 22 | public function testGetHostPart(): void 23 | { 24 | $this->assertSame('gmail.com', $this->validEmail->getHostPart()); 25 | } 26 | 27 | public function testGetTldPart(): void 28 | { 29 | $this->assertSame('com', $this->validEmail->getTopLevelDomainPart()); 30 | } 31 | 32 | public function testGetNamePart(): void 33 | { 34 | $this->assertSame('dave', $this->validEmail->getNamePart()); 35 | } 36 | 37 | public function testGetHostPartForInvalidEmail(): void 38 | { 39 | $this->assertNull($this->invalidEmail->getHostPart()); 40 | } 41 | 42 | public function testGetTldPartForInvalidEmail(): void 43 | { 44 | $this->assertNull($this->invalidEmail->getTopLevelDomainPart()); 45 | } 46 | 47 | public function testGetNamePartForInvalidEmail(): void 48 | { 49 | $this->assertNull($this->invalidEmail->getNamePart()); 50 | } 51 | 52 | public function testIsValidFormat(): void 53 | { 54 | $this->assertTrue($this->validEmail->isValidEmailAddressFormat()); 55 | } 56 | 57 | public function testIsValidFormatForInvalidEmail(): void 58 | { 59 | $this->assertFalse($this->invalidEmail->isValidEmailAddressFormat()); 60 | } 61 | 62 | protected function setUp(): void 63 | { 64 | $this->validEmail = new EmailAddress(self::VALID_TEST_EMAIL); 65 | $this->invalidEmail = new EmailAddress(self::INVALID_TEST_EMAIL); 66 | } 67 | } -------------------------------------------------------------------------------- /src/EmailValidator.php: -------------------------------------------------------------------------------- 1 | emailAddress = $emailAddress; 27 | $this->validationResults = $validationResults; 28 | $this->emailDataProvider = $emailDataProvider; 29 | } 30 | 31 | /** 32 | * @param Validator[] $validators 33 | * @return self 34 | */ 35 | public function registerValidators(array $validators): EmailValidator 36 | { 37 | foreach ($validators as $validator) { 38 | $this->registerValidator($validator); 39 | } 40 | return $this; 41 | } 42 | 43 | public function registerValidator(Validator $validator): EmailValidator 44 | { 45 | $this->registeredValidators[] = $validator 46 | ->setEmailAddress($this->getEmailAddress()) 47 | ->setEmailDataProvider($this->getEmailDataProvider()); 48 | return $this; 49 | } 50 | 51 | private function getEmailAddress(): EmailAddress 52 | { 53 | return $this->emailAddress; 54 | } 55 | 56 | private function getEmailDataProvider(): EmailDataProviderInterface 57 | { 58 | return $this->emailDataProvider; 59 | } 60 | 61 | public function getValidationResults(): ValidationResults 62 | { 63 | if (!$this->validationResults->hasResults()) { 64 | $this->runValidation(); 65 | } 66 | return $this->validationResults; 67 | } 68 | 69 | public function runValidation(): void 70 | { 71 | foreach ($this->registeredValidators as $validator) { 72 | $this->validationResults->addResult($validator->getValidatorName(), $validator->getResultResponse()); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /tests/EmailValidationTest.php: -------------------------------------------------------------------------------- 1 | validationResultsMock 27 | ->shouldReceive('addResult') 28 | ->times(1); 29 | 30 | /** @var MisspelledEmailValidator|MockInterface $mockValidation */ 31 | $mockValidation = Mockery::mock(MisspelledEmailValidator::class); 32 | 33 | $mockValidation->shouldReceive('getValidatorName')->andReturn('hello'); 34 | $mockValidation->shouldReceive('getResultResponse')->andReturn('hello'); 35 | $mockValidation->shouldReceive('setEmailAddress')->andReturnSelf(); 36 | $mockValidation->shouldReceive('setEmailDataProvider')->andReturnSelf(); 37 | 38 | $this->emailValidation->registerValidators([$mockValidation]); 39 | $this->emailValidation->runValidation(); 40 | } 41 | 42 | public function testGetValidationResults(): void 43 | { 44 | $this->validationResultsMock->shouldReceive('addResult')->times(1); 45 | $this->validationResultsMock->shouldReceive('hasResults')->andReturn(false); 46 | $this->validationResultsMock->shouldReceive('getValidationResults')->andReturnSelf(); 47 | $this->validationResultsMock->shouldReceive('asArray')->andReturn([ 48 | 'valid_email' => true 49 | ] 50 | ); 51 | 52 | /** @var ValidFormatValidator|MockInterface $mockValidation */ 53 | $mockValidation = Mockery::mock(ValidFormatValidator::class); 54 | 55 | $mockValidation->shouldReceive('getValidatorName')->andReturn('valid_format'); 56 | $mockValidation->shouldReceive('getResultResponse')->andReturn(true); 57 | $mockValidation->shouldReceive('setEmailAddress')->andReturnSelf(); 58 | $mockValidation->shouldReceive('setEmailDataProvider')->andReturnSelf(); 59 | 60 | $this->emailValidation->registerValidator($mockValidation); 61 | 62 | $actual = $this->emailValidation->getValidationResults(); 63 | $this->assertInstanceOf(ValidationResults::class, $actual); 64 | } 65 | 66 | protected function setUp(): void 67 | { 68 | $emailMock = Mockery::mock(EmailAddress::class); 69 | $this->validationResultsMock = Mockery::mock(ValidationResults::class); 70 | $emailDataProviderMock = Mockery::mock(EmailDataProvider::class); 71 | $this->emailValidation = new EmailValidator( 72 | $emailMock, 73 | $this->validationResultsMock, 74 | $emailDataProviderMock 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Validations/MisspelledEmailValidator.php: -------------------------------------------------------------------------------- 1 | getEmailAddress()->isValidEmailAddressFormat()) { 23 | return ''; // @codeCoverageIgnore 24 | } 25 | 26 | $suggestion = $this->findEmailAddressSuggestion(); 27 | if ($suggestion === $this->getEmailAddress()->asString()) { 28 | return ''; // @codeCoverageIgnore 29 | } 30 | 31 | return $suggestion; 32 | } 33 | 34 | /** 35 | * @return string 36 | */ 37 | private function findEmailAddressSuggestion(): string 38 | { 39 | if ($domainSuggestion = $this->findDomainSuggestion()) { 40 | return str_replace( 41 | $this->getEmailAddress()->getHostPart(), 42 | $domainSuggestion, 43 | $this->getEmailAddress()->asString() 44 | ); 45 | } 46 | 47 | if ($topLevelDomainSuggestion = $this->findTopLevelDomainSuggestion()) { 48 | return str_replace( 49 | $this->getEmailAddress()->getTopLevelDomainPart(), 50 | $topLevelDomainSuggestion, 51 | $this->getEmailAddress()->asString() 52 | ); 53 | } 54 | 55 | return ''; 56 | } 57 | 58 | /** 59 | * @return bool|null|string 60 | */ 61 | private function findDomainSuggestion() 62 | { 63 | $domain = $this->getEmailAddress()->getHostPart(); 64 | $possibleMatch = $this->findClosestWord( 65 | $domain, 66 | $this->getEmailDataProvider()->getEmailProviders(), 67 | self::MINIMUM_WORD_DISTANCE_DOMAIN 68 | ); 69 | 70 | return $domain === $possibleMatch ? null : $possibleMatch; 71 | } 72 | 73 | private function findClosestWord(string $stringToCheck, array $wordsToCheck, int $minimumDistance): string 74 | { 75 | if (in_array($stringToCheck, $wordsToCheck)) { 76 | return $stringToCheck; 77 | } 78 | 79 | $closestMatch = ''; 80 | foreach ($wordsToCheck as $testedWord) { 81 | $distance = levenshtein($stringToCheck, $testedWord); 82 | if ($distance <= $minimumDistance) { 83 | $minimumDistance = $distance - 1; 84 | $closestMatch = $testedWord; 85 | } 86 | } 87 | 88 | return $closestMatch; 89 | } 90 | 91 | /** 92 | * @return bool|null|string 93 | */ 94 | private function findTopLevelDomainSuggestion() 95 | { 96 | $topLevelDomain = $this->getEmailAddress()->getTopLevelDomainPart(); 97 | $possibleTopLevelMatch = $this->findClosestWord( 98 | $topLevelDomain, 99 | $this->getEmailDataProvider()->getTopLevelDomains(), 100 | self::MINIMUM_WORD_DISTANCE_TLD 101 | ); 102 | 103 | return $topLevelDomain === $possibleTopLevelMatch ? null : $possibleTopLevelMatch; 104 | } 105 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | PHP Email Validation Tool 3 |

4 | 5 | [![codecov](https://codecov.io/gh/daveearley/Email-Validation-Tool/branch/master/graph/badge.svg)](https://codecov.io/gh/daveearley/Email-Validation-Tool/) [![Build Status](https://travis-ci.org/daveearley/Email-Validation-Tool.svg?branch=master)](https://travis-ci.org/daveearley/Email-Validation-Tool) [![Code Climate](https://codeclimate.com/github/daveearley/Email-Validation-Tool/badges/gpa.svg)](https://codeclimate.com/github/daveearley/Email-Validation-Tool/) 6 | 7 | **An extensible email validation library for PHP 7+** 8 | 9 | The aim of this library is to offer a more detailed email validation report than simply checking if an email is the valid format, and also to make it possible to easily add custom validations. 10 | 11 | Currently this tool checks the following: 12 | 13 | 14 | | Validation | Description | 15 | | ------------- | ------------- | 16 | | MX records | Checks if the email's domain has valid MX records | 17 | | Valid format | Validates e-mail addresses against the syntax in RFC 822, with the exceptions that comments and whitespace folding and dotless domain names are not supported (as it uses PHP's [filter_var()](http://php.net/manual/en/function.filter-var.php)). | 18 | | Email Host | Checks if the email's host (e.g gmail.com) is reachable | 19 | | Role/Business Email^ | Checks if the email is a role/business based email (e.g info@reddit.com). | 20 | | Disposable email provider^ | Checks if the email is a [disposable email](https://en.wikipedia.org/wiki/Disposable_email_address) (e.g person@10minutemail.com). | 21 | | Free email provider^ | Checks if the email is a free email (e.g person@yahoo.com). | 22 | | Misspelled Email ^ | Checks the email for possible typos and returns a suggested correction (e.g hi@gmaol.con -> hi@gmail.com). | 23 | 24 | ^ **Data used for these checks can be found [here](https://github.com/daveearley/Email-Validation-Tool/tree/master/src/data)** 25 | 26 | # Installation 27 | 28 | ```bash 29 | composer require daveearley/daves-email-validation-tool 30 | ``` 31 | 32 | # Usage 33 | ## Quick Start 34 | 35 | ```php 36 | // Include the composer autoloader 37 | require __DIR__ . '/vendor/autoload.php'; 38 | 39 | $validator = EmailValidation\EmailValidatorFactory::create('dave@gmoil.con'); 40 | 41 | $jsonResult = $validator->getValidationResults()->asJson(); 42 | $arrayResult = $validator->getValidationResults()->asArray(); 43 | 44 | echo $jsonResult; 45 | 46 | ``` 47 | 48 | Expected output: 49 | 50 | ```json 51 | { 52 | "valid_format": true, 53 | "valid_mx_records": false, 54 | "possible_email_correction": "dave@gmail.com", 55 | "free_email_provider": false, 56 | "disposable_email_provider": false, 57 | "role_or_business_email": false, 58 | "valid_host": false 59 | } 60 | ``` 61 | 62 | ## Adding Custom Validations 63 | 64 | To add a custom validation simply extend the [EmailValidation\Validations\Validator](https://github.com/daveearley/Email-Validation-Tool/blob/master/src/Validations/Validator.php) class and implement the **getResultResponse()** and **getValidatorName()** methods. You then register the validation using the **EmailValidation\EmailValidator->registerValidator()** method. 65 | 66 | 67 | ### Example code 68 | 69 | // Validations/GmailValidator.php 70 | ```php 71 | getEmailAddress()->getHostPart(); 85 | return strpos($hostName, 'gmail.com') !== false; 86 | } 87 | } 88 | ``` 89 | 90 | // file-where-you-are-doing-your-validation.php 91 | ```php 92 | registerValidator(new GmailValidator()); 101 | 102 | echo $validator->getValidationResults()->asJson(); 103 | ``` 104 | 105 | The expected output will be: 106 | 107 | ```json 108 | { 109 | "is_gmail": true, 110 | "valid_format": true, 111 | "valid_mx_records": false, 112 | "possible_email_correction": "", 113 | "free_email_provider": true, 114 | "disposable_email_provider": false, 115 | "role_or_business_email": false, 116 | "valid_host": false 117 | } 118 | ``` 119 | 120 | ## Running in Docker 121 | ```bash 122 | docker-compose up -d 123 | ``` 124 | You can then validate an email by navigating to http://localhost:8880?email=email.to.validate@example.com. The result will be JSON string as per above. 125 | 126 | ## Adding a custom data source 127 | 128 | You can create your own data provider by creating a data provider class which implements the [EmailValidation\EmailDataProviderInterface](https://github.com/daveearley/Email-Validation-Tool/blob/master/src/EmailDataProviderInterface.php). 129 | 130 | Example Code: 131 | 132 | ```php 133 |