RegEx Patterns

Ma Mariya Vinciya Updated 21 Oct 2026
8 min read ·Lesson 157 of 174
In this post

A regular expression pattern is built from small pieces: literal characters, character classes, quantifiers, anchors, groups, and assertions. This lesson explains each building block with a short exam

A regular expression pattern is built from small pieces: literal characters, character classes, quantifiers, anchors, groups, and assertions. This lesson explains each building block with a short example, so you can both write patterns and read patterns written by others.


Literal Characters and Metacharacters

Most characters match themselves: the pattern /cat/ finds the text cat. A few characters have special meaning — these are the metacharacters:

.  ^  $  *  +  ?  {  }  [  ]  \  |  (  )

 

To match a metacharacter literally, escape it with a backslash:

<?php
var_dump(preg_match('/1.5/', '1x5'));    // . matches any character
var_dump(preg_match('/1\.5/', '1x5'));   // \. matches only a dot
var_dump(preg_match('/1\.5/', '1.5'));

 

Output

int(1)
int(0)
int(1)

 

The unescaped dot matched x, which is a common source of overly permissive patterns — for example, /example.com/ also matches exampleXcom.


Character Classes [...]

A character class matches one character from a set:

PatternMatches
[aeiou]One vowel
[a-z]One lowercase ASCII letter
[A-Za-z0-9]One letter or digit
[^0-9]One character that is not a digit (^ at the start negates)
[-.] or [.0]A hyphen or a dot

 

