Signature
preg_match(string $pattern, string $subject, array &$matches = null, int $flags = 0, int $offset = 0): int|falsePurpose
preg_match() searches a string for the first match of a Perl-compatible regular expression. It answers a yes/no question through its return value, and it can also hand back the matched text and the text captured by parenthesized groups. Use it when you need to validate a format or pull a few pieces out of a single string. If you need every match, look at preg_match_all() instead.
Signature
Parameters and defaults
$pattern: the regular expression as a string. It needs delimiters (such as/.../) and may carry modifiers after the closing delimiter, for exampleifor case-insensitive matching.$subject: the string to search.$matches: optional, passed by reference. When supplied, it is filled with the results. Index0holds the text matched by the whole pattern, index1the first capture group, and so on. Named groups appear under their name as well as their number.$flags: defaults to0.PREG_OFFSET_CAPTUREturns each entry into a pair of the matched text and its byte offset.PREG_UNMATCHED_AS_NULLreports groups that did not participate asnull.$offset: defaults to0. It is the byte position where the search starts.
Return value and failure
The function returns 1 if the pattern matched, 0 if it did not, and false on failure. Failure happens, for example, when the backtracking or recursion limit is hit on a long subject. Call preg_last_error() or preg_last_error_msg() to find out why. If the pattern itself does not compile, PHP emits an E_WARNING. Because 0 and false are both falsy, test with === false whenever you need to tell a failed run from a simple non-match.
Example 1: result and captured groups
Here two groups capture a letter and a number. The result is 1, and $matches contains the full match followed by each group.
$subject = 'Order A-1042 shipped';
$result = preg_match('/([A-Z])-(\d+)/', $subject, $matches);
echo json_encode(['result' => $result, 'matches' => $matches], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";{"result":1,"matches":["A-1042","A","1042"]}
Example 2: named groups
Named groups make the code easier to read. Each named group is stored under its name, immediately followed by its numeric entry.
preg_match('/(?<key>\w+)=(?<value>\d+)/', 'limit=25', $m);
echo json_encode($m, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";{"0":"limit=25","key":"limit","1":"limit","value":"25","2":"25"}
Example 3: offset, anchors and offset capture
The $offset parameter is not the same as cutting the string with substr(). The ^ anchor still means the start of the whole subject, so it cannot match at offset 3. The \G assertion matches exactly where the search begins, so it works with an offset. With PREG_OFFSET_CAPTURE, each entry is a [text, byteOffset] pair.
$subject = 'abcdef';
$a = preg_match('/^def/', $subject, $m1, 0, 3);
$b = preg_match('/\Gdef/', $subject, $m2, PREG_OFFSET_CAPTURE, 3);
echo json_encode(['caret' => $a, 'g' => $b, 'm2' => $m2], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";{"caret":0,"g":1,"m2":[["def",3]]}
Edge cases
- No match:
$matchesbecomes an empty array, so check the return value before reading indexes. - Trailing unmatched groups: without
PREG_UNMATCHED_AS_NULL, a group that did not match but is followed by a matched group appears as an empty string, while trailing unmatched groups are left out entirely. With the flag, every group is present and unmatched ones arenull. Since PHP 7.4 this includes trailing groups, so the array always has the same size. - Byte offsets: both
$offsetand the offsets fromPREG_OFFSET_CAPTUREcount bytes, not characters. With multibyte text, theumodifier makes the pattern treat the subject as UTF-8, but offsets are still in bytes. - Empty matches: a pattern such as
/x*/can match an empty string and still return1. - Only the first match: a subject with several matches yields only the first one.
Common mistakes
- Testing truthiness only.
if (preg_match(...))treats a failure like a non-match. Use=== 1,=== 0or=== falsewhere the difference matters. - Forgetting delimiters. A pattern like
'\d+'without delimiters is invalid and raises a warning. - Reading
$matchesafter a failed match. The array is empty, so indexes such as$matches[1]do not exist. - Using it for plain substring checks. For a literal search,
str_contains()orstrpos()is simpler and faster. - Mixing up
^and\Gwith an offset. Anchors refer to the whole subject, not to the offset position. - Interpolating user input into a pattern. Escape such text with
preg_quote()and pass the delimiter as its second argument.
Related functions
preg_match_all()finds every match.preg_replace()replaces matches.preg_split()splits a string at matches.preg_quote()escapes literal text for use inside a pattern.preg_last_error()andpreg_last_error_msg()explain afalseresult.
Recap
preg_match() finds the first match of a pattern, returns 1, 0 or false, and optionally fills $matches with the full match and its capture groups. Use named groups for readability, remember that offsets are in bytes, prefer \G over ^ when searching from an offset, and compare against false strictly to detect real failures.
Tested examples
$subject = 'Order A-1042 shipped';
$result = preg_match('/([A-Z])-(\d+)/', $subject, $matches);
echo json_encode(['result' => $result, 'matches' => $matches], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
{"result":1,"matches":["A-1042","A","1042"]}
preg_match('/(?<key>\w+)=(?<value>\d+)/', 'limit=25', $m);
echo json_encode($m, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
{"0":"limit=25","key":"limit","1":"limit","value":"25","2":"25"}
$subject = 'abcdef';
$a = preg_match('/^def/', $subject, $m1, 0, 3);
$b = preg_match('/\Gdef/', $subject, $m2, PREG_OFFSET_CAPTURE, 3);
echo json_encode(['caret' => $a, 'g' => $b, 'm2' => $m2], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
{"caret":0,"g":1,"m2":[["def",3]]}