Regular expressions7 min readExamples run on PHP 8.5.10

preg_last_error()

Learn how preg_last_error() reports the outcome of the last PCRE call in PHP 8.4, how to tell failures from non-matches, and which mistakes to avoid.

Signature

preg_last_error(): int
Available since PHP 5.2.07.48.08.18.28.38.48.5All versions →

Purpose

preg_last_error() tells you whether the most recent PCRE function call finished cleanly or hit a runtime problem. The preg_* functions usually signal trouble by returning false or null, and those values do not say what went wrong. This function supplies the reason as an integer code that you compare with the PREG_*_ERROR constants.

It is most useful when you process text you do not control, such as user input that may contain invalid UTF-8, or patterns that may backtrack heavily.

Signature

Parameters and defaults

The function takes no parameters, so there is nothing to configure. It reports on the last PCRE execution only. Call it straight after the preg_* call you care about, before any other regex function runs.

Return value and failure cases

The function always returns an int. It does not throw and does not emit warnings. The possible values are these constants:

  • PREG_NO_ERROR: the last call finished without a PCRE error.
  • PREG_INTERNAL_ERROR: an internal PCRE error occurred.
  • PREG_BACKTRACK_LIMIT_ERROR: the backtracking limit was exhausted (see pcre.backtrack_limit).
  • PREG_RECURSION_LIMIT_ERROR: the recursion limit was exhausted (see pcre.recursion_limit).
  • PREG_BAD_UTF8_ERROR: the subject is not valid UTF-8 while the u modifier is in use.
  • PREG_BAD_UTF8_OFFSET_ERROR: the supplied offset does not fall at the start of a UTF-8 character.
  • PREG_JIT_STACKLIMIT_ERROR: the JIT stack ran out of space.

Compare against the named constants, not literal numbers. For a readable message, preg_last_error_msg() returns text for the same state.

Example 1: a clean call reports no error

After a normal match, the code is PREG_NO_ERROR, which is 0.

Example
<?php
preg_match('/a/', 'abc');
echo json_encode([preg_last_error(), preg_last_error_msg()], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
[0,"No error"]

Example 2: invalid UTF-8 in the subject

With the u modifier, PCRE validates the subject. The byte \xff can never appear in valid UTF-8, so preg_match() returns false and the error code explains why.

Example
<?php
$result = preg_match('/./u', "\xff");
echo json_encode([
    $result,
    preg_last_error() === PREG_BAD_UTF8_ERROR,
    preg_last_error_msg(),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
[false,true,"Malformed UTF-8 characters, possibly incorrectly encoded"]

Example 3: a wrapper that separates errors from non-matches

A plain "no match" is not an error. The helper below throws only when the call failed, and otherwise returns a boolean.

Example
<?php
function matchOrFail(string $pattern, string $subject): bool
{
    $result = preg_match($pattern, $subject);
    if ($result === false || preg_last_error() !== PREG_NO_ERROR) {
        throw new RuntimeException(preg_last_error_msg());
    }
    return $result === 1;
}

$results = [];
foreach (['abc7', 'abc', "\xff"] as $subject) {
    try {
        $results[] = matchOrFail('/\d/u', $subject);
    } catch (RuntimeException $e) {
        $results[] = $e->getMessage();
    }
}
echo json_encode($results, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
[true,false,"Malformed UTF-8 characters, possibly incorrectly encoded"]

Edge cases

  • A subject with no match gives 0 from preg_match() while preg_last_error() stays at PREG_NO_ERROR. Only a false result (or null from some functions such as preg_replace()) points at a runtime error.
  • The value describes the last regex call only. If you run another preg_* function in between, even for logging or cleanup, you lose the information from the earlier call.
  • Limit-related errors depend on configuration such as pcre.backtrack_limit, pcre.recursion_limit and whether JIT is active. The same pattern may therefore behave differently on different setups, so do not build logic that assumes a specific limit error for a given input.

Common mistakes

  1. Treating 0 from preg_match() as a failure. It only means the pattern did not match. Check for false first, then consult preg_last_error().
  2. Using loose truthiness. if (!preg_match(...)) merges a non-match with an error. Compare with false using ===.
  3. Checking too late. Any intervening preg_* call replaces the stored state, so read it right away.
  4. Hard-coding numbers. Use PREG_BAD_UTF8_ERROR and the other constants, which are clearer and safer.
  5. Expecting an exception. PCRE runtime errors do not throw by themselves. Add your own check, like the wrapper above, if you want exceptions.
  • preg_last_error_msg() returns the matching human-readable message.
  • preg_match(), preg_match_all(), preg_replace(), preg_replace_callback() and preg_split() are the calls whose outcome it reports.

Recap

preg_last_error() takes no arguments and returns an int code describing the last PCRE call. Pair it with a strict === false check on the regex function's result, compare against the PREG_* constants, and call it immediately after the operation. Use preg_last_error_msg() when you need text for logs or exceptions.

Tested examples

A clean call reports no error
<?php
preg_match('/a/', 'abc');
echo json_encode([preg_last_error(), preg_last_error_msg()], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
[0,"No error"]
Invalid UTF-8 in the subject
<?php
$result = preg_match('/./u', "\xff");
echo json_encode([
    $result,
    preg_last_error() === PREG_BAD_UTF8_ERROR,
    preg_last_error_msg(),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
[false,true,"Malformed UTF-8 characters, possibly incorrectly encoded"]
A wrapper that separates errors from non-matches
<?php
function matchOrFail(string $pattern, string $subject): bool
{
    $result = preg_match($pattern, $subject);
    if ($result === false || preg_last_error() !== PREG_NO_ERROR) {
        throw new RuntimeException(preg_last_error_msg());
    }
    return $result === 1;
}

$results = [];
foreach (['abc7', 'abc', "\xff"] as $subject) {
    try {
        $results[] = matchOrFail('/\d/u', $subject);
    } catch (RuntimeException $e) {
        $results[] = $e->getMessage();
    }
}
echo json_encode($results, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
[true,false,"Malformed UTF-8 characters, possibly incorrectly encoded"]