preg_match

Searches a string for the first match of a regular expression and captures the groups.

Signature
preg_match(string $pattern, string $subject, array &$matches = null, int $flags = 0, int $offset = 0): int|false
  • Added in PHP 4.0

Parameters

ParameterTypeDescription
$patternstringThe pattern to search for, including delimiters and any modifiers.
$subjectstringThe string to search in.
$matchesoptionalarray= nullPassed 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.
$flagsoptionalint= 0A bitmask of PREG_OFFSET_CAPTURE and PREG_UNMATCHED_AS_NULL.
$offsetoptionalint= 0Byte 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

Try it yourself
Loading editor…
Press Run to execute the code.

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.

Example · php
<?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.

Example · php
<?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.

Example · php
<?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

  • Be the first to comment on this lesson.