Form Input Sanitizing
What Is Sanitizing? In simple terms: Sanitizing means cleaning input: removing extra spaces, unwanted characters, or formatting so the value is in a consistent shape — for example, turning +91 98765-4
What Is Sanitizing?
In simple terms: Sanitizing means cleaning input: removing extra spaces, unwanted characters, or formatting so the value is in a consistent shape — for example, turning +91 98765-43210 into 9876543210.
Technically: Sanitization transforms a value, while validation only checks it. PHP provides sanitizing filters (FILTER_SANITIZE_*) and many string functions (trim(), preg_replace(), strip_tags(), mb_strtolower()) that can be used for this purpose.
Sanitizing vs Validating vs Escaping
These three are often confused, but each has a different job:
| Step | Question | Example | When |
|---|---|---|---|
| Normalize / sanitize | "Can I bring this value into a standard shape?" | Trim spaces, remove separators from a phone number | When receiving input |
| Validate | "Is this value acceptable?" | Is it a valid email? Is the age 18–100? | After normalizing |
| Escape | "How do I output this safely in this context?" | htmlspecialchars() for HTML, prepared statements for SQL | When using the value |
The modern approach is: normalize lightly, validate strictly, escape on output. Sanitizing is not a security measure on its own; it does not replace validation or escaping.
Why "Sanitize Everything" Is a Bad Strategy
A common older practice was to strip or encode all input on arrival "to make it safe". This causes real problems:
- It changes the meaning of data. A name like
O'Brienor a message containing<3gets altered or corrupted. - It hides invalid input. An invalid email that is "cleaned" into a different, valid-looking address is worse than rejecting it.
- It applies the wrong protection. Data stored in a database may later be shown in HTML, exported to CSV, or sent in JSON — each needs different escaping, which can only be applied at output time.
Example: Sanitizing Can Change Meaning
<?php
var_dump(filter_var(' asha (at) example.com ', FILTER_SANITIZE_EMAIL));
var_dump(filter_var('₹1,299/-', FILTER_SANITIZE_NUMBER_INT));
Output
string(17) "ashaatexample.com"
string(5) "1299-"
FILTER_SANITIZE_EMAILremoved the spaces and parentheses, producing a different string that is still not a valid email. Rejecting the input with a clear message would be better.FILTER_SANITIZE_NUMBER_INTremoved the rupee sign, comma, and slash but kept the minus sign, producing1299-, which is not a valid number.
Sanitizing filters are useful in specific situations, but their output must still be validated.
The Sanitizing Filters
| Filter | What It Does |
|---|---|
FILTER_SANITIZE_EMAIL | Removes characters not allowed in email addresses |
FILTER_SANITIZE_URL | Removes characters not allowed in URLs |
FILTER_SANITIZE_NUMBER_INT | Keeps only digits, +, and - |
FILTER_SANITIZE_NUMBER_FLOAT | Keeps digits, +, -, and (with flags) ., ,, e/E |
FILTER_SANITIZE_SPECIAL_CHARS | HTML-encodes '"<>& and characters below ASCII 32 |
FILTER_SANITIZE_FULL_SPECIAL_CHARS | Equivalent to htmlspecialchars() with ENT_QUOTES |
FILTER_SANITIZE_ADD_SLASHES | Applies addslashes() (PHP 7.3+) |
FILTER_UNSAFE_RAW | Does nothing by default; flags can strip low/high characters |
Deprecated:
FILTER_SANITIZE_STRINGandFILTER_SANITIZE_STRIPPEDwere deprecated in PHP 8.1. They tried to strip tags and encode quotes, which was confusing and insufficient for security. Usehtmlspecialchars()when outputting, andstrip_tags()only if you really need plain text.
Extracting a Number From Formatted Input
<?php
$price = filter_var('₹1,299.50', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
var_dump($price);
var_dump(filter_var($price, FILTER_VALIDATE_FLOAT));
Output
string(7) "1299.50"
float(1299.5)
The sanitizing filter removes the currency symbol and thousands separator (commas are removed unless FILTER_FLAG_ALLOW_THOUSAND is set), and the validation filter then converts the string to a real float — or returns false if the result is still not a valid number. Sanitize first, then validate.
Useful Normalizations
Most real-world sanitizing is simple normalization with string functions.
Whitespace
<?php
function normalizeSpaces(string $value): string
{
return preg_replace('/\s+/u', ' ', trim($value)) ?? '';
}
var_dump(normalizeSpaces(" Meena \t Raj \n "));
Output
string(9) "Meena Raj"
Leading and trailing whitespace is removed, and internal runs of spaces, tabs, or newlines become single spaces. Use this for names, titles, and similar single-line fields — not for passwords or multi-line text such as addresses and messages.
Phone Numbers
<?php
function normalizeIndianMobile(string $input): ?string
{
$digits = preg_replace('/\D+/', '', $input) ?? '';
if (strlen($digits) === 12 && str_starts_with($digits, '91')) {
$digits = substr($digits, 2);
} elseif (strlen($digits) === 11 && str_starts_with($digits, '0')) {
$digits = substr($digits, 1);
}
return preg_match('/^[6-9]\d{9}$/', $digits) === 1 ? $digits : null;
}
foreach (['+91 98765-43210', '098765 43210', '(987) 654-3210', '12345'] as $input) {
echo str_pad($input, 17) . var_export(normalizeIndianMobile($input), true) . "\n";
}
Output
+91 98765-43210 '9876543210'
098765 43210 '9876543210'
(987) 654-3210 '9876543210'
12345 NULL
- Every non-digit is removed, so spaces, hyphens, brackets, and
+disappear. - A country code (
91) or a leading trunk0is removed. - The result is validated; invalid numbers return
nullinstead of a "cleaned" wrong value.
Storing phone numbers in one consistent format makes searching, de-duplication, and sending SMS messages reliable.
Case
<?php
echo mb_strtolower(' Admin@Example.COM ') . "\n";
echo mb_convert_case('meena raj', MB_CASE_TITLE) . "\n";
Output
admin@example.com
Meena Raj
Normalize case for values compared case-insensitively (usernames, tags, codes). Be careful with names: forcing title case turns "McDonald" or "van der Berg" into incorrect forms, so it is usually better to keep names as typed.
strip_tags() — Removing HTML
<?php
$comment = '<b>Hello</b> <script>alert(1)</script>';
var_dump(strip_tags($comment));
var_dump(strip_tags('<p>Use <b>bold</b> and <i>italic</i>.</p>', '<b><i>'));
Output
string(14) "Hello alert(1)"
string(34) "Use <b>bold</b> and <i>italic</i>."
strip_tags()removes tags but keeps the text inside them — including the contents of<script>.- The second argument allows specific tags. However, allowed tags keep all their attributes, so
<b onmouseover="...">would remain dangerous.
strip_tags() is not an XSS defense. For plain text, store the text as entered and escape it with htmlspecialchars() when displaying it. For user-supplied rich text (formatted comments, blog posts), use a dedicated HTML sanitizer library such as HTML Purifier or Symfony HtmlSanitizer, which parse the HTML and keep only safe tags and attributes.
What Sanitizing Must Not Replace
| Threat | Correct Defense |
|---|---|
| SQL injection | Prepared statements with bound parameters — never addslashes() or escaping by hand |
| XSS (HTML output) | htmlspecialchars() at output time, or an auto-escaping template engine |
| Command injection | Avoid shell commands; if unavoidable, escapeshellarg() |
| Invalid data | Validation with clear error messages |
Practical Example: Normalizing a Profile Form
<?php
function normalizeProfile(array $input): array
{
$text = fn(string $key): string => is_string($input[$key] ?? null) ? $input[$key] : '';
$email = trim($text('email'));
$at = strrpos($email, '@');
return [
'name' => preg_replace('/\s+/u', ' ', trim($text('name'))) ?? '',
'email' => $at === false ? $email : substr($email, 0, $at) . '@' . strtolower(substr($email, $at + 1)),
'phone' => preg_replace('/\D+/', '', $text('phone')) ?? '',
'bio' => mb_substr(trim(strip_tags($text('bio'))), 0, 200),
'website' => trim($text('website')),
];
}
$submitted = [
'name' => " Meena Raj ",
'email' => ' Meena@Example.COM ',
'phone' => '98765-43210',
'bio' => '<b>PHP</b> developer from <i>Madurai</i>.',
'website' => ' https://example.com/meena ',
];
var_export(normalizeProfile($submitted));
echo "\n";
Output
array (
'name' => 'Meena Raj',
'email' => 'Meena@example.com',
'phone' => '9876543210',
'bio' => 'PHP developer from Madurai.',
'website' => 'https://example.com/meena',
)
Step-by-Step Explanation
- The
$texthelper reads each field as a string, ignoring unexpected arrays. - Names are trimmed and internal whitespace is collapsed.
- The email domain is lowercased; the local part is kept as typed.
- The phone number is reduced to digits.
- The bio is limited to plain text and 200 characters (
mb_substr()counts characters, not bytes). - The website is trimmed.
The normalized values are then validated (email format, phone pattern, allowed URL schemes) as shown in the previous lessons — normalization makes validation more forgiving of formatting differences, but it never replaces it.
Common Mistakes
Using Sanitizing Instead of Validation
Effect: Invalid input is silently changed into different, wrong values.
Fix: Sanitize lightly, then validate and report errors.
Escaping on Input
Effect: & and ' end up in the database, then get escaped again on output (&), and break CSV exports and APIs.
Fix: Store raw (validated) data; escape when outputting.
Using FILTER_SANITIZE_STRING
Effect: Deprecation notices and inconsistent behavior.
Fix: Use htmlspecialchars() on output or strip_tags() for plain text.
Trusting strip_tags() for Security
Effect: Attributes on allowed tags and leftover script text remain.
Fix: Escape plain text; use an HTML sanitizer for rich text.
Trimming or Altering Passwords
Effect: Users cannot log in with the password they chose.
Fix: Never sanitize passwords; hash them exactly as entered.
Best Practices
- Normalize formatting (whitespace, case, separators) where it helps consistency.
- Always validate after sanitizing, and reject values that cannot be fixed.
- Store validated raw data and escape it for each output context.
- Use prepared statements for SQL and a dedicated sanitizer for rich HTML.
- Leave passwords and free-form text as the user entered them.
Official reference: https://www.php.net/manual/en/filter.filters.sanitize.php