nalabdou/disposable-email-bundle
Composer 安装命令:
composer require nalabdou/disposable-email-bundle
包简介
Production-grade Symfony bundle to detect and block disposable email addresses. Ships with 100K+ domains, extensible loaders, PSR-6 caching, Twig helpers, and a sync console command.
README 文档
README
The most complete Symfony bundle for detecting and blocking disposable (temporary) email addresses.
Ships with a built-in domain list, extensible loader/whitelist architecture, PSR-6 caching, Twig integration, two console commands, Symfony Events, and a rich value-object API.
✅ Features
| Feature | Description |
|---|---|
| 🔥 Bundled domain list | 500+ domains ready to use out of the box |
| 🌐 Remote sync | Pull 110K+ domains on demand via php bin/console disposable-email:sync |
| 🧩 Extensible loaders | Tag any service with disposable_email.domain_loader |
| 🛡️ Whitelist providers | Tag any service with disposable_email.whitelist_provider |
| 🏷️ PHP 8 Attributes | #[AsDomainLoader] #[AsWhitelistProvider] #[AsDomain] #[AsDomainList] #[AsWhitelistedDomain] |
| 📦 Inline config domains | Add domains directly in YAML without a file |
| 🧠 Symfony Validator | #[NotDisposableEmail] PHP 8 Attribute + YAML support |
| ⚙️ Rich service API | check() returns a full CheckResult value object |
| 🧁 Twig helpers | Function, filter, and test for templates |
| 🔔 Symfony Events | DisposableEmailCheckedEvent + DomainListSyncedEvent |
| 🗃️ PSR-6 caching | Plug in any Symfony cache pool |
| 🐛 Debug command | disposable-email:debug for runtime inspection |
| 📝 PSR-3 logging | All operations are logged at appropriate levels |
| ✅ Symfony 6.4 + 7.x | Tested on PHP 8.2+ |
🚀 Installation
composer require nalabdou/disposable-email-bundle
Register the bundle in config/bundles.php:
return [ // ... Nalabdou\DisposableEmailBundle\DisposableEmailBundle::class => ['all' => true], ];
That's it. Zero configuration required to get started.
⚙️ Configuration
Publish the example config:
cp vendor/nalabdou/disposable-email-bundle/config/packages/disposable_email.yaml config/packages/
Full reference (config/packages/disposable_email.yaml):
disposable_email: # Directory for custom .txt blacklists and remote sync output blacklist_directory: '%kernel.project_dir%/var/disposable' # Domains added directly in config (no file required) extra_domains: - mycompetitor-fake.com # Set false to skip the bundled list entirely use_bundled_list: true # Remote sources for the sync command remote_sources: - url: 'https://remote_source_for_disposable_email_list' timeout: 30 # Domains that are NEVER flagged as disposable whitelist: - mycompany.com - staging.mycompany.com cache: enabled: false # true = highly recommended in production pool: 'cache.app' # any PSR-6 Symfony cache pool ttl: 86400 # 24 h key_prefix: 'disposable_email' # Dispatch events on every check (set false for max throughput) dispatch_events: true
⚙️ Usage
1. Validator Constraint — PHP 8 Attribute (recommended)
use Nalabdou\DisposableEmailBundle\Constraint\NotDisposableEmail; use Symfony\Component\Validator\Constraints as Assert; class RegistrationDto { #[Assert\NotBlank] #[Assert\Email] #[NotDisposableEmail] public string $email = ''; }
With a custom error message:
#[NotDisposableEmail(message: 'Please use a real, permanent email address.')] public string $email = '';
2. Validator Constraint — YAML
# config/validator/App.Entity.User.yaml App\Entity\User: properties: email: - NotBlank: ~ - Email: ~ - Nalabdou\DisposableEmailBundle\Constraint\NotDisposableEmail: ~
3. Runtime Service — Simple API
Inject DisposableEmailChecker anywhere via constructor injection:
use Nalabdou\DisposableEmailBundle\Service\DisposableEmailChecker; class RegistrationHandler { public function __construct( private readonly DisposableEmailChecker $checker, ) {} public function handle(string $email): void { if ($this->checker->isDisposable($email)) { throw new \DomainException('Disposable emails are not allowed.'); } } }
Accepts bare domains too:
$checker->isDisposable('mailinator.com'); // true $checker->isValid('gmail.com'); // true $checker->count(); // number of loaded disposable domains
4. Runtime Service — Rich CheckResult API
$result = $checker->check('user@mailinator.com'); $result->isDisposable(); // bool $result->isValid(); // bool $result->isWhitelisted(); // bool $result->domain; // 'mailinator.com' $result->detectedBy; // 'bundled' — which loader flagged it $result->fromCache; // bool — was result served from PSR-6 cache? echo $result; // [DISPOSABLE] user@mailinator.com (domain: mailinator.com, cache: no, detected_by: bundled)
5. Twig
{# Twig test (most readable) #} {% if user.email is disposable_email %} <p class="text-red-600">⚠ Disposable email detected.</p> {% else %} <p class="text-green-600">✔ Email looks valid.</p> {% endif %} {# Twig filter #} {% if user.email|is_disposable_email %} <span class="badge badge-danger">Disposable</span> {% endif %} {# Full CheckResult object via function #} {% set result = disposable_email_check(user.email) %} {% if result.disposable %} Flagged by loader: {{ result.detectedBy }} {% endif %}
🔄 Console Commands
disposable-email:sync — Update the domain list
# Sync all configured remote_sources php bin/console disposable-email:sync # Show a per-source table php bin/console disposable-email:sync --stats
Schedule automatic sync with cron:
# /etc/cron.d/disposable-email
0 3 * * * www-data /var/www/html/bin/console disposable-email:sync >> /var/log/disposable-email-sync.log 2>&1
Or with the Symfony Scheduler:
use Symfony\Component\Scheduler\Attribute\AsSchedule; use Symfony\Component\Scheduler\RecurringMessage; use Symfony\Component\Scheduler\Schedule; use Symfony\Component\Scheduler\ScheduleProviderInterface; #[AsSchedule] final class AppSchedule implements ScheduleProviderInterface { public function getSchedule(): Schedule { return (new Schedule())->add( RecurringMessage::cron('0 3 * * *', new SyncDisposableEmailsMessage()), ); } }
disposable-email:debug — Inspect runtime state
# Overview (loader count, total domains, etc.) php bin/console disposable-email:debug # Check a specific email or domain php bin/console disposable-email:debug user@mailinator.com php bin/console disposable-email:debug mailinator.com # List all registered loaders and whitelist providers php bin/console disposable-email:debug --loaders # Search for domains containing a substring php bin/console disposable-email:debug --search=mailinator # Show total domain count php bin/console disposable-email:debug --count
📝 Custom Blacklist
Drop any .txt file (one domain per line) into the configured blacklist_directory:
# var/disposable/my_domains.txt
competitor-disposable.com
internal-test.local
No command needed. The file is picked up automatically on the next request (or cache refresh).
🏷️ PHP 8 Attributes
All five bundle attributes are in the Nalabdou\DisposableEmailBundle\Attribute\ namespace. They are the zero-YAML alternative to YAML tags and inline config — pure PHP, fully type-safe, discovered automatically at container compile time.
#[AsDomainLoader] — Register a loader service
use Nalabdou\DisposableEmailBundle\Attribute\AsDomainLoader; use Nalabdou\DisposableEmailBundle\Contract\DomainLoaderInterface; use Doctrine\DBAL\Connection; #[AsDomainLoader(priority: 20)] final class DatabaseDomainLoader implements DomainLoaderInterface { public function __construct(private readonly Connection $db) {} public function load(): iterable { return $this->db->fetchFirstColumn('SELECT domain FROM disposable_domains'); } public function getName(): string { return 'database'; } public function isEnabled(): bool { return true; } }
Override the name surfaced in CheckResult::$detectedBy and debug output:
#[AsDomainLoader(priority: 20, name: 'my_custom_source')] final class DatabaseDomainLoader implements DomainLoaderInterface { ... }
Priority reference:
| Source | Priority |
|---|---|
| Bundled list | -10 |
| Custom blacklist files | 0 |
#[AsDomain] / #[AsDomainList] attributes |
5 |
Inline extra_domains YAML |
10 |
Your #[AsDomainLoader] classes |
20+ (recommended) |
#[AsWhitelistProvider] — Register a whitelist provider service
use Nalabdou\DisposableEmailBundle\Attribute\AsWhitelistProvider; use Nalabdou\DisposableEmailBundle\Contract\WhitelistProviderInterface; #[AsWhitelistProvider] final class CompanyWhitelistProvider implements WhitelistProviderInterface { public function __construct(private readonly Connection $db) {} public function getWhitelistedDomains(): iterable { return $this->db->fetchFirstColumn('SELECT domain FROM trusted_domains'); } }
With an optional description visible in disposable-email:debug --loaders:
#[AsWhitelistProvider(description: 'Company-approved domains from CRM')] final class CompanyWhitelistProvider implements WhitelistProviderInterface { ... }
#[AsDomain] — Mark a constant or enum case as a disposable domain
On class constants:
use Nalabdou\DisposableEmailBundle\Attribute\AsDomain; final class KnownDisposableDomains { #[AsDomain] public const MAILINATOR = 'mailinator.com'; #[AsDomain] public const GUERRILLA = 'guerrillamail.com'; // No attribute — not loaded public const SOME_INTERNAL = 'internal.local'; }
On a string-backed enum (recommended for domain modelling):
use Nalabdou\DisposableEmailBundle\Attribute\AsDomain; enum DisposableDomain: string { #[AsDomain] case Mailinator = 'mailinator.com'; #[AsDomain] case Trashmail = 'trashmail.com'; }
With an explicit domain override:
#[AsDomain(domain: 'actual-domain.com')] public const LEGACY_CONSTANT_NAME = 'old-value.com'; // Loads 'actual-domain.com', not 'old-value.com'
Register your class as a service to have it discovered:
# config/services.yaml (or use autowire: true on the whole App\ namespace) App\Mail\KnownDisposableDomains: public: false
#[AsDomainList] — Declare an inline or static-method domain list
Lighter than #[AsDomainLoader] — no interface required. Perfect for simple static lists that don't need lifecycle control.
Inline domains:
use Nalabdou\DisposableEmailBundle\Attribute\AsDomainList; #[AsDomainList( domains: ['fakeinbox.com', 'tempmail.io', 'throwaway.email'], priority: 15, )] final class ProjectBlockList {}
Via a static method:
#[AsDomainList(method: 'getDomains', priority: 15, name: 'project_blocklist')] final class ProjectBlockList { public static function getDomains(): array { return ['fakeinbox.com', 'tempmail.io', 'throwaway.email']; } }
Multiple lists on one class (IS_REPEATABLE):
#[AsDomainList(domains: ['competitor-a.com', 'competitor-b.com'], priority: 20)] #[AsDomainList(method: 'getRegionalDomains', priority: 15)] final class AllBlockLists { public static function getRegionalDomains(): array { ... } }
The method must be static. The compiler pass validates this and throws a \LogicException at compile time if the method is missing or non-static.
#[AsWhitelistedDomain] — Mark a constant or enum case as a trusted domain
On class constants:
use Nalabdou\DisposableEmailBundle\Attribute\AsWhitelistedDomain; final class TrustedDomains { #[AsWhitelistedDomain] public const COMPANY = 'mycompany.com'; #[AsWhitelistedDomain] public const STAGING = 'staging.mycompany.com'; // No attribute — not whitelisted public const PARTNER_LEGACY = 'old-partner.com'; }
On a string-backed enum:
use Nalabdou\DisposableEmailBundle\Attribute\AsWhitelistedDomain; enum TrustedDomain: string { #[AsWhitelistedDomain] case Company = 'mycompany.com'; #[AsWhitelistedDomain] case Staging = 'staging.mycompany.com'; }
With an explicit domain override:
#[AsWhitelistedDomain(domain: 'real-domain.com')] public const PLACEHOLDER = 'draft-name.com';
Attribute quick-reference
| Attribute | Target | Purpose |
|---|---|---|
#[AsDomainLoader] |
Class | Register a DomainLoaderInterface service |
#[AsWhitelistProvider] |
Class | Register a WhitelistProviderInterface service |
#[AsDomain] |
Class constant / enum case | Mark a single value as a disposable domain |
#[AsDomainList] |
Class | Declare an inline or static-method domain list |
#[AsWhitelistedDomain] |
Class constant / enum case | Mark a single value as a trusted (whitelisted) domain |
🧩 Extending — Custom Domain Loader
Implement DomainLoaderInterface and tag the service:
namespace App\Mail; use Nalabdou\DisposableEmailBundle\Contract\DomainLoaderInterface; use Doctrine\DBAL\Connection; final class DatabaseDomainLoader implements DomainLoaderInterface { public function __construct(private readonly Connection $db) {} public function load(): iterable { return $this->db->fetchFirstColumn('SELECT domain FROM disposable_domains'); } public function getName(): string { return 'database'; } public function isEnabled(): bool { return true; } }
# config/services.yaml App\Mail\DatabaseDomainLoader: tags: - { name: disposable_email.domain_loader, priority: 20 }
Note: when two loaders provide the same domain, the higher-priority one is credited in CheckResult::$detectedBy.
🛡️ Extending — Custom Whitelist Provider
namespace App\Mail; use Nalabdou\DisposableEmailBundle\Contract\WhitelistProviderInterface; final class DatabaseWhitelistProvider implements WhitelistProviderInterface { public function __construct(private readonly Connection $db) {} public function getWhitelistedDomains(): iterable { return $this->db->fetchFirstColumn('SELECT domain FROM trusted_domains'); } }
App\Mail\DatabaseWhitelistProvider: tags: - { name: disposable_email.whitelist_provider }
🔔 Events
DisposableEmailCheckedEvent (on every check)
use Nalabdou\DisposableEmailBundle\Event\DisposableEmailCheckedEvent; use Symfony\Component\EventDispatcher\Attribute\AsEventListener; #[AsEventListener(event: DisposableEmailCheckedEvent::NAME)] final class DisposableEmailListener { public function __invoke(DisposableEmailCheckedEvent $event): void { $result = $event->getResult(); if ($result->isDisposable()) { $this->logger->warning('Disposable email attempt', [ 'email' => $result->input, 'domain' => $result->domain, 'detected_by' => $result->detectedBy, ]); } } }
DomainListSyncedEvent (after sync command)
use Nalabdou\DisposableEmailBundle\Event\DomainListSyncedEvent; use Symfony\Component\EventDispatcher\Attribute\AsEventListener; #[AsEventListener(event: DomainListSyncedEvent::NAME)] final class SyncCompletedListener { public function __invoke(DomainListSyncedEvent $event): void { if ($event->hasFailures()) { $this->notifyOpsChannel('Disposable email sync had failures!'); } $this->metrics->gauge('disposable_email.total_domains', $event->getTotalDomains()); } }
🧠 Caching (Production)
Strongly recommended in production to avoid rebuilding the 100K+ domain set on every request:
# config/packages/disposable_email.yaml disposable_email: cache: enabled: true pool: 'cache.app' # or 'cache.redis', any PSR-6 pool ttl: 86400
After syncing a new domain list, the cache is invalidated automatically. To clear it manually:
php bin/console cache:pool:clear cache.app
📁 Directory Structure
DisposableEmailBundle/
├── composer.json
├── phpunit.xml.dist
├── README.md
│
├── config/
│ ├── services.php
│ └── packages/
│ └── disposable_email.yaml
│
├── resources/
│ └── domains/
│ └── disposable_domains.txt
│
└── src/
├── DisposableEmailBundle.php
│
├── Attribute/
│ ├── AsDomain.php
│ ├── AsDomainList.php
│ ├── AsDomainLoader.php
│ ├── AsWhitelistedDomain.php
│ └── AsWhitelistProvider.php
│
├── Command/
│ ├── SyncDisposableEmailListCommand.php
│ └── DebugDisposableEmailCommand.php
│
├── Constraint/
│ ├── NotDisposableEmail.php
│ └── NotDisposableEmailValidator.php
│
├── Contract/
│ ├── CheckResult.php
│ ├── DomainLoaderInterface.php
│ ├── SyncResult.php
│ └── WhitelistProviderInterface.php
│
├── DependencyInjection/
│ ├── Compiler/
│ │ ├── DomainLoaderPass.php
│ │ ├── RegisterAttributesPass.php
│ │ └── WhitelistProviderPass.php
│ ├── Configuration.php
│ └── DisposableEmailExtension.php
│
├── Event/
│ ├── DisposableEmailCheckedEvent.php
│ └── DomainListSyncedEvent.php
│
├── Exception/
│ ├── DisposableEmailException.php
│ ├── DomainLoaderException.php
│ └── SyncException.php
│
├── Loader/
│ ├── AbstractFileLoader.php
│ ├── AttributeDomainLoader.php
│ ├── BundledDomainLoader.php
│ ├── ChainDomainLoader.php
│ ├── CustomBlacklistLoader.php
│ └── InlineDomainsLoader.php
│
├── Provider/
│ ├── AttributeWhitelistProvider.php
│ ├── ChainWhitelistProvider.php
│ └── ConfigWhitelistProvider.php
│
├── Service/
│ ├── DisposableEmailChecker.php
│ └── DomainListSyncer.php
│
└── Twig/
└── DisposableEmailExtension.php
tests/
├── Attribute/
│ └── AttributesTest.php
├── Constraint/
│ └── NotDisposableEmailValidatorTest.php
├── DependencyInjection/
│ └── RegisterAttributesPassTest.php
└── Service/
├── ChainDomainLoaderTest.php
├── DisposableEmailCheckerTest.php
├── LoadersTest.php
├── ValueObjectsTest.php
└── WhitelistProviderTest.php
Running Tests
composer install vendor/bin/phpunit
License
This project is licensed under the MIT License — see the LICENSE file for details.
nalabdou/disposable-email-bundle 适用场景与选型建议
nalabdou/disposable-email-bundle 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 1 次下载、GitHub Stars 达 0, 最近一次更新时间为 2026 年 04 月 06 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「symfony」 「email」 「bundle」 「validation」 「blacklist」 「disposable」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 nalabdou/disposable-email-bundle 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 nalabdou/disposable-email-bundle 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 nalabdou/disposable-email-bundle 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
2lenet/EasyAdminPlusBundle
The bundle for easy using json-rpc api on your project
Provide a way to secure accesses to all routes of an symfony application.
DMS Meetup API Bundle, enables Meetup API clients in services
Bundle Symfony DaplosBundle
Extensible library for building notifications and sending them via different delivery channels.
统计信息
- 总下载量: 1
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 1
- 点击次数: 45
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-04-06