preg_match($re, $subject, $m) returns 1 or 0 and fills $m with the match at [0], then each group by number and by name. preg_match_all() returns the count, and PREG_SET_ORDER groups results per match. preg_replace() refers to groups as $1 or ${1}, preg_replace_callback() computes each replacement, preg_split() splits on a pattern, and preg_grep() filters an array.
<?php
$log = "10:04:11 ERROR [db] Deadlock\n10:04:15 INFO [web] GET /books/7\n"
. "10:05:02 ERROR [mail] Timeout";
$re = '/^(?<time>\S+) (?<level>[A-Z]+) \[(?<chan>\w+)\] (?<msg>.*)$/m';
preg_match($re, $log, $m);
echo "first: {$m['level']} in {$m['chan']}: {$m['msg']}\n";
preg_match_all($re, $log, $all, PREG_SET_ORDER);
echo implode(' ', array_map(fn($r) => "{$r['chan']}={$r['level']}", $all)), "\n";
echo preg_replace('/(\d{4})-(\d{2})-(\d{2})/', '$3/$2/$1', '2026-09-23'), ' ',
preg_replace('/\s+/', ' ', "a \t\n b"), "\n";
echo preg_replace_callback('/\border (\d+)/', fn(array $m): string =>
'order #' . str_pad($m[1], 6, '0', STR_PAD_LEFT), 'See order 1042 and order 7.'), "\n";
echo json_encode(preg_split('/\s*[,;]\s*/', 'php, mysql ;;apache', -1, PREG_SPLIT_NO_EMPTY)),
' ', json_encode(preg_split('/([+-])/', '3+4-1', -1, PREG_SPLIT_DELIM_CAPTURE)), "\n";
var_dump(preg_match('/^(a+)+$/', str_repeat('a', 30) . 'b'));
echo preg_last_error_msg(), "\n";first: ERROR in db: Deadlock db=ERROR web=INFO mail=ERROR 23/09/2026 a b See order #001042 and order #000007. ["php","mysql","apache"] ["3","+","4","-","1"] bool(false) Backtrack limit exhausted
PREG_SPLIT_DELIM_CAPTURE kept the captured operators. The last test is catastrophic backtracking: (a+)+ tries exponentially many splits, PCRE stopped at pcre.backtrack_limit (1,000,000) and returned false, not 0. Run on user input, such a pattern is a denial-of-service risk, so check === false and avoid nested quantifiers. A plain string function, where one fits, is faster and clearer.