prewk/result
prewk/result brings Rust-like Result to PHP: explicit Ok/Err values for safer, more readable error handling without exceptions. Use map/flatMap, unwrap/unwrapOr, and chain operations to handle success and failure paths cleanly in functional style.
Installation
composer require prewk/result
The package is dependency-free and requires PHP 8.0+.
Basic Usage
Import the Result class and create instances:
use Prewk\Result\Result;
$success = Result::ok("Operation succeeded");
$failure = Result::err(new \RuntimeException("Something went wrong"));
First Use Case: Safe API Response Handling
function fetchUser($id) {
if ($user = User::find($id)) {
return Result::ok($user);
}
return Result::err(new \InvalidArgumentException("User not found"));
}
$result = fetchUser(1);
$result->match(
fn($user) => "User: {$user->name}",
fn($e) => "Error: {$e->getMessage()}"
);
Chaining Results (Flat Map) Transform a successful result into another operation:
$result = fetchUser(1)
->flatMap(fn($user) => fetchPosts($user->id))
->map(fn($posts) => collect($posts)->pluck('title'));
Error Handling with match()
$result->match(
fn($data) => response()->json($data),
fn($e) => response()->json(['error' => $e->getMessage()], 400)
);
Validation Pipeline
function validateInput(array $data): Result {
return Result::ok($data)
->map(fn($d) => Validator::make($d, rules()))
->flatMap(fn($validator) => $validator->fails()
? Result::err(new \InvalidArgumentException($validator->errors()->first()))
: Result::ok($validator->validated())
);
}
Form Requests
public function rules(): array {
return ['email' => 'required|email'];
}
public function withValidator($validator) {
if ($validator->fails()) {
return Result::err(new \InvalidArgumentException($validator->errors()->first()));
}
return Result::ok($validator->validated());
}
Service Layer
class UserService {
public function createUser(array $data): Result {
return User::create($data)
? Result::ok($data)
: Result::err(new \RuntimeException("Failed to create user"));
}
}
Middleware for API Errors
public function handle($request, Closure $next) {
$response = $next($request);
if ($response->original instanceof Result && $response->original->isErr()) {
return response()->json(['error' => $response->original->unwrapErr()->getMessage()], 400);
}
return $response;
}
Unwrapping Errors Blindly
// ❌ Dangerous: Throws exception if Result is Err
$data = $result->unwrap(); // Avoid unless you're certain of success
// ✅ Safer
$data = $result->unwrapOr(null);
Overusing match()
isOk()/isErr() checks for complex logic to avoid nested callbacks.Performance with Heavy Operations
flatMap() chains operations eagerly. Use map() + unwrap() if lazy evaluation is needed.Inspecting Results
$result->isOk(); // bool
$result->isErr(); // bool
$result->unwrap(); // throws on Err
$result->unwrapErr();// throws on Ok
Logging Errors
$result->match(
fn($data) => log()->info("Success", ['data' => $data]),
fn($e) => log()->error("Failed", ['error' => $e->getMessage()])
);
Custom Error Types
class ValidationError extends \RuntimeException {}
$result = Result::err(new ValidationError("Invalid data"));
Result Decorators
trait LoggableResult {
public function tap(callable $callback): self {
$this->match($callback, $callback);
return $this;
}
}
Laravel Collections Integration
Result::ok($data)->then(fn($d) => collect($d)->keyBy('id'));
Result::unwrap() calls in a helper:
if (!$result->isOk()) {
throw $result->unwrapErr();
}
How can I help you explore Laravel packages today?