A PHP implementation of the Result type pattern, inspired by Rust's Result and JavaScript's neverthrow library. This library provides a type-safe way to handle errors without relying on exceptions, making your code more predictable and easier to reason about.
- Type-safe error handling without exceptions
- Immutable Result type with
OkandErrvariants - Comprehensive set of methods for working with Results
- Pure functional programming approach
- PHP 8.2+ with strict typing support
composer require martyrer/throwless-php-
isOk()- Check if the Result contains a success valueisErr()- Check if the Result contains an error valueisOkAnd(callable $fn)- Executes the closure and returns its result as a boolean if the result is OkisErrAnd(callable $fn)- Executes the closure and returns its result as a boolean if the result is Err
-
map(callable $fn)- Transform the success valuemapErr(callable $fn)- Transform the error valueandThen(callable $fn)- Chain Result-returning operations
-
match(callable $okFn, callable $errFn)- Handle both success and error cases
-
inspect(callable $fn)- Inspect the success valueinspectErr(callable $fn)- Inspect the error value
-
unwrap()- Get the success value or throwunwrapErr()- Get the error value or throwunwrapOr(mixed $default)- Get the success value or a defaultunwrapOrElse(callable $fn)- Get the success value or compute a defaultunwrapOrDefault(DefaultValueProvider $provider)- Get the success value or type's defaultexpect(string $msg)- Get the success value or throw with custom messageexpectErr(string $msg)- Get the error value or throw with custom messageunwrapUnchecked()- Get the success value without checking (unsafe)unwrapErrUnchecked()- Get the error value without checking (unsafe)
use Martyrer\Throwless\Ok;
use Martyrer\Throwless\Err;
use Martyrer\Throwless\Result;
// Creating Results
$success = new Ok(42); // Ok<int, mixed>
$failure = new Err("something went wrong"); // Err<mixed, string>
// Checking Result type
$success->isOk(); // true
$success->isErr(); // false
$failure->isOk(); // false
$failure->isErr(); // true
// Unwrapping values
$success->unwrap(); // 42
$failure->unwrapErr(); // "something went wrong"
// Using default values
$success->unwrapOr(0); // 42
$failure->unwrapOr(0); // 0Check if the Result contains a success value:
$result = new Ok(42);
$result->isOk(); // true
$error = new Err("failed");
$error->isOk(); // falseCheck if the Result contains an error value:
$result = new Ok(42);
$result->isErr(); // false
$error = new Err("failed");
$error->isErr(); // trueCheck if Result is Ok and the value matches a predicate:
$result = new Ok(42);
$isEven = $result->isOkAnd(fn($x) => $x % 2 === 0); // true
$isNegative = $result->isOkAnd(fn($x) => $x < 0); // falseCheck if Result is Err and the error matches a predicate:
$error = new Err("invalid input");
$isInputError = $error->isErrAnd(fn($e) => str_contains($e, "input")); // true
$isTimeout = $error->isErrAnd(fn($e) => str_contains($e, "timeout")); // falseTransform the success value while preserving the error:
$result = new Ok(5);
$doubled = $result->map(fn($x) => $x * 2); // Ok(10)
$error = new Err("failed");
$doubled = $error->map(fn($x) => $x * 2); // Err("failed")Transform the error value while preserving the success:
$result = new Err("error");
$mapped = $result->mapErr(fn($e) => "Error: " . $e); // Err("Error: error")
$success = new Ok(42);
$mapped = $success->mapErr(fn($e) => "Error: " . $e); // Ok(42)Chain operations that might fail:
function divide($x, $y): Result {
return $y === 0 ? new Err("division by zero") : new Ok($x / $y);
}
$result = new Ok(10)
->andThen(fn($x) => divide($x, 2)) // Ok(5)
->andThen(fn($x) => divide($x, 0)); // Err("division by zero")Handle both success and error cases:
$result = new Ok(42);
$value = $result->match(
fn($value) => "Success: $value",
fn($error) => "Error: $error"
); // "Success: 42"Perform side effects on success value without consuming the Result:
$result = new Ok(42);
$result->inspect(fn($value) => print("Got value: $value"))
->map(fn($x) => $x * 2);Perform side effects on error value without consuming the Result:
$error = new Err("error");
$error->inspectErr(fn($err) => print("Error occurred: $err"))
->mapErr(fn($err) => "Handled: $err");Get the success value or throw an exception:
$result = new Ok(42);
$value = $result->unwrap(); // 42
$error = new Err("oops");
$value = $error->unwrap(); // throws UnwrapExceptionGet the error value or throw an exception:
$error = new Err("error message");
$value = $error->unwrapErr(); // "error message"
$success = new Ok(42);
$value = $success->unwrapErr(); // throws UnwrapExceptionGet the value or a default:
$result = new Err("error");
$value = $result->unwrapOr(42); // 42Get the value or compute a default:
$result = new Err("error");
$value = $result->unwrapOrElse(fn($error) => strlen($error)); // 5Get the success value or the type's default value:
class DefaultProvider implements DefaultValueProvider {
public function getDefault(): int {
return 0;
}
}
$result = new Err("error");
$value = $result->unwrapOrDefault(new DefaultProvider()); // 0Unwrap with a custom error message:
$result = new Ok(42);
$value = $result->expect("This should never fail"); // 42
$error = new Err("oops");
$value = $error->expect("Critical error"); // throws UnwrapException with message "Critical error"Get the error value with a custom error message:
$error = new Err("not found");
$value = $error->expectErr("Expected error not found"); // "not found"
$success = new Ok(42);
$value = $success->expectErr("Expected an error"); // throws UnwrapException with message "Expected an error"Get the success value without safety checks (use with caution):
// WARNING: Only use when you are absolutely certain the Result is Ok
// Otherwise, it will cause undefined behavior
$result = new Ok(42);
$value = $result->unwrapUnchecked(); // 42Get the error value without safety checks (use with caution):
// WARNING: Only use when you are absolutely certain the Result is Err
// Otherwise, it will cause undefined behavior
$error = new Err("error");
$value = $error->unwrapErrUnchecked(); // "error"class UserAuthenticator {
public function authenticate(string $username, string $password): Result {
$user = $this->findUser($username);
if ($user === null) {
return new Err("User not found");
}
return password_verify($password, $user->password)
? new Ok($user)
: new Err("Invalid password");
}
}
// Usage
$auth = new UserAuthenticator();
$result = $auth->authenticate("john", "password123")
->map(fn($user) => $user->toArray())
->mapErr(fn($error) => ["error" => $error]);class ApiClient {
public function fetchData(string $url): Result {
try {
$response = file_get_contents($url);
$data = json_decode($response, true);
return $data === null
? new Err("Invalid JSON response")
: new Ok($data);
} catch (Throwable $e) {
return new Err($e->getMessage());
}
}
}
// Usage
$client = new ApiClient();
$result = $client->fetchData("https://api.example.com/data")
->andThen(function($data) {
return isset($data['items'])
? new Ok($data['items'])
: new Err("Missing items in response");
})
->map(fn($items) => array_map(fn($item) => $item['name'], $items));Contributions are welcome! Please feel free to submit a Pull Request. Make sure to read the contributing guidelines before submitting your PR.
This project is licensed under the MIT License - see the LICENSE file for details.