PHPRegular Expressions (PCRE)

Regular Expressions (PCRE)

A regular expression is a compact pattern language for describing the shape of text — "a sequence of digits," "something that looks like an email address," "a word starting with a capital letter" — rather than an exact string to match literally. PHP's regex engine is PCRE (Perl-Compatible Regular Expressions), a mature implementation that follows the same conventions as Perl's regex syntax, which is also close to what JavaScript, Python, and most other mainstream languages use.

Delimiters: patterns need a wrapper

Unlike some languages that treat regex as its own literal syntax, PHP represents a regular expression as a plain string, and that string needs delimiters so PHP knows where the pattern ends and where optional flags begin. The forward slash is the conventional choice, but any non-alphanumeric character works — #, ~, and % are common alternatives, especially useful when the pattern itself contains a lot of literal slashes.

Equivalent patterns with different delimiters

PHP
<?php
$pattern1 = '/hello/';
$pattern2 = '#hello#';
$pattern3 = '~hello~';

// Useful when matching against paths, which are full of slashes
$pathPattern = '#^/api/v1/users/\d+$#';
Flags: modifying how the pattern is applied

After the closing delimiter, a pattern can carry one or more single -letter flags that change matching behavior without changing the pattern text itself.

Flag

Effect

i

Case-insensitive matching

m

^ and $ match at the start/end of each line, not just the whole string

s

. also matches newline characters

u

Treats the pattern and subject as UTF-8, enabling proper multibyte matching

x

Ignores whitespace and # comments inside the pattern, for readability

Using the i flag for case-insensitive matching

PHP
<?php
var_dump(preg_match('/hello/', 'Hello, world'));  // 0 — case matters
var_dump(preg_match('/hello/i', 'Hello, world')); // 1 — case ignored
int(0)
int(1)
The building blocks: anchors, classes, and quantifiers

Most patterns are built from three kinds of ingredients. Anchors pin a match to a position rather than matching characters themselves: ^ means "start of the string" and $ means "end of the string." Character classes, written in square brackets, match any one character from a set — [a-z] matches any lowercase letter, [0-9] matches any digit, and [^0-9] (a caret right after the bracket) means "anything except a digit." PHP also provides shorthand classes: \\d for a digit, \\w for a "word" character (letters, digits, underscore), and \\s for whitespace, each with an uppercase inverse (\\D, \\W, \\S).

Quantifiers control how many times the preceding element may repeat: * means zero or more, + means one or more, ? means zero or one (making the preceding element optional), and {n,m} means "between n and m times."

Anchors, classes, and quantifiers together

PHP
<?php
// Matches strings that are entirely 3 to 5 digits, nothing else
var_dump(preg_match('/^\d{3,5}$/', '4821'));   // 1
var_dump(preg_match('/^\d{3,5}$/', '48'));     // 0 (too short)
var_dump(preg_match('/^\d{3,5}$/', '482199')); // 0 (too long)
int(1)
int(0)
int(0)
Worked example: an email-shape check

Real, fully correct email validation according to the RFC spec is notoriously complex, but a practical "does this look roughly like an email address" pattern is a good illustration of combining classes and quantifiers.

A practical (not RFC-complete) email shape check

PHP
<?php
$pattern = '/^[\w.+-]+@[a-zA-Z\d.-]+\.[a-zA-Z]{2,}$/';

var_dump(preg_match($pattern, 'firoz@example.com'));    // 1
var_dump(preg_match($pattern, 'not-an-email'));         // 0
var_dump(preg_match($pattern, 'missing@domain'));       // 0 (no dot)
int(1)
int(0)
int(0)

[\w.+-]+ matches the local part before the @ (letters, digits, underscore, dot, plus, hyphen), @ matches itself literally, [a-zA-Z\d.-]+ matches the domain, and \.[a-zA-Z]{2,} requires a literal dot followed by at least two letters for the top-level domain.

Worked example: extracting digits

Combining \\d with + is enough to pull consecutive runs of digits out of a larger string, which is useful for stripping formatting characters from things like phone numbers.

Pulling every digit out of formatted text

PHP
<?php
$phone = '(555) 123-4567';
$digitsOnly = preg_replace('/\D+/', '', $phone);

echo $digitsOnly, "\n";
5551234567

\\D+ matches one or more characters that are not digits, and replacing every such run with an empty string leaves only the digits behind — an approach that's covered in more depth, alongside preg_match and its capture groups, on the next page.

  • A PHP regex is a string with matching delimiters on both ends — /pattern/, #pattern#, or any other non-alphanumeric character.

  • Flags follow the closing delimiter: i for case-insensitive, m for multiline anchors, s for dot-matches-newline, u for UTF-8 mode.

  • ^ and $ anchor to the start and end of the string (or line, with the m flag).

  • Character classes [...] match one character from a set; \d, \w, \s are shorthand classes with uppercase inverses.

  • Quantifiers *, +, ?, and {n,m} control how many times the preceding element repeats.

Note
PCRE syntax is the same across `preg_match()`, `preg_replace()`, `preg_split()`, and every other `preg_*` function — once the pattern language is understood, only the surrounding function call changes between use cases.
Tip
For anything beyond a very simple pattern, add the `x` (extended) flag and spread the pattern across multiple lines with comments explaining each part. A dense one-line regex is one of the hardest things to safely modify six months later, and future maintainers (including a future version of the author) will thank you for the annotations.