preg_match
Searches a string for the first match of a regular expression and captures the groups.
preg_match(string $pattern, string $subject, array &$matches = null, int $flags = 0, int $offset = 0): int|false- Added in PHP 4.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$pattern | string | The pattern to search for, including delimiters and any modifiers. |
$subject | string | The string to search in. |
$matchesoptional | array= null | Passed by reference and filled with the match: index 0 is the whole match, index n is the nth capture group, named groups are also available by name. |
$flagsoptional | int= 0 | A bitmask of PREG_OFFSET_CAPTURE and PREG_UNMATCHED_AS_NULL. |
$offsetoptional | int= 0 | Byte offset in the subject at which to start searching. |
Return value
int|false1 if the pattern matched, 0 if it did not, and false if an error occurred.
Errors
Returns false and emits an E_WARNING when the pattern fails to compile. Runtime failures such as exceeding the backtrack limit also return false, silently — check preg_last_error().
Changelog
- PHP 7.4.0With PREG_UNMATCHED_AS_NULL, trailing unmatched groups are now included as null (or [null, -1] with PREG_OFFSET_CAPTURE), so $matches always has the same size. Previously they were omitted.
- PHP 7.2.0PREG_UNMATCHED_AS_NULL is now supported in the flags parameter.
preg_match() stops at the first match and writes it into $matches. Index 0 holds the whole matched text, index 1 onwards hold the capture groups in the order their opening parentheses appear, and named groups (?<name>...) appear under both the name and their number.
When to reach for it
Use it for validation ("does this look like a postcode?") and for pulling a handful of fields out of one line of text — a timestamp and level out of a log entry, a version number out of a user agent, the ID out of a URL. If you need every occurrence, use preg_match_all(); if the pattern is a fixed string with no wildcards, str_contains() is far cheaper.
What trips people up
The return value is 0 for no match and false for an error, so always compare with ===; a loose if (!preg_match(...)) treats a blown backtrack limit as "no match". Optional groups that did not participate come back as empty strings, not null, unless you pass PREG_UNMATCHED_AS_NULL. And $offset is counted in bytes, not characters, even under the u modifier.
Example
When to use it
- Validate that a submitted string is a well-formed invoice number before storing it
- Extract the date, level and message from a single log line
- Pull the numeric ID out of a URL path such as /posts/482/edit
Frequently asked questions
What is the difference between preg_match and preg_match_all?
preg_match stops after the first match and returns 1 or 0; preg_match_all finds every match and returns how many it found. Their $matches arrays are shaped differently — preg_match_all nests one level deeper.
Why is $matches[1] empty when my group clearly matched?
An optional group that did not participate in the match comes back as an empty string, and a trailing one may be missing from the array entirely. Pass PREG_UNMATCHED_AS_NULL to get null for every non-participating group and a consistent array size.
How do I do a case-insensitive regex match in PHP?
Add the i modifier after the closing delimiter, for example '/hello/i'. For multibyte text add u as well, as in '/hello/iu'.
More examples
Named capture groups
print_r shows each named group twice — once under its name and once under its number — so $m has five entries for two groups. Named groups make the code survive someone inserting another set of parentheses earlier in the pattern.
<?php
preg_match('/(?<user>[^@]+)@(?<host>.+)/', '[email protected]', $m);
echo $m['user'], "\n";
echo $m['host'], "\n";
print_r($m);PREG_OFFSET_CAPTURE
Each entry becomes [text, byte offset]. Here the match is '404' at offset 12, which is why the substr() call reproduces it. Offsets are byte positions, so they line up with substr() but not with mb_substr() on multibyte text.
<?php
$s = 'error: code 404 at line 12';
preg_match('/\d+/', $s, $m, PREG_OFFSET_CAPTURE);
print_r($m);
echo substr($s, $m[0][1], strlen($m[0][0])), "\n";Unmatched optional groups
Without the flag, the trailing optional group is simply absent from $a, so $a[2] is undefined. With PREG_UNMATCHED_AS_NULL the array always has the same shape and the missing group is null, which makes destructuring and null-coalescing safe.
<?php
$pattern = '/^(\w+)(?:-(\d+))?$/';
preg_match($pattern, 'widget', $a);
var_dump($a);
preg_match($pattern, 'widget', $b, PREG_UNMATCHED_AS_NULL);
var_dump($b);
Discussion