Skip to content

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.

  1. Success/failure indicator:

    • A boolean flag ($success or similar) indicating whether the operation succeeded (true) or failed (false).
  2. Result data: The actual data returned if the operation succeeded.

  3. Error information: An error message, exception object, or any other relevant information explaining why the operation failed.

  4. Methods to access results:

    • Success/failure checkers: Methods (isSuccess() and isFailure()) 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.

Benefits of the Result Pattern vs. Exceptions

Section titled “Benefits of the Result Pattern vs. Exceptions”
  1. 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.

  2. Performance: More efficient than exceptions, especially in performance-critical sections where exceptions would be expensive to handle.

  3. 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.

  4. 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.

  5. Composability: Result objects can be easily passed around and composed with other functions or methods, promoting code reuse and maintainability.


  1. Result Pattern: Ideal for cases where you expect failures as part of normal operation (e.g., user input validation, simple computations).

  2. 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).


The following example illustrates how you can implement the Result pattern in PHP:

PHP implementation of the Result pattern
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 #1: Using the Result pattern for input validation
<?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/>";
}
?>