The Result Pattern
What is the Result Pattern?
Section titled “What is the Result Pattern?”- The Result pattern is a design pattern used to handle the outcome of operations in a way that explicitly communicates success or failure, and carries additional information about the result or error.
- The key idea is to return an object (often called Result,Outcome,Response, etc.) that encapsulates whether the operation succeeded and relevant data or error details.
Components of the Result Pattern
Section titled “Components of the Result Pattern”- 
Success/failure indicator: - A boolean flag ($successor similar) indicating whether the operation succeeded (true) or failed (false).
 
- A boolean flag (
- 
Result data: The actual data returned if the operation succeeded. 
- 
Error information: An error message, exception object, or any other relevant information explaining why the operation failed. 
- 
Methods to access results: - Success/failure checkers: Methods (isSuccess()andisFailure()) to check whether the operation succeeded or failed.
- Data access: Method (getData()) to retrieve the result data when the operation is successful.
- Error access: Method (getError()) to retrieve the error message or object when the operation fails.
 
- Success/failure checkers: Methods (
Benefits of the Result Pattern vs. Exceptions
Section titled “Benefits of the Result Pattern vs. Exceptions”- 
Explicit control flow: Provides a more explicit and predictable way to handle success and failure without disrupting program flow like exceptions do. Code becomes easier to read and understand by avoiding the complexity of try/catch blocks. 
- 
Performance: More efficient than exceptions, especially in performance-critical sections where exceptions would be expensive to handle. 
- 
Rich error context and better testing: Allows for detailed error reporting with additional context, and makes testing/debugging easier by avoiding the need to simulate exceptional conditions with try/catch blocks. 
- 
Avoids exception overuse: Exceptions should be reserved for exceptional situations, not regular control flow. The Result pattern keeps normal outcomes clear and explicit using return values. 
- 
Composability: Result objects can be easily passed around and composed with other functions or methods, promoting code reuse and maintainability. 
When to Use Each
Section titled “When to Use Each”- 
Result Pattern: Ideal for cases where you expect failures as part of normal operation (e.g., user input validation, simple computations). 
- 
Exceptions: More appropriate for truly exceptional, unexpected conditions that should not occur under normal operation (e.g., file system errors, network errors, database connectivity issues). 
Implementing the Result Pattern in PHP
Section titled “Implementing the Result Pattern in PHP”The following example illustrates how you can implement the Result pattern in PHP:
class Result{    private bool $is_success = false;    private string $message;    private $data;    private $errors;
    private function __construct(bool $success, string $message, mixed $data = null, mixed $errors = null)    {        $this->is_success = $success;        $this->message = $message;        $this->data = $data;        $this->errors = $errors;    }
    public static function success($message, mixed $data = null): Result    {        return new Result(true, $message, $data);    }
    public static function failure($message, mixed $errors = null): Result    {        return new Result(false, $message, null, $errors);    }
    public function isSuccess(): bool    {        return $this->is_success;    }
    public function isFailure(): bool    {        return !$this->is_success;    }
    public function getData(): mixed    {        if (!$this->is_success) {            throw new Exception("Cannot get data from a failed result.");        }        return $this->data;    }
    public function getErrors(): mixed    {        if ($this->is_success) {            throw new Exception("Cannot get errors from a successful result.");        }        return $this->errors;    }
    public function getMessage(): string    {        return $this->message;    }
    public function __toString(): string    {        if ($this->is_success) {            $data = $this->data !== null ? 'Data: ' . json_encode($this->data) : 'No data';            return "Success: {$this->message}, {$data}";        } else {            $errors = $this->errors !== null ? 'Errors: ' . json_encode($this->errors) : 'No errors';            return "Failure: {$this->message}, {$errors}";        }    }}Example: Using the Result pattern
Section titled “Example: Using the Result pattern”<?php
class PlanetValidator{    private const VALID_TYPES = ['Terrestrial', 'Gas Giant', 'Ice Giant', 'Dwarf'];
    public function validate(array $planetData): Result    {        $errors = [];
        // Validate planet name.        if (empty($planetData['name']) || !is_string($planetData['name'])) {            $errors[] = "Planet name is required and must be a string.";        } elseif (strlen($planetData['name']) < 2) {            $errors[] = "Planet name must be at least 2 characters long.";        }
        // Validate planet type.        if (empty($planetData['type'])) {            $errors[] = "Planet type is required.";        } elseif (!in_array($planetData['type'], self::VALID_TYPES)) {            $errors[] = "Invalid planet type. Must be one of: " . implode(', ', self::VALID_TYPES);        }
        // Validate radius.        if (!isset($planetData['radius'])) {            $errors[] = "Planet radius is required.";        } elseif (!is_numeric($planetData['radius']) || $planetData['radius'] <= 0) {            $errors[] = "Planet radius must be a positive number.";        }
        // Validate moons count.        if (!isset($planetData['moons'])) {            $errors[] = "Number of moons is required.";        } elseif (!is_int($planetData['moons']) || $planetData['moons'] < 0) {            $errors[] = "Number of moons must be a non-negative integer.";        }
        // Return result based on validation.        if (!empty($errors)) {            return Result::failure("Planet data validation failed.", $errors);        }
        return Result::success("Planet data is valid.", $planetData);    }}
// Example usage:$validator = new PlanetValidator();
// Test 1: Valid planet data.$result = $validator->validate([    'name' => 'Mars',    'type' => 'Terrestrial',    'radius' => 3389,    'moons' => 2]);
if ($result->isSuccess()) {    echo "Success: " . $result->getMessage() . "<br/>";    echo "Planet data: " . json_encode($result->getData()) . "<br/>";} else {    echo "Failure: " . $result->getMessage() . "<br/>";    echo "Errors: " . json_encode($result->getErrors()) . "<br/>";}
// Test 2: Invalid planet type.$result = $validator->validate([    'name' => 'Unknown',    'type' => 'Rocky',    'radius' => 5000,    'moons' => 1]);
if ($result->isSuccess()) {    echo "Success: " . $result->getMessage() . "<br/>";    echo "Planet data: " . json_encode($result->getData()) . "<br/>";} else {    echo "Failure: " . $result->getMessage() . "<br/>";    echo "Errors: " . json_encode($result->getErrors()) . "<br/>";}
// Test 3: Multiple validation errors.$result = $validator->validate([    'name' => '',    'type' => 'Invalid',    'radius' => -100,    'moons' => -5]);
if ($result->isSuccess()) {    echo "Success: " . $result->getMessage() . "<br/>";    echo "Planet data: " . json_encode($result->getData()) . "<br/>";} else {    echo "Failure: " . $result->getMessage() . "<br/>";    echo "Errors: " . json_encode($result->getErrors()) . "<br/>";}?>