Learning Objectives – after this chapter you will be able to:
- Explain client-side and server-side validation and when each is needed.
- Write regular expressions using quantifiers, metacharacters and character classes.
- Use
preg_match(),preg_match_all(),preg_replace(),preg_replace_callback(),preg_split()andpreg_grep(). - Validate a registration form on the server and show the error messages on the same page.
Client-Side and Server-Side Validation
User input should be validated in the browser whenever possible (with HTML5 attributes such as required, type="email", pattern, or with JavaScript). Browser validation is faster and reduces the server load.
But browser validation can be switched off or bypassed by anyone, so you must always validate again on the server – especially when the input is inserted into a database. A good way to validate a form on the server is to post the form to itself instead of jumping to a different page. The user then gets the error messages on the same page as the form, which makes it easier to find and correct the mistakes.
Regular Expressions
A regular expression (regex) is a pattern that describes a set of strings. It confirms that an entry matches a pattern – for example the format of an e-mail address, a telephone number, a PIN code or a password. In PHP, regular expressions are written between delimiters, usually slashes: "/pattern/flags". The flag i makes the match case-insensitive.
Quantifiers
Quantifiers specify how many times a character, or a group of characters in parentheses, may repeat. A quantifier always refers to the item immediately to its left.
| Quantifier | Meaning |
|---|---|
| * | 0 or more occurrences |
| + | 1 or more occurrences |
| ? | 0 or 1 occurrence (optional) |
| {n} | exactly n occurrences |
| {min,} | at least min occurrences |
| {min,max} | between min and max occurrences |
Metacharacters
Characters with special meaning in a regular expression are called metacharacters: ^ $ . * + ? { } [ ] ( ) | and \. To match one of them literally, escape it with a backslash, e.g. \. for a dot.
| Symbol | Meaning |
|---|---|
| . | any single character (except a new line) |
| ^ | start of the string (inside [ ] it means "not") |
| $ | end of the string |
| | | alternation – either the left or the right part, e.g. cat|dog |
| ( ) | grouping and capturing a sub-pattern |
| [ ] | a character class |
| \ | escapes the next character |
Character classes
A character class, written in square brackets, matches one character from the list. [aeiou] matches any vowel. Ranges use a hyphen: [0-9] any digit, [a-z] any lowercase letter, [A-Z] any uppercase letter, [a-zA-Z0-9] any letter or digit. [^0-9] matches any character that is not a digit. To include a hyphen, put it first: [-.?].
Predefined character classes
| Class | Matches |
|---|---|
| \d | any decimal digit – same as [0-9] |
| \D | any non-digit |
| \w | any word character – letters, digits and underscore [a-zA-Z0-9_] |
| \W | any non-word character |
| \s | any white-space character (space, tab, new line) |
| \S | any non-white-space character |
| \b | a word boundary |
Matching Patterns – preg_match()
preg_match() performs Perl-compatible pattern matching on a string:
preg_match(pattern, subject [, &matches [, flags [, offset]]])
It returns 1 if a match is found and 0 otherwise. The optional $matches array receives the text that matched: $matches[0] is the whole match, $matches[1] the first parenthesised group, and so on. Let us search the string "Hello World!" for the letters "ell":
<?php
if (preg_match("/ell/", "Hello World!", $matches)) {
echo "Match was found <br>";
echo $matches[0];
}
?>Match was found ell
The next pattern looks for the letters "ll" followed by any characters (.*):
<?php
if (preg_match("/ll.*/", "The History of Halloween", $matches)) {
echo "Match was found <br>";
echo $matches[0];
}
?>Match was found lloween
Validating a strong password
A popular use of regular expressions is validation. The example checks whether a password is strong: at least 8 characters with at least one lowercase letter, one uppercase letter and one digit.
<?php
$passwords = ["Fyfjk34sdfjfsjq7", "secret12", "Short1A"];
$pattern = "/^.*(?=.{8,})(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).*$/";
foreach ($passwords as $password) {
if (preg_match($pattern, $password)) {
echo "$password : Your password is strong.<br>";
} else {
echo "$password : Your password is weak.<br>";
}
}
?>Fyfjk34sdfjfsjq7 : Your password is strong. secret12 : Your password is weak. Short1A : Your password is weak.
^ and $ anchor the pattern at the start and the end of the string, and .* at both ends allows any characters. In between are look-ahead groups (?=…) – "the following text must be like this" – which do not consume characters, so the order of the requirements does not matter:
(?=.{8,})– there are at least 8 characters;(?=.*\d)– somewhere there is a digit;(?=.*[a-z])and(?=.*[A-Z])– somewhere there is a lowercase and an uppercase letter.
Validating an e-mail address
<?php
$emails = ["firstname.lastname@aaa.bbb.com", "1abc@site.com", "wrong@site"];
$regexp = "/^[^0-9][A-z0-9_]+([.][A-z0-9_]+)*[@][A-z0-9_]+([.][A-z0-9_]+)*[.][A-z]{2,4}$/";
foreach ($emails as $email) {
if (preg_match($regexp, $email)) {
echo "$email : Email address is valid.<br>";
} else {
echo "$email : Email address is <u>not</u> valid.<br>";
}
}
?>firstname.lastname@aaa.bbb.com : Email address is valid. 1abc@site.com : Email address is not valid. wrong@site : Email address is not valid.
This expression rejects an address that starts with a digit and allows several dots in the user name and domain. In real projects it is simpler and more reliable to use filter_var($email, FILTER_VALIDATE_EMAIL) (Chapter 21).
For speed, preg_match() stops at the first match. preg_match_all() finds all matches and returns their number:
<?php
$text = "Call 9876543210 or 9123456789; office 0755-2551234.";
$n = preg_match_all("/\b[6-9]\d{9}\b/", $text, $m);
echo "$n mobile numbers found:<br>";
echo implode("<br>", $m[0]);
?>2 mobile numbers found: 9876543210 9123456789
Replacing Patterns – preg_replace()
preg_replace() looks for sub-strings that match a pattern and replaces them with new text:
preg_replace(pattern, replacement, subject [, limit])
It returns the changed string (or an unchanged copy if nothing matched). In the replacement, back references $1, $2 … insert the text matched by the corresponding parenthesised group; $0 is the whole match.
<?php
// replace the year in a copyright notice
echo preg_replace("/([Cc]opyright) 200(3|4|5|6)/", "$1 2026", "Copyright 2005");
echo "<br>";
// change the date format from yyyy-mm-dd to dd/mm/yyyy
echo preg_replace("/(\d+)-(\d+)-(\d+)/", "$3/$2/$1", "2026-10-22");
echo "<br>";
// arrays of patterns and replacements
$search = ["/\s+/", "/(\d{4})-(\d{2})-(\d{2})/"];
$replace = [" ", "$3/$2/$1"];
echo preg_replace($search, $replace, "Posted by John on 2026-02-15");
?>Copyright 2026 22/10/2026 Posted by John on 15/02/2026
preg_replace_callback() instead of the /e modifier
Old PHP versions had an /e modifier that executed the replacement as PHP code – for example to convert a matched name to capitals. It was a serious security risk and was removed in PHP 7.0. Use preg_replace_callback(), which calls a function for every match:
<?php
$string = "Posted by John | 2026-02-15 02:43:41";
echo preg_replace_callback(
"/(Posted by) (\w+)/",
fn($m) => $m[1] . " " . strtoupper($m[2]),
$string
);
?>Posted by JOHN | 2026-02-15 02:43:41
Splitting and Filtering – preg_split() and preg_grep()
preg_split() breaks a string apart at a pattern – useful when the separator is not one fixed string. preg_grep() returns the array elements that match a pattern.
<?php
$keywords = preg_split("/[\s,]+/", "php, regular expressions, mysqli");
echo "<pre>";
print_r($keywords);
$names = ['Andrew', 'John', 'Peter', 'Nastin', 'Bill'];
$output = preg_grep('/^[a-j]/i', $names); // names starting with A to J
print_r($output);
echo "</pre>";
?>Array
(
[0] => php
[1] => regular
[2] => expressions
[3] => mysqli
)
Array
(
[0] => Andrew
[1] => John
[4] => Bill
)Complete Example – Server-Side Form Validation
The registration form below posts to itself, validates each field and re-displays the form with error messages and the values already typed. When everything is correct it shows a success message.
<?php
$name = $email = $mobile = "";
$errors = [];
function clean($v) { return htmlspecialchars(trim($v)); }
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$name = clean($_POST["name"] ?? "");
$email = clean($_POST["email"] ?? "");
$mobile = clean($_POST["mobile"] ?? "");
if ($name === "") {
$errors['name'] = "Name is required";
} elseif (!preg_match("/^[a-zA-Z ]{3,40}$/", $name)) {
$errors['name'] = "Only letters and spaces (3 to 40)";
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors['email'] = "Invalid e-mail address";
}
if (!preg_match("/^[6-9][0-9]{9}$/", $mobile)) {
$errors['mobile'] = "Enter a 10-digit mobile number";
}
}
$ok = $_SERVER["REQUEST_METHOD"] == "POST" && !$errors;
?>
<html><body style="font-family:Arial;font-size:13px">
<style>.err{color:#c00;font-size:12px}</style>
<?php if ($ok): ?>
<p style="color:green">Thank you, <?= $name ?>. Registration successful.</p>
<?php else: ?>
<form method="post" action="register.php">
Name: <input id="name" name="name" value="<?= $name ?>">
<span class="err"><?= $errors['name'] ?? '' ?></span><br>
E-mail: <input id="email" name="email" value="<?= $email ?>">
<span class="err"><?= $errors['email'] ?? '' ?></span><br>
Mobile: <input id="mobile" name="mobile" value="<?= $mobile ?>">
<span class="err"><?= $errors['mobile'] ?? '' ?></span><br>
<input type="submit" id="go" value="Register">
</form>
<?php endif; ?>
</body></html>
Submitting correct data gives the success message:
Thank you, Rahul Verma. Registration successful.
Key Takeaways
- Validate in the browser for convenience and always on the server for safety.
- Regular expressions are written between delimiters:
/pattern/flags. - Quantifiers:
* + ? {n} {min,max}; anchors^ $; classes[a-z],\d \w \s. preg_match()finds the first match;preg_match_all()finds all.preg_replace()replaces with back references$1;/ewas removed in PHP 7 – usepreg_replace_callback().preg_split()splits by a pattern;preg_grep()filters an array.
Program File
Extract the ZIP into C:\xampp\htdocs. It creates the folder phpbook/ch12 used in the file labels above; open the programs at http://localhost/phpbook/… with Apache (and MySQL, where needed) started in the XAMPP Control Panel.
Question / Answer (15 questions)
MCQ Test : Check Your Understanding (5 questions)
Select an option and press Check Answer, or answer all questions and press Submit Test to see your score. Each answer comes with an explanation.
Bibliography/References
The notes on this page are the author’s own compilation from the e-book PHP: Basics to Advance. Every program was run on Apache with PHP 8 and MariaDB, and the output shown is the real result of that run. The works listed below were consulted and are cited for attribution and further reading. Product names and trademarks belong to their respective owners.
- The PHP Group. (n.d.). PHP Manual – PCRE Patterns. https://www.php.net/manual/en/reference.pcre.pattern.syntax.php
- The PHP Group. (n.d.). PHP Manual – preg_match. https://www.php.net/manual/en/function.preg-match.php
- The PHP Group. (n.d.). PHP Manual – preg_replace_callback. https://www.php.net/manual/en/function.preg-replace-callback.php
- The PHP Group. (n.d.). PHP Manual – preg_split. https://www.php.net/manual/en/function.preg-split.php
- MDN contributors. (n.d.). Client-side form validation. MDN Web Docs. https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation