php 62 lines · 9 steps

Building a reusable address form validator in PHP

A validator class accumulates field errors into a keyed map, layering required checks, format rules, and country-specific logic.

Explained by highlit
1<?php
2 
3namespace App\Validation;
4 
5class AddressFormValidator
6{
7 private array $errors = [];
8 
9 public function validate(array $data): array
10 {
11 $this->errors = [];
12 
13 $this->required($data, 'recipient', 'Recipient name is required.');
14 $this->required($data, 'phone', 'A contact phone number is required.');
15 
16 if (!empty($data['phone']) && !preg_match('/^\+?[0-9 ()-]{7,20}$/', $data['phone'])) {
17 $this->add('phone', 'Phone number format is invalid.');
18 }
19 
20 $address = $data['address'] ?? [];
21 
22 $this->requiredNested($address, 'address.line1', 'Street address is required.');
23 $this->requiredNested($address, 'address.city', 'City is required.');
24 $this->requiredNested($address, 'address.country', 'Country is required.');
25 
26 $country = strtoupper(trim($address['country'] ?? ''));
27 
28 if ($country === 'US') {
29 $zip = $address['postal_code'] ?? '';
30 if (!preg_match('/^\d{5}(-\d{4})?$/', $zip)) {
31 $this->add('address.postal_code', 'A valid US ZIP code is required.');
32 }
33 if (empty($address['state'])) {
34 $this->add('address.state', 'State is required for US addresses.');
35 }
36 } elseif ($country !== '' && empty($address['postal_code'])) {
37 $this->add('address.postal_code', 'Postal code is required.');
38 }
39 
40 return $this->errors;
41 }
42 
43 private function required(array $data, string $key, string $message): void
44 {
45 if (trim((string) ($data[$key] ?? '')) === '') {
46 $this->add($key, $message);
47 }
48 }
49 
50 private function requiredNested(array $address, string $fullKey, string $message): void
51 {
52 $field = substr($fullKey, strrpos($fullKey, '.') + 1);
53 if (trim((string) ($address[$field] ?? '')) === '') {
54 $this->add($fullKey, $message);
55 }
56 }
57 
58 private function add(string $key, string $message): void
59 {
60 $this->errors[$key][] = $message;
61 }
62}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Collecting errors into a keyed map lets one pass report every problem at once instead of failing on the first.
  2. 2Small private helpers like required and add keep validation rules readable and consistent.
  3. 3Conditional branches let you apply locale-specific rules without cluttering the common path.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building a reusable address form validator in PHP — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code