Regex

composer require php-standard-library/regex

The Regex component provides type-safe regular expression functions. PHP's preg_* functions return false on invalid patterns and use null for unmatched groups, making it easy to silently propagate errors. PSL's Regex functions throw InvalidPatternException for bad patterns and support typed capture groups so you know exactly what shape your matches will have.

Usage

Checking for Matches

use Psl\Regex;

Regex\matches('[email protected]', '/^[^@]+@[^@]+$/'); // true
Regex\matches('not-an-email', '/^[^@]+@[^@]+$/'); // false
Regex\matches('foo bar foo', '/foo/', 4); // true (from offset)

Extracting Matches

first_match() returns the first match as an array, or null if nothing matched:

use Psl\Regex;

$match = Regex\first_match('Order #12345', '/Order #(\d+)/');

// ['Order #12345', '12345']

Typed Capture Groups

Pass a Type shape to get a well-typed result that validates the match structure at runtime:

use Psl\Regex;
use Psl\Type;

$pattern = '/(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})/';

$match = Regex\first_match('Date: 2025-03-15', $pattern, Type\shape([
    0 => Type\string(),
    'year' => Type\string(),
    'month' => Type\string(),
    'day' => Type\string(),
]));

// $match['year'] === '2025', $match['month'] === '03', $match['day'] === '15'

The capture_groups() helper builds a shape type from a list of expected group keys:

use Psl\Regex;

$match = Regex\first_match('Hello World', '/(Hello) (World)/', Regex\capture_groups([1, 2]));

// $match[0] === 'Hello World', $match[1] === 'Hello', $match[2] === 'World'

Finding All Matches

every_match() returns all matches as a list, or null if none matched:

use Psl\Regex;

$matches = Regex\every_match('prices: $10, $20, $30', '/\$(\d+)/');

// [['$10', '10'], ['$20', '20'], ['$30', '30']]

Search and Replace

use Psl\Regex;
use Psl\Str;

Regex\replace('hello world', '/world/', 'PHP');
// 'hello PHP'

// Dynamic replacement with a callback
Regex\replace_with('hello world', '/\b(\w)/', fn($m) => Str\uppercase($m[1]));
// 'Hello World'

// Replace multiple patterns at once
Regex\replace_every('foo bar baz', [
    '/foo/' => 'one',
    '/bar/' => 'two',
    '/baz/' => 'three',
]);

// 'one two three'

Splitting

use Psl\Regex;

Regex\split('one, two,  three', '/,\s*/');
// ['one', 'two', 'three']

Regex\split('a-b-c-d', '/-/', 2);

// ['a', 'b-c-d']

Error Handling

All functions throw instead of returning error sentinels. An invalid pattern like /unclosed(group throws Regex\Exception\InvalidPatternException immediately rather than returning false.

See src/Psl/Regex/ for the full API.