preg_filter
Runs a regex replacement over subjects and returns only the ones that actually matched.
preg_filter(string|array $pattern, string|array $replacement, string|array $subject, int $limit = -1, int &$count = null): string|array|null- Added in PHP 5.3.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$pattern | string|array | One pattern, or an array of patterns, applied in order. |
$replacement | string|array | Replacement string, or an array of replacements paired positionally with the patterns. Backreferences are written as $1 or \1. |
$subject | string|array | The string or array of strings to search and replace in. |
$limitoptional | int= -1 | Maximum replacements per pattern per subject; -1 means no limit. |
$countoptional | int= null | Passed by reference and filled with the total number of replacements performed. |
Return value
string|array|nullAn array when subject is an array, otherwise a string. Only subjects that matched at least one pattern are returned; if nothing matched you get an empty array (array subject) or null (string subject).
Errors
Emits an E_WARNING when a pattern fails to compile; it does not throw. The failure value follows the subject type, exactly as for 'no match': null when subject is a string, an empty array when subject is an array (verified on PHP 8.3: @preg_filter('/[/', 'x', ['a','b']) returns array(0) {}).
preg_filter() is preg_replace() with the non-matching subjects dropped. Given an array, it returns only the elements that at least one pattern touched, already transformed. Given a plain string, it returns the transformed string or null.
When to reach for it
Use it when you want to filter and reshape in a single pass: pull the numeric ID out of every well-formed row and silently discard the junk lines, normalise the phone numbers that look like phone numbers, or turn a mixed list of paths into just the ones under a prefix. Doing the same with preg_grep() followed by preg_replace() means compiling and running the pattern twice.
What trips people up
The returned array keeps the original keys, so the result is usually not a list any more — index 0 may be missing entirely. That matters the moment you json_encode() it, because a gapped array encodes as a JSON object, not an array.
Also remember that "matched" means matched by any of the patterns, not that the value changed. A replacement that produces the same text still counts as a match and is kept.
Example
When to use it
- Pull the numeric order ID out of a mixed log dump and throw away the lines that have none
- Normalise the entries of a CSV column that match a date format and drop the rest
- Turn a list of file paths into just the basenames of the ones with a given extension
Frequently asked questions
What is the difference between preg_filter and preg_replace?
They perform the same replacement, but preg_filter returns only the subjects that matched at least one pattern, while preg_replace returns every subject whether it matched or not. For a string subject, preg_filter returns null on no match where preg_replace returns the original string.
Does preg_filter preserve array keys?
Yes. The surviving elements keep the keys they had in the input array, so the result is usually not a sequential list. Wrap it in array_values() if you need a list.
Can I use preg_filter to just filter without replacing?
You can, by passing '$0' as the replacement so each match is replaced with itself, but preg_grep() is clearer and cheaper for pure filtering.
More examples
preg_filter vs preg_replace
preg_replace returns all three elements ('a#', 'bb', 'c#') because it always returns the subject. preg_filter returns only keys 0 and 2, because 'bb' contained no digit. This is the entire difference between the two functions.
<?php
$in = ['a1', 'bb', 'c3'];
print_r(preg_replace('/\d/', '#', $in));
print_r(preg_filter('/\d/', '#', $in));Counting the replacements
$count receives 3 — one per subject that matched. The returned array has keys 0, 2 and 3, so the count tells you how many rows survived without having to call count() on a re-indexed copy.
<?php
$lines = ['id=7', 'junk', 'id=8', 'id=9'];
$out = preg_filter('/^id=(\d+)$/', 'ID $1', $lines, -1, $count);
print_r($out);
echo "replacements: $count\n";Several patterns at once
Patterns and replacements are paired by position and every pattern is tried against every subject. 'hello' matches neither, so it is dropped; the other two are rewritten and keep keys 0 and 1.
<?php
$values = ['12:30', '2026-08-30', 'hello'];
$out = preg_filter(
['/^(\d{2}):(\d{2})$/', '/^(\d{4})-(\d{2})-(\d{2})$/'],
['time $1h$2', 'date $3\/$2\/$1'],
$values
);
print_r($out);
Discussion