Table of Contents
Here’s a pattern I keep seeing in real codebases: a method starts returning bool, then requirements grow, and the return type evolves through several stages. Each stage tells you something about what the method is actually doing.
Stage 1: The Boolean
public function validate(array $data): bool
{
if (empty($data['email'])) {
return false;
}
// ... more checks
return true;
}
Simple. Did it work? Yes or no. But the caller has no idea why it failed.
Stage 2: true or String
public function validate(array $data): true|string
{
if (empty($data['email'])) {
return 'Email is required';
}
if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
return 'Invalid email format';
}
return true;
}
Now the caller gets context. true means success, a string means “here’s what went wrong.” The true type (PHP 8.2+) makes this explicit β you can’t accidentally return false.
The calling code reads naturally:
$result = $validator->validate($input);
if ($result !== true) {
// $result is the error message
throw new ValidationException($result);
}
Stage 3: Array or String
public function process(array $items): array|string
{
if (empty($items)) {
return 'No items to process';
}
$results = [];
foreach ($items as $item) {
$results[] = $this->transform($item);
}
return $results;
}
The method got smarter. On success it returns structured data, on failure it returns why. The union type documents this contract right in the signature.
When to Use Each
boolβ When the caller truly only needs yes/no (toggle states, feature flags, existence checks)true|stringβ When failure needs explanation but success is just “it worked”array|stringβ When success produces data and failure needs explanation
The Takeaway
If you find yourself adding error logging inside a method that returns bool, that’s the signal. The method wants to tell you more than just true/false. Let the return type evolve to match what the method actually knows.
Union types aren’t just a PHP 8 feature to know about β they’re documentation that lives in the code itself. When you see true|string, you immediately know: success is silent, failure talks.

Leave a Reply