Inside a class, most metacharacters lose their special meaning: [.] matches a literal dot. Only ], 0, ^(at the start), and-` (between characters) are special.

Example: Vehicle Registration Numbers

<?php
$pattern = '/^[A-Z]{2}\d{2}[A-Z]{1,2}\d{4}$/';

foreach (['TN09AB1234', 'KA05M4321', 'TN9AB1234', 'tn09ab1234'] as $plate) {
    echo $plate . ': ' . (preg_match($pattern, $plate) === 1 ? 'valid' : 'invalid') . "\n";
}

 

Output

TN09AB1234: valid
KA05M4321: valid
TN9AB1234: invalid
tn09ab1234: invalid

 

The pattern requires two uppercase letters (state code), two digits (district), one or two letters (series), and four digits. Lowercase input fails because [A-Z] is case-sensitive; normalize with strtoupper() first or add the i modifier.


Shorthand Classes and the Dot

ShorthandMatchesEquivalent (ASCII)
\dA digit[0-9]
\DA non-digit[^0-9]
\wA word character[A-Za-z0-9_]
\WA non-word character[^A-Za-z0-9_]
\sWhitespace (space, tab, newline, …)
\SNon-whitespace
.Any character except a newline

 

<?php
preg_match_all('/\w+/', 'Hello, world_2026! Ready?', $matches);
print_r($matches[0]);

 

Output

Array
(
    [0] => Hello
    [1] => world_2026
    [2] => Ready
)

 

Punctuation and spaces are not word characters, so they separate the matches. Note that the underscore is a word character.


Quantifiers

Quantifiers specify how many times the preceding element may repeat:

QuantifierMeaningExampleMatches
*0 or moreab*cac, abc, abbbc
+1 or moreab+cabc, abbbc (not ac)
?0 or 1 (optional)colou?rcolor, colour
{n}Exactly n\d{6}Six digits
{n,}n or more\d{2,}Two or more digits
{n,m}Between n and m[a-z]{3,8}3 to 8 lowercase letters

 

Greedy vs Lazy

By default, quantifiers are greedy: they match as much as possible. Adding ? after a quantifier makes it lazy: it matches as little as possible.

<?php
$html = '<b>bold</b> text';

preg_match('/<.+>/', $html, $greedy);
preg_match('/<.+?>/', $html, $lazy);

echo $greedy[0] . "\n";
echo $lazy[0] . "\n";

 

Output

<b>bold</b>
<b>

 

  • Greedy .+ runs to the last > in the string.
  • Lazy .+? stops at the first >.

An even more precise alternative is a negated class: /<[^>]+>/ matches "a <, then any characters that are not >, then >", which is both clear and efficient. (For real HTML processing, use a DOM parser rather than regular expressions.)


Anchors and Boundaries

Anchors match positions, not characters:

AnchorPosition
^Start of the string (or line, with the m modifier)
$End of the string — or just before a final newline (or end of line with m)
\AStart of the string only
\zEnd of the string only
\bA word boundary (between a word character and a non-word character)
\BNot a word boundary

 

Word Boundaries

<?php
$text = 'cat catalog concat cat.';

echo preg_match_all('/cat/', $text) . "\n";
echo preg_match_all('/\bcat\b/', $text) . "\n";

 

Output

4
2

 

Without boundaries, cat is also found inside catalog and concat. With \b on both sides, only the whole word matches.

$ vs \z

<?php
var_dump(preg_match('/^\d+$/', "123\n"));
var_dump(preg_match('/^\d+\z/', "123\n"));

 

Output

int(1)
int(0)

 

$ allows a trailing newline, so "123\n" passes a ^\d+$ check. For strict validation, use \z (or the D modifier, see RegEx Modifiers).


Alternation and Groups

Alternation |

| means "or". Combined with a group, it limits the alternatives to part of the pattern:

<?php
$pattern = '/\.(?:jpe?g|png|webp)$/i';

foreach (['photo.JPG', 'icon.png', 'scan.jpeg', 'notes.pdf'] as $file) {
    echo $file . ': ' . (preg_match($pattern, $file) === 1 ? 'image' : 'not an image') . "\n";
}

 

Output

photo.JPG: image
icon.png: image
scan.jpeg: image
notes.pdf: not an image

 

  • (?:...) is a non-capturing group: it groups the alternatives without storing the match.
  • jpe?g matches both jpg and jpeg.
  • Checking a file name's extension like this is fine for display or routing, but not for security — see \$_FILES for validating uploads by content.

Capturing Groups

Parentheses (...) capture the text they match. Captured groups appear in the matches array by number:

<?php
preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', '2026-10-15', $m);

print_r($m);

 

Output

Array
(
    [0] => 2026-10-15
    [1] => 2026
    [2] => 10
    [3] => 15
)

 

Index 0 holds the full match; indexes 1, 2, 3 hold the groups, numbered by the position of their opening parenthesis.

Named Groups

Naming groups makes code self-explanatory:

<?php
preg_match('/^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$/', '2026-10-15', $m);

echo "Day {$m['day']}, month {$m['month']}, year {$m['year']}\n";

 

Output

Day 15, month 10, year 2026

 

The matches array contains each named group twice — by name and by number — so $m['year'] and $m[1] are the same value.

Backreferences

\1, \2, … refer to the text captured by a previous group. This finds repeated words:

<?php
preg_match_all('/\b(\w+)\s+\1\b/i', 'This is is a test test case', $m);

print_r($m[1]);

 

Output

Array
(
    [0] => is
    [1] => test
)

 

(\w+) captures a word, \s+ matches the space, and \1 requires the same word again.


Lookarounds

Lookarounds check what comes before or after a position without including it in the match:

SyntaxNameMeaning
(?=...)Positive lookaheadFollowed by ...
(?!...)Negative lookaheadNot followed by ...
(?<=...)Positive lookbehindPreceded by ...
(?<!...)Negative lookbehindNot preceded by ...

 

Example: Password Requirements With Lookaheads

<?php
$pattern = '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/';

foreach (['Secure2026', 'secure2026', 'Short1', 'NoDigitsHere'] as $password) {
    echo $password . ': ' . (preg_match($pattern, $password) === 1 ? 'accepted' : 'rejected') . "\n";
}

 

Output

Secure2026: accepted
secure2026: rejected
Short1: rejected
NoDigitsHere: rejected

 

Each (?=.*X) checks "somewhere ahead there is an X" from the start of the string, without consuming characters. The final .{8,} enforces the minimum length. (Modern password guidance favors length and breach checks over composition rules; the pattern illustrates lookaheads.)

Example: Prices After a Currency Symbol

<?php
preg_match_all('/(?<=₹)\d+/u', 'Pen ₹20, Book ₹250, Bag 999', $m);

print_r($m[0]);

 

Output

Array
(
    [0] => 20
    [1] => 250
)

 

The lookbehind requires ₹ before the digits but does not include it in the match. 999 has no ₹ and is not matched. The u modifier is needed because ₹ is a multi-byte UTF-8 character.


Unicode Patterns

By default, \w and [a-z] cover only ASCII. With the u modifier, patterns work on UTF-8 characters, and Unicode properties become useful:

PropertyMatches
\p{L}Any letter in any script
\p{Lu} / \p{Ll}Uppercase / lowercase letter
\p{N}Any number
\p{M}Combining marks (for example, vowel signs in Indian scripts)
\p{Tamil}, \p{Devanagari}, …Characters of a specific script

 

<?php
preg_match_all('/\w+/', 'Café São Paulo', $ascii);
preg_match_all('/\p{L}+/u', 'Café São Paulo', $unicode);

echo implode(' | ', $ascii[0]) . "\n";
echo implode(' | ', $unicode[0]) . "\n";

 

Output

Caf | S | o | Paulo
Café | São | Paulo

 

Without Unicode support, accented letters break words apart. For scripts such as Tamil or Hindi, which use combining vowel signs, match words with [\p{L}\p{M}]+ so that the signs stay attached to their letters.


Practical Example: Parsing a Log Line

<?php
$line = '2026-10-09 10:16:45 [ERROR] Payment failed for order #501';

$pattern = '/^(?<date>\d{4}-\d{2}-\d{2}) (?<time>\d{2}:\d{2}:\d{2}) \[(?<level>[A-Z]+)\] (?<message>.+)$/';

if (preg_match($pattern, $line, $m) === 1) {
    $entry = array_filter($m, 'is_string', ARRAY_FILTER_USE_KEY);
    print_r($entry);

    if (preg_match('/#(\d+)/', $entry['message'], $order) === 1) {
        echo "Order ID: {$order[1]}\n";
    }
}

 

Output

Array
(
    [date] => 2026-10-09
    [time] => 10:16:45
    [level] => ERROR
    [message] => Payment failed for order #501
)
Order ID: 501

 

Step-by-Step Explanation

  1. Named groups capture each part of the line: date, time, level, and message.
  2. 0 and 1 match literal square brackets around the level.
  3. .+ captures the rest of the line as the message.
  4. array_filter(..., 'is_string', ARRAY_FILTER_USE_KEY) keeps only the named keys, removing the duplicate numeric ones.
  5. A second, smaller pattern extracts the order number from the message.

Splitting the work into two simple patterns is easier to read and maintain than one complex pattern.


Quick Reference

CategorySyntax
Any character.
Character class[abc], [a-z], [^abc]
Shorthands\d \D \w \W \s \S
Quantifiers* + ? {n} {n,} {n,m} — add ? for lazy
Anchors^ $ \A \z \b \B
Alternation`a
Groups(...), (?:...), (?<name>...)
Backreference\1, \k<name>
Lookarounds(?=...) (?!...) (?<=...) (?<!...)
Unicode (with u)\p{L}, \p{N}, \p{M}, \p{Tamil}

 


Common Mistakes

Unescaped Dots

Effect: . matches any character, so /gocourse.in/ matches gocourseXin.

Fix: 0 for a literal dot.

Greedy Matching Too Much

Effect: .+ or .* runs to the last possible match.

Fix: Use lazy quantifiers or negated classes like [^>]+.

Missing Word Boundaries

Effect: Matches inside longer words.

Fix: Use \b around whole words.

ASCII-Only Classes for International Text

Effect: Names with accents or Indian-language text fail validation.

Fix: Use the u modifier with \p{L} and \p{M}.

Overly Complex Patterns

Effect: Hard-to-maintain code and performance problems.

Fix: Split the task into simpler patterns, use named groups, and document complex patterns (the x modifier allows comments).


Best Practices

  • Escape metacharacters you mean literally.
  • Anchor validation patterns with ^/\A and \z.
  • Prefer specific classes and lazy or negated patterns over broad .*.
  • Use named groups for readability.
  • Enable the u modifier whenever input may contain non-ASCII text.

Official reference: https\://www.php.net/manual/en/reference.pcre.pattern.syntax.php

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else User-defined Functions Slicing Strings PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators AJAX PHP AJAX Database AJAX XML AJAX Live Search AJAX Poll AJAX with JSON Security Introduction Password Hashing SQL Injection Prevention XSS Prevention Garbage Collection Project: Simple Blog PHP What is OOP PHP Classes/Objects The $this Keyword Access Array Items Update Array Items Add Array Items Remove Array Items Sorting Arrays Multidimensional Arrays Array Destructuring Array Spread Operator Search in Arrays Merge Arrays PHP Abstract Classes PHP Interfaces Polymorphism PHP Traits PHP Static Methods PHP Static Properties Late Static Binding Magic Methods Object Cloning MySQL Database MySQL Connect MySQL Create DB MySQL Create Table MySQL Insert Data MySQL Get Last ID MySQL Insert Multiple MySQL Prepared MySQL Select Data MySQL Where Debugging PHP Project: Image Upload Gallery $_SESSION $_ENV PHP RegEx RegEx Patterns Heredoc and Nowdoc RegEx Modifiers preg_match preg_replace preg_split PHP RegEx Functions Nullable Types Mixed and Never Types Nullsafe Operator PHP Attributes PHP Fibers Readonly Classes Property Hooks (PHP 8.4) Asymmetric Visibility (PHP 8.4) Reflection API SPL Data Structures Composer
Ask about this post
AI Ask about this post

Ask questions about RegEx Patterns and get answers drawn from it.

Signed-in readers only.