EmailVerify LogoEmailVerify

Laravel

Email checker for Laravel. PHP email verification in Laravel controllers and forms.

使用自定义验证规则、中间件和服务提供者将电子邮件验证添加到您的 Laravel 应用中。

安装

composer require emailverify/laravel

或直接安装 PHP SDK:

composer require emailverify/php-sdk

配置

发布配置

php artisan vendor:publish --tag=emailverify-config

环境变量

# .env
EMAILVERIFY_API_KEY=bv_live_xxx

配置文件

// config/emailverify.php
return [
    'api_key' => env('EMAILVERIFY_API_KEY'),
    'timeout' => 10,
    'cache_duration' => 3600, // 1 小时
    'block_disposable' => true,
    'block_role_based' => false,
];

服务提供者

如果未使用自动发现,请注册服务提供者:

// config/app.php
'providers' => [
    EmailVerify\Laravel\EmailVerifyServiceProvider::class,
],

'aliases' => [
    'EmailVerify' => EmailVerify\Laravel\Facades\EmailVerify::class,
],

验证规则

使用内置规则

use App\Http\Requests\RegisterRequest;

class RegisterRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'email' => ['required', 'email', 'emailverify'],
            'name' => ['required', 'string', 'max:255'],
            'password' => ['required', 'confirmed', 'min:8'],
        ];
    }

    public function messages(): array
    {
        return [
            'email.emailverify' => '请提供有效的电子邮件地址。',
        ];
    }
}

自定义验证规则

// app/Rules/ValidEmail.php
<?php

namespace App\Rules;

use EmailVerify\Client;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class ValidEmail implements ValidationRule
{
    protected bool $blockDisposable;
    protected bool $blockRoleBased;

    public function __construct(
        bool $blockDisposable = true,
        bool $blockRoleBased = false
    ) {
        $this->blockDisposable = $blockDisposable;
        $this->blockRoleBased = $blockRoleBased;
    }

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $client = new Client(config('emailverify.api_key'));

        try {
            $result = $client->verify($value);

            if ($result->status === 'invalid') {
                $fail(':attribute 必须是有效的电子邮件地址。');
                return;
            }

            if ($this->blockDisposable && $result->result->disposable) {
                $fail('不允许使用一次性电子邮件地址。');
                return;
            }

            if ($this->blockRoleBased && $result->result->role) {
                $fail('不允许使用基于角色的电子邮件地址。');
                return;
            }

        } catch (\Exception $e) {
            // 记录错误但不阻止提交
            \Log::warning('EmailVerify API 错误', [
                'email' => $value,
                'error' => $e->getMessage(),
            ]);
        }
    }
}

使用方法

use App\Rules\ValidEmail;

$request->validate([
    'email' => ['required', 'email', new ValidEmail(
        blockDisposable: true,
        blockRoleBased: false
    )],
]);

外观使用

use EmailVerify\Laravel\Facades\EmailVerify;

// 单个验证
$result = EmailVerify::verify('user@example.com');

if ($result->status === 'valid') {
    // 电子邮件有效
}

// 检查特定属性
if (EmailVerify::isDisposable('user@example.com')) {
    // 阻止一次性电子邮件
}

if (EmailVerify::isValid('user@example.com')) {
    // 电子邮件可递送
}

中间件

为路由级别验证创建中间件。

创建中间件

// app/Http/Middleware/VerifyEmailAddress.php
<?php

namespace App\Http\Middleware;

use EmailVerify\Client;
use Closure;
use Illuminate\Http\Request;

class VerifyEmailAddress
{
    protected Client $client;

    public function __construct()
    {
        $this->client = new Client(config('emailverify.api_key'));
    }

