Regular expressions7 min readExamples run on PHP 8.5.10

preg_match()

Learn how preg_match() tests a string against a regular expression, fills capture groups, uses offsets and flags, and signals failure with false.

Signature

preg_match(string $pattern, string $subject, array &$matches = null, int $flags = 0, int $offset = 0): int|false

Purpose

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 example i for case-insensitive matching.
  • $subject: the string to search.
  • $matches: optional, passed by reference. When supplied, it is filled with the results. Index 0 holds the text matched by the whole pattern, index 1 the first capture group, and so on. Named groups appear under their name as well as their number.
  • $flags: defaults to 0. PREG_OFFSET_CAPTURE turns each entry into a pair of the matched text and its byte offset. PREG_UNMATCHED_AS_NULL reports groups that did not participate as null.
  • $offset: defaults to 0. 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.

Example
<?php
$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";
Output
{"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.

Example
<?php
preg_match('/(?<key>\w+)=(?<value>\d+)/', 'limit=25', $m);
echo json_encode($m, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"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.

Example
<?php
$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";
Output
{"caret":0,"g":1,"m2":[["def",3]]}

Edge cases

  • No match: $matches becomes 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 are null. Since PHP 7.4 this includes trailing groups, so the array always has the same size.
  • Byte offsets: both $offset and the offsets from PREG_OFFSET_CAPTURE count bytes, not characters. With multibyte text, the u modifier 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 return 1.
  • Only the first match: a subject with several matches yields only the first one.

Common mistakes

  1. Testing truthiness only. if (preg_match(...)) treats a failure like a non-match. Use === 1, === 0 or === false where the difference matters.
  2. Forgetting delimiters. A pattern like '\d+' without delimiters is invalid and raises a warning.
  3. Reading $matches after a failed match. The array is empty, so indexes such as $matches[1] do not exist.
  4. Using it for plain substring checks. For a literal search, str_contains() or strpos() is simpler and faster.
  5. Mixing up ^ and \G with an offset. Anchors refer to the whole subject, not to the offset position.
  6. Interpolating user input into a pattern. Escape such text with preg_quote() and pass the delimiter as its second argument.
  • 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() and preg_last_error_msg() explain a false result.

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

Result and captured groups
<?php
$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";
Output
{"result":1,"matches":["A-1042","A","1042"]}
Named groups
<?php
preg_match('/(?<key>\w+)=(?<value>\d+)/', 'limit=25', $m);
echo json_encode($m, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"0":"limit=25","key":"limit","1":"limit","value":"25","2":"25"}
Offset with ^ versus \G and PREG_OFFSET_CAPTURE
<?php
$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";
Output
{"caret":0,"g":1,"m2":[["def",3]]}