Chapter 9: Testing & Debugging
Goal: Build a practical workflow to test, understand, and fix patterns.
1) Start Simple with PHP
You don’t need any extra tools to test patterns. PHP’s built-in functions are more than enough for learning.
$pattern = '/\d{3}/';
$text = 'Order 123 and 456';
if (preg_match($pattern, $text, $matches)) {
echo "Found: " . $matches[0]; // "123"
}
To find all matches:
preg_match_all('/\d+/', 'a1 b22 c333', $matches);
print_r($matches[0]); // ["1", "22", "333"]
2) A Practical Testing Workflow
When a pattern doesn’t behave the way you expect, go step by step:
Step 1: Write a small test
Keep it minimal. Test on the exact string you’re trying to match.
$pattern = '/^hello$/';
var_dump(preg_match($pattern, 'hello')); // int(1)
var_dump(preg_match($pattern, 'hello!')); // int(0)
Step 2: Check anchors and scope
If it’s matching too much or too little, check anchors.
// Matches anywhere
preg_match('/cat/', 'category'); // matches "cat"
echo preg_match('/cat/', 'category'), PHP_EOL; // 1
// Match whole word only
preg_match('/\bcat\b/', 'category'); // no match
echo preg_match('/\bcat\b/', 'category'), PHP_EOL; // 0
Step 3: Check quantifiers
If it’s greedy when you want lazy, or vice versa, check quantifiers.
$text = '<b>hi</b>';
// Too much
preg_match('/<.+>/', $text, $m); // <b>hi</b>
echo $m[0], PHP_EOL;
// Just right
preg_match('/<.+?>/', $text, $m); // <b>
echo $m[0], PHP_EOL;
Step 4: Check groups and alternation
If | is behaving oddly, it’s usually a grouping issue (Chapter 5).
// Wrong scope
preg_match('/jpg|png\.gif/', 'a.png.gif', $m); // matches "png.gif" but for wrong reason
echo $m[0], PHP_EOL;
// Better
preg_match('/(?:jpg|png)\.gif/', 'a.png.gif', $m);
echo $m[0], PHP_EOL;
3) Common Debugging Tricks
Use var_dump on matches
preg_match('/(\w+)@(\w+)\.(\w+)/', '[email protected]', $matches);
var_dump($matches);
// Shows full match + each captured group
Test with preg_match_all to see all matches
preg_match_all('/\b\w{4}\b/', 'cat dog bird fish', $matches);
print_r($matches[0]); // ["bird", "fish"]
Isolate pieces
If a long pattern fails, break it down. Test each part separately, then combine.
// Test parts
preg_match('/^\w+$/', 'user'); // yes
echo preg_match('/^\w+$/', 'user'), PHP_EOL; // 1
preg_match('/^@/', '@example'); // yes
echo preg_match('/^@/', '@example'), PHP_EOL; // 1
preg_match('/^\w+\.\w+$/', 'com'); // no — this part expects a dot
echo preg_match('/^\w+\.\w+$/', 'com'), PHP_EOL; // 0
Watch for escaping
Remember Chapter 1: in PHP strings, backslashes need care. In single quotes less so, but better to be aware.
// In PHP string
$pattern = '/\d+/'; // \d is fine in single quotes
echo preg_match($pattern, 'Order 123'), PHP_EOL; // 1
Check case sensitivity
preg_match('/hello/', 'HELLO'); // no match
echo preg_match('/hello/', 'HELLO'), PHP_EOL; // 0
preg_match('/hello/i', 'HELLO'); // match
echo preg_match('/hello/i', 'HELLO'), PHP_EOL; // 1
4) Testing Checklist
Before using a pattern anywhere important, ask yourself:
- Does it match what I want? Test positive cases
- Does it reject what I don’t want? Test negative cases
- Does it match the whole thing or just part? Consider anchors
- Is it too greedy? Try lazy if needed
- Is it safe? Check for nested quantifiers
- Does it handle edge cases? Empty string, single char, special chars
- Is it readable? If it’s too complex, can you simplify it?
5) Exercises
Exercise 1: Debug a broken pattern
This pattern is supposed to match dates like 2025-10-23, but something’s wrong. Fix it.
$pattern = '/\d{4}\d{2}\d{2}/'; // broken
echo preg_match($pattern, '2025-10-23'), PHP_EOL; // 0 — it never matches a real date
Solution
Missing the dashes:
$pattern = '/\d{4}-\d{2}-\d{2}/';
echo preg_match($pattern, '2025-10-23'), PHP_EOL; // 1
Exercise 2: Greedy vs lazy
Extract just title from <title>My Page</title>. Use a lazy match.
Solution
preg_match('/<title>(.*?)<\/title>/', '<title>My Page</title>', $matches);
echo $matches[1]; // "My Page"
Exercise 3: Whole word only
Fix this so it matches cat only as a whole word, not in category.
$pattern = '/cat/';
echo preg_match($pattern, 'category'), PHP_EOL; // 1 — matches too much
Solution
$pattern = '/\bcat\b/';
echo preg_match($pattern, 'category'), PHP_EOL; // 0
Exercise 4: Build a test
Write a small test script that checks if an email-like string is valid using anchors and a simple pattern.
Solution
$pattern = '/^[\w.-]+@[\w.-]+\.[a-z]{2,}$/i';
$tests = ['[email protected]', 'bad-email', '[email protected]'];
foreach ($tests as $t) {
echo $t . ': ' . (preg_match($pattern, $t) ? 'OK' : 'NO') . PHP_EOL;
}
Key Takeaways
- Test small — Start with a tiny example and grow
- Use
preg_match/preg_match_allwith$matchesto inspect results - Break it down — Isolate complex patterns into parts
- Think about scope — Anchors, word boundaries, and grouping fix most issues
- Test negative cases — Making sure it doesn’t match is just as important
Optional: Extra Tools
If you’d like to explain or visualize patterns while debugging, there are optional tools available. You can skip these entirely and still follow everything above.
vendor/bin/regex explain '/\d{4}-\d{2}-\d{2}/'
vendor/bin/regex diagram '/(\w+)\s+\1/'
use PHPRegex\Toolkit\Regex;
$regex = Regex::create();
echo $regex->explain('/^[a-z]+$/i');