    public function handle(Request $request, Closure $next, string $field = 'email')
    {
        $email = $request->input($field);

        if (!$email) {
            return $next($request);
        }

        try {
            $result = $this->client->verify($email);

            if ($result->status === 'invalid') {
                return response()->json([
                    'error' => '无效的电子邮件地址',
                ], 422);
            }

            // 将验证结果添加到请求
            $request->merge(['email_verification' => $result]);

        } catch (\Exception $e) {
            \Log::warning('电子邮件验证失败', [
                'email' => $email,
                'error' => $e->getMessage(),
            ]);
        }

        return $next($request);
    }
}

注册中间件

// bootstrap/app.php (Laravel 11+)
->withMiddleware(function (Middleware $middleware) {
    $middleware->alias([
        'verify.email' => \App\Http\Middleware\VerifyEmailAddress::class,
    ]);
})

// 或者在 app/Http/Kernel.php (Laravel 10)
protected $middlewareAliases = [
    'verify.email' => \App\Http\Middleware\VerifyEmailAddress::class,
];

在路由中使用

Route::post('/register', [AuthController::class, 'register'])
    ->middleware('verify.email');

Route::post('/subscribe', [NewsletterController::class, 'subscribe'])
    ->middleware('verify.email:subscriber_email');

控制器集成

// app/Http/Controllers/AuthController.php
<?php

namespace App\Http\Controllers;

use App\Models\User;
use EmailVerify\Client;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;

class AuthController extends Controller
{
    protected Client $emailVerify;

    public function __construct()
    {
        $this->emailVerify = new Client(config('emailverify.api_key'));
    }

    public function register(Request $request)
    {
        $validated = $request->validate([
            'name' => 'required|string|max:255',
            'email' => 'required|email|unique:users',
            'password' => 'required|confirmed|min:8',
        ]);

        // 验证电子邮件
        $verification = $this->emailVerify->verify($validated['email']);

        if ($verification->status === 'invalid') {
            return back()->withErrors([
                'email' => '请提供有效的电子邮件地址。',
            ]);
        }

        if ($verification->result->disposable) {
            return back()->withErrors([
                'email' => '不允许使用一次性电子邮件。',
            ]);
        }

        // 创建用户
        $user = User::create([
            'name' => $validated['name'],
            'email' => $validated['email'],
            'password' => Hash::make($validated['password']),
            'email_verified_score' => $verification->score,
        ]);

        auth()->login($user);

        return redirect('/dashboard');
    }
}

缓存

缓存验证结果以减少 API 调用。

// app/Services/EmailVerificationService.php
<?php

namespace App\Services;

use EmailVerify\Client;
use Illuminate\Support\Facades\Cache;

class EmailVerificationService
{
    protected Client $client;
    protected int $cacheDuration;

    public function __construct()
    {
        $this->client = new Client(config('emailverify.api_key'));
        $this->cacheDuration = config('emailverify.cache_duration', 3600);
    }

    public function verify(string $email): object
    {
        $cacheKey = 'email_verification:' . md5($email);

        return Cache::remember($cacheKey, $this->cacheDuration, function () use ($email) {
            return $this->client->verify($email);
        });
    }

    public function isValid(string $email): bool
    {
        return $this->verify($email)->status === 'valid';
    }

    public function isDisposable(string $email): bool
    {
        return $this->verify($email)->result->disposable ?? false;
    }

    public function clearCache(string $email): void
    {
        Cache::forget('email_verification:' . md5($email));
    }
}

服务提供者注册

// app/Providers/AppServiceProvider.php
public function register(): void
{
    $this->app->singleton(EmailVerificationService::class);
}

使用方法

use App\Services\EmailVerificationService;

class SomeController extends Controller
{
    public function __construct(
        protected EmailVerificationService $emailVerification
    ) {}

    public function store(Request $request)
    {
        if (!$this->emailVerification->isValid($request->email)) {
            return back()->withErrors(['email' => '无效的电子邮件']);
        }

        // 继续处理
    }
}

批量验证

高效地验证多个电子邮件。

// app/Console/Commands/VerifyUserEmails.php
<?php

namespace App\Console\Commands;

use App\Models\User;
use EmailVerify\Client;
use Illuminate\Console\Command;

