Let Your Return Types Evolve: From Bool to Union Types

πŸ“– 2 minutes read

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.

Daryle De Silva

VP of Technology

11+ years building and scaling web applications. Writing about what I learn in the trenches.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *