Context Matching
Built-in Matchers
Section titled “Built-in Matchers”Suspend includes these matchers out of the box:
| Matcher | Type | Description |
|---|---|---|
EmailMatcher | email | Email addresses with wildcard support |
IpMatcher | ip | IPv4/IPv6 addresses with CIDR support |
PhoneMatcher | phone | Phone numbers (normalized) |
DomainMatcher | domain | Domain names with subdomain matching |
CountryMatcher | country | ISO country codes |
FingerprintMatcher | fingerprint | Device/browser fingerprints |
RegexMatcher | regex | Regular expression patterns |
GlobMatcher | glob | Shell-style glob patterns |
ExactMatcher | exact | Exact string matching |
Creating Context Suspensions
Section titled “Creating Context Suspensions”Email Matching
Section titled “Email Matching”use Cline\Suspend\Facades\Suspend;
// Block specific emailSuspend::match('email', 'spammer@example.com')->suspend('Known spammer');
// Block entire domain (wildcard)Suspend::match('email', '*@spam-domain.com')->suspend('Spam domain');
// Block patternSuspend::match('email', 'bot*@*')->suspend('Bot pattern');IP Address Matching
Section titled “IP Address Matching”// Block specific IPSuspend::match('ip', '1.2.3.4')->suspend('Malicious IP');
// Block CIDR rangeSuspend::match('ip', '192.168.1.0/24')->suspend('Internal network');
// Block IPv6Suspend::match('ip', '2001:db8::/32')->suspend('IPv6 range');Phone Number Matching
Section titled “Phone Number Matching”// Block specific number (formatting is normalized)Suspend::match('phone', '+1-555-123-4567')->suspend();
// Numbers are normalized, so these are equivalent:// +15551234567, (555) 123-4567, 555.123.4567Domain Matching
Section titled “Domain Matching”// Block domain and all subdomainsSuspend::match('domain', 'malware.com')->suspend('Malware source');
// Matches: malware.com, www.malware.com, sub.malware.comCountry Matching
Section titled “Country Matching”// Block by ISO country codeSuspend::match('country', 'XX')->suspend('Restricted country');Glob Patterns
Section titled “Glob Patterns”Simple wildcard matching without regex complexity:
// * matches any charactersSuspend::match('glob', 'spam@*')->suspend();
// ? matches single characterSuspend::match('glob', 'user?@example.com')->suspend();
// Character classesSuspend::match('glob', 'user[123]@*')->suspend();
// Negated classesSuspend::match('glob', 'test[!0-9]@*')->suspend();Regular Expressions
Section titled “Regular Expressions”For complex patterns:
// Regex pattern (include delimiters)Suspend::match('regex', '/^bot\d+@/i')->suspend('Bot pattern');
// Match disposable email providersSuspend::match('regex', '/@(tempmail|throwaway)\./i') ->suspend('Disposable email');Checking Context Matches
Section titled “Checking Context Matches”Single Context Check
Section titled “Single Context Check”$suspended = Suspend::check() ->email('user@example.com') ->matches();Multiple Context Check
Section titled “Multiple Context Check”Check multiple contexts at once - returns true if ANY match:
$suspended = Suspend::check() ->email($request->input('email')) ->ip($request->ip()) ->phone($request->input('phone')) ->domain($request->getHost()) ->fingerprint($request->header('X-Device-Fingerprint')) ->matches();Get Matching Suspensions
Section titled “Get Matching Suspensions”$suspensions = Suspend::check() ->email($email) ->ip($ip) ->getSuspensions();
// Returns Collection of matching Suspension modelsforeach ($suspensions as $suspension) { echo "Blocked by: {$suspension->reason}";}Check with Request
Section titled “Check with Request”Automatically extract context from the current request:
$suspended = Suspend::check() ->fromRequest($request) ->matches();Pattern Validation
Section titled “Pattern Validation”Matchers validate patterns before creating suspensions:
// This will fail - invalid email patternSuspend::match('email', 'not-an-email')->suspend();
// This will fail - invalid CIDRSuspend::match('ip', '192.168.1.0/33')->suspend();
// This will fail - invalid regexSuspend::match('regex', '/unclosed[/')->suspend();Custom Matchers
Section titled “Custom Matchers”Create your own matcher for custom context types:
use Cline\Suspend\Matchers\Contracts\Matcher;
class UsernamePatternMatcher implements Matcher{ public function type(): string { return 'username'; }
public function normalize(mixed $value): string { return mb_strtolower(trim((string) $value)); }
public function matches(string $pattern, mixed $value): bool { return fnmatch($pattern, $this->normalize($value)); }
public function validate(mixed $value): bool { $normalized = $this->normalize($value); return $normalized !== '' && strlen($normalized) <= 255; }
public function extract(mixed $value): ?string { return null; // No extraction for usernames }}Register your custom matcher:
// In a service provideruse Cline\Suspend\Facades\Suspend;
public function boot(){ Suspend::registerMatcher(new UsernamePatternMatcher());}Use it:
Suspend::match('username', 'bot_*')->suspend('Bot username pattern');
Suspend::check()->add('username', $username)->matches();Real-World Examples
Section titled “Real-World Examples”Block Disposable Email Providers
Section titled “Block Disposable Email Providers”$disposableProviders = [ '*@tempmail.com', '*@throwaway.io', '*@10minutemail.com', '*@guerrillamail.com',];
foreach ($disposableProviders as $pattern) { Suspend::match('email', $pattern)->suspend('Disposable email provider');}Block Known Bad IP Ranges
Section titled “Block Known Bad IP Ranges”$badRanges = [ '192.0.2.0/24', // TEST-NET-1 '198.51.100.0/24', // TEST-NET-2 '203.0.113.0/24', // TEST-NET-3];
foreach ($badRanges as $range) { Suspend::match('ip', $range)->suspend('Reserved/test IP range');}Geographic Restrictions
Section titled “Geographic Restrictions”$restrictedCountries = ['XX', 'YY', 'ZZ'];
foreach ($restrictedCountries as $country) { Suspend::match('country', $country) ->suspend('Service not available in this region');}Fraud Prevention Patterns
Section titled “Fraud Prevention Patterns”// Block suspicious email patternsSuspend::match('regex', '/^[a-z]{20,}@/i') ->suspend('Suspicious random email');
// Block VoIP phone prefixesSuspend::match('phone', '+1800*')->suspend('Toll-free number');Suspend::match('phone', '+1888*')->suspend('Toll-free number');