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”-
Exceptions should be reserved for truly exceptional situations, not regular control flow. The Result pattern provides a more predictable way to handle success and failure without the complexity of try/catch blocks.
-
The Result pattern is more efficient than exceptions, especially in performance-critical sections where exceptions would be expensive to handle.
-
Allows for detailed error reporting with additional context, and makes testing and debugging easier by avoiding the need to simulate exceptional conditions with try/catch blocks.
-
Result objects can be easily passed around and composed with other functions or methods, promoting code reuse and maintainability.
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();
// 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/>";}
// Invalid planet data — triggers 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/>";}?>