01 · THE IDEAREAD → TRY → LEARN
Why normalize first?
Raw input is messy. A user pastes an email with a trailing space, types ADA@Example.COM, or sends an age as the string " 36 ". If you validate that text as-is, you reject data that a person would call correct. If you store it as-is, you end up with duplicates that differ only by case or spacing.
The usual pipeline has three steps:
- Read each field defensively. A missing key or a non-scalar value becomes an empty string.
- Normalize it: trim, collapse whitespace, fold case, and so on.
- Validate the normalized value and return either a clean record or a list of errors.
Normalization should be predictable and should never invent data. Trimming and lowercasing an ASCII email is safe. Guessing what a malformed value "probably meant" is not.
Whitespace
trim() removes whitespace from both ends. For internal runs of spaces, tabs and newlines, preg_replace('/\s+/', ' ', $text) collapses each run into one space. Do the trim first so no leading or trailing space survives.
$raw = " Ada \t Lovelace\n";
echo json_encode(preg_replace('/\s+/', ' ', trim($raw))), PHP_EOL;"Ada Lovelace"
The output is the string "Ada Lovelace".
Validating with filter_var()
filter_var($value, $filter, $options) returns the filtered value on success and false on failure. Scalars are converted to strings internally before filtering, so 42 and '42' behave the same. For integers, pass min_range and max_range inside the nested options key:
$options = ['options' => ['min_range' => 1, 'max_range' => 120]];
echo json_encode([
filter_var('42', FILTER_VALIDATE_INT, $options),
filter_var('4x', FILTER_VALIDATE_INT, $options),
filter_var('200', FILTER_VALIDATE_INT, $options),
]), PHP_EOL;[42,false,false]
The valid value comes back as a real int. The other two give false. This is better than an (int) cast, because (int) '4x' silently becomes 4.
Be careful with the failure value. A validated integer 0 is falsy, so compare with === false rather than testing truthiness.
Worked example: a tag normalizer
This function normalizes a tag and then validates it. It trims, lowercases, turns whitespace runs into hyphens, and checks the result against a small pattern.
function normalize_tag(string $raw): array
{
$tag = strtolower(trim($raw));
$tag = preg_replace('/\s+/', '-', $tag);
if ($tag === '') {
return ['ok' => false, 'error' => 'required'];
}
if (preg_match('/^[a-z0-9-]{1,20}$/', $tag) !== 1) {
return ['ok' => false, 'error' => 'invalid'];
}
return ['ok' => true, 'value' => $tag];
}
$results = array_map('normalize_tag', [" PHP Basics ", " ", "C++"]);
echo json_encode($results), PHP_EOL;[{"ok":true,"value":"php-basics"},{"ok":false,"error":"required"},{"ok":false,"error":"invalid"}]
Notice the order: normalization runs before the emptiness check. A whitespace-only string is therefore reported as required, not invalid. The function also always returns the same shape, with an ok flag and either value or error, so callers never have to guess.
Exercise contract
Write normalize_signup(array $input): array. It reads name, email and age from the input. A missing key or a non-scalar value counts as an empty string.
Normalization:
name: trim, then collapse each whitespace run to one space.email: trim, then lowercase withstrtolower().age: trim, then convert to an integer withfilter_var().
Validation:
namemust not be empty. If it is, the error isrequired.emailmust passFILTER_VALIDATE_EMAIL. If it does not, the error isinvalid. An empty email also fails this way.agemust be an integer from 13 to 120 inclusive. If it is not, the error isinvalid.
If any field fails, return ['ok' => false, 'errors' => [...]]. The errors array is keyed by field and lists failing fields in the order name, email, age. Otherwise return ['ok' => true, 'value' => ['name' => ..., 'email' => ..., 'age' => ...]], where age is an int.
The tests compare the returned arrays, so build them with exactly these keys and in this order.
Recap
- Normalize first, then validate the normalized value.
- Use
trim()andpreg_replace()for whitespace andstrtolower()for ASCII case folding. - Use
filter_var()with=== falsechecks, not casts, to turn text into validated values. - Return one consistent shape: a clean record, or structured errors keyed by field.
Functions · Arrays · Types
- Return a clear in-memory validated record or structured errors
- Implement and test a deterministic PHP 8.4 function