composer/pcre
Type-safe wrapper around PHP’s preg_* functions. Composer\Pcre\Preg prevents silent PCRE failures, standardizes return types (PREG_UNMATCHED_AS_NULL), and improves static analysis with a PHPStan extension for regex-aware typing.
Installation:
composer require composer/pcre
Ensure your PHP version meets requirements (7.4+ for v3.x).
First Use Case:
Replace preg_match with Preg::match to enforce type safety:
use Composer\Pcre\Preg;
try {
if (Preg::match('/\d+/', 'abc123', $matches)) {
$number = $matches[0]; // Guaranteed to be a string, no null checks needed
}
} catch (PcreException $e) {
// Handle failure explicitly
}
Key Files to Explore:
src/Preg.php: Core wrapper class with all preg_* replacements.src/Regex.php: Alternative API with result objects for stricter type safety.extension.neon: PHPStan extension for regex validation (include in phpstan.neon).Type-Safe Matching:
Use Preg::match or Regex::match for predictable returns (throws PcreException on failure):
$result = Regex::match('/(foo)(bar)/', 'foobar');
if ($result->matched) {
$foo = $result->matches[1]; // Non-nullable
$bar = $result->matches[2]; // Non-nullable
}
Strict Groups Handling:
Enforce non-nullable groups with *StrictGroups methods:
try {
Preg::matchStrictGroups('/(foo)(bar)?/', 'foo', $matches);
// $matches[2] is guaranteed to be null (not missing) if the group didn’t match
} catch (PcreException $e) {
// Throws if any group is missing (e.g., due to optional patterns)
}
Replacement Patterns:
Use Preg::replaceCallback for dynamic replacements with type safety:
$replaced = Preg::replaceCallback(
'/(\d+)/',
fn($match) => str_pad($match[1], 5, '0', STR_PAD_LEFT),
'123'
); // Returns "00123"
PHPStan Integration:
Add to phpstan.neon:
includes:
- vendor/composer/pcre/extension.neon
Now PHPStan validates regex syntax and infers $matches shapes:
// PHPStan will flag invalid regex or infer $matches[1] as string
Preg::match('/invalid[regex/', 'test', $matches);
Validation Rules: Replace custom regex validation with type-safe alternatives:
use Composer\Pcre\Preg;
$validator->rule(function ($attribute, $value, $fail) {
try {
if (!Preg::isMatch('/^[A-Z0-9]+$/', $value)) {
$fail('Invalid format.');
}
} catch (PcreException) {
$fail('Regex error.');
}
});
Request Parsing: Extract query params or headers with strict groups:
$path = request()->path();
$result = Regex::matchStrictGroups('/^\/api\/v(\d+)\/(.+)$/', $path);
if ($result->matched) {
$version = $result->matches[1]; // Non-nullable
$resource = $result->matches[2]; // Non-nullable
}
Blade Directives: Create custom Blade directives for regex-based templating:
Blade::directive('regexMatch', function ($expression) {
return "<?php
try {
if (Composer\Pcre\Preg::isMatch({$expression}, \$__env->data['content'])) {
echo 'Matched!';
}
} catch (Composer\Pcre\PcreException) {
echo 'No match.';
}
?>";
});
Usage in Blade:
@regexMatch('/success/')
Artisan Commands:
Validate CLI arguments with Regex:
protected function handle() {
$input = $this->argument('input');
$result = Regex::match('/^(start|stop|restart)$/', $input);
if (!$result->matched) {
$this->error('Invalid command.');
return 1;
}
// Proceed...
}
Optional Groups in *StrictGroups:
Methods like matchStrictGroups throw PcreException if optional groups (e.g., (foo)?) are missing.
Fix: Use non-optional patterns or handle exceptions:
try {
Preg::matchStrictGroups('/(foo)(bar)?/', 'foo', $matches);
} catch (PcreException) {
// Handle case where 'bar' is optional and missing
}
PREG_OFFSET_CAPTURE Limitations:
matchWithOffsets instead of passing flags to match.splitWithOffsets is required for offset splits (no PREG_SPLIT_OFFSET_CAPTURE support).PHPStan Extension Quirks:
composer.json for compatibility).phpstan.neon:
excludeFiles:
- 'path/to/complex-regexes.php'
Callback Type Safety:
replaceCallback callbacks must return strings. PHPStan will flag violations:
// PHPStan error: Callback must return string
Preg::replaceCallback('/\d+/', fn($m) => $m[0] + 1, '123');
Deprecations:
Enable Strict Mode:
Use Regex class for explicit result objects to debug matches:
$result = Regex::match('/(foo)(bar)/', 'foobar');
dump($result->matched, $result->matches); // Inspect raw output
Regex Validation: Test regexes in isolation before integrating:
$testCases = ['foobar', 'foo', 'bar'];
foreach ($testCases as $case) {
$result = Regex::match('/(foo)(bar)/', $case);
dump($case, $result->matches);
}
Exception Handling:
Catch PcreException for all preg_* operations to avoid silent failures:
try {
$filtered = Preg::grep('/^[A-Z]/', ['apple', 'Banana']);
} catch (PcreException $e) {
Log::error('Regex filter failed:', ['error' => $e->getMessage()]);
$filtered = [];
}
Custom Result Objects:
Extend MatchResult or ReplaceResult for project-specific needs:
class CustomMatchResult extends MatchResult {
public function getGroupNames(): array {
return array_keys($this->matches);
}
}
PHPStan Extensions:
Add custom regex rules to extension.neon:
parameters:
regexes:
email: '^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$'
Laravel Service Provider:
Bind Preg/Regex globally for convenience:
public function register() {
$this->app->singleton('regex', function () {
return new Composer\Pcre\Regex();
});
}
Usage:
$this->regex->match('/pattern/', $subject);
Testing:
Mock Preg/Regex in tests for isolated regex logic:
$mock = Mockery::mock('alias:Composer\Pcre\Preg');
$mock->shouldReceive('match')
->with('/pattern/', 'input')
->andReturn(true)
->once();
How can I help you explore Laravel packages today?