class VerifyUserEmails extends Command
{
    protected $signature = 'users:verify-emails {--chunk=100}';
    protected $description = '验证所有用户电子邮件地址';

    public function handle()
    {
        $client = new Client(config('emailverify.api_key'));
        $chunk = (int) $this->option('chunk');

        $users = User::whereNull('email_verified_at')
            ->orWhere('email_verification_date', '<', now()->subMonths(3))
            ->get();

        $this->info("正在验证 {$users->count()} 个电子邮件...");

        // 提交批量任务
        $emails = $users->pluck('email')->toArray();
        $job = $client->verifyBulk($emails);

        $this->info("任务 ID:{$job->job_id}");

        // 轮询以获取完成状态
        $bar = $this->output->createProgressBar(100);

        do {
            sleep(5);
            $status = $client->getBulkJobStatus($job->job_id);
            $bar->setProgress($status->progress_percent);
        } while ($status->status !== 'completed');

        $bar->finish();
        $this->newLine();

        // 获取结果并更新用户
        $results = $client->getBulkJobResults($job->job_id);

        foreach ($results->results as $result) {
            User::where('email', $result->email)->update([
                'email_verification_status' => $result->status,
                'email_verification_score' => $result->score,
                'email_verification_date' => now(),
            ]);
        }

        $this->info('验证完成!');
        $this->table(
            ['状态', '数量'],
            collect($results->results)
                ->groupBy('status')
                ->map(fn($items, $status) => [$status, $items->count()])
                ->values()
        );
    }
}

事件监听器

使用事件响应验证结果。

// app/Events/EmailVerified.php
<?php

namespace App\Events;

use Illuminate\Foundation\Events\Dispatchable;

class EmailVerified
{
    use Dispatchable;

    public function __construct(
        public string $email,
        public string $status,
        public float $score,
        public array $result
    ) {}
}

// app/Listeners/HandleEmailVerification.php
<?php

namespace App\Listeners;

use App\Events\EmailVerified;
use Illuminate\Support\Facades\Log;

class HandleEmailVerification
{
    public function handle(EmailVerified $event): void
    {
        if ($event->status === 'invalid') {
            Log::warning('检测到无效的电子邮件', [
                'email' => $event->email,
            ]);
        }

        if ($event->result['disposable'] ?? false) {
            Log::info('阻止的一次性电子邮件', [
                'email' => $event->email,
            ]);
        }
    }
}

测试

模拟服务

// tests/Feature/RegistrationTest.php
<?php

namespace Tests\Feature;

use EmailVerify\Client;
use Mockery;
use Tests\TestCase;

class RegistrationTest extends TestCase
{
    public function test_registration_with_valid_email(): void
    {
        $mock = Mockery::mock(Client::class);
        $mock->shouldReceive('verify')
            ->once()
            ->andReturn((object) [
                'status' => 'valid',
                'score' => 0.95,
                'result' => (object) [
                    'disposable' => false,
                    'role' => false,
                ],
            ]);

        $this->app->instance(Client::class, $mock);

        $response = $this->post('/register', [
            'name' => 'John Doe',
            'email' => 'john@example.com',
            'password' => 'password123',
            'password_confirmation' => 'password123',
        ]);

        $response->assertRedirect('/dashboard');
    }

    public function test_registration_blocks_disposable_email(): void
    {
        $mock = Mockery::mock(Client::class);
        $mock->shouldReceive('verify')
            ->once()
            ->andReturn((object) [
                'status' => 'valid',
                'score' => 0.5,
                'result' => (object) [
                    'disposable' => true,
                    'role' => false,
                ],
            ]);

        $this->app->instance(Client::class, $mock);

        $response = $this->post('/register', [
            'name' => 'John Doe',
            'email' => 'temp@mailinator.com',
            'password' => 'password123',
            'password_confirmation' => 'password123',
        ]);

        $response->assertSessionHasErrors('email');
    }
}

相关资源

On this page