PHPackages                             hypothesisphp/result - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. hypothesisphp/result

ActiveLibrary

hypothesisphp/result
====================

Type-safe error handling for PHP — chain operations that can fail without exceptions.

v1.0.0(1mo ago)11↑2900%MITPHP &gt;=8.2

Since Jul 17Compare

[ Source](https://github.com/HypothesisPHP/Result)[ Packagist](https://packagist.org/packages/hypothesisphp/result)[ Docs](https://github.com/HypothesisPHP/Result)[ RSS](/packages/hypothesisphp-result/feed)WikiDiscussions Synced 1mo ago

READMEChangelogDependencies (4)Versions (2)Used By (0)

Result
======

[](#result)

Type-safe error handling for PHP — chain operations that can fail without exceptions.

Installation
------------

[](#installation)

```
composer require hypothesisphp/result
```

**Requirements:** PHP 8.2+, zero dependencies.

Quick Start
-----------

[](#quick-start)

```
use Result\Result;
use Result\Success;
use Result\Failure;

// Create results
$success = Result::success('hello');
$failure = Result::failure('something went wrong');

// Chain operations
$result = Result::success(5)
    ->map(fn (int $v) => $v * 2)
    ->filter(fn (int $v) => $v > 5, 'too small')
    ->map(fn (int $v) => $v + 1);

// Unwrap safely
$value = $result->unwrap();           // 11
$value = $result->unwrapOr(0);        // 11
$value = $failure->unwrapOr('default'); // 'default'

// Pattern match
$message = $result->match(
    onSuccess: fn (int $v) => "Result: {$v}",
    onFailure: fn (string $e) => "Error: {$e}",
);
```

Why Result?
-----------

[](#why-result)

Exceptions are unpredictable — they break control flow, hide in call stacks, and make error handling implicit. `Result` makes errors explicit, composable, and type-safe.

**Before (exceptions):**

```
try {
    $user = $userService->find($id);
    $profile = $profileService->load($user);
    echo $profile->name;
} catch (UserNotFoundException $e) {
    // handle
} catch (ProfileException $e) {
    // handle
}
```

**After (Result):**

```
$userService->find($id)
    ->flatMap(fn (User $u) => $profileService->load($u))
    ->match(
        onSuccess: fn (Profile $p) => echo $p->name,
        onFailure: fn (string $e) => handle($e),
    );
```

Features
--------

[](#features)

FeatureDescription**Immutable**All operations return new instances — original never changes**Composable**Chain `map`, `flatMap`, `filter`, `tap`, `or`, `and`**Pattern Match**`match()` with exhaustive success/failure handling**Collect**Aggregate multiple results with strategies**Try/Catch**Wrap throwing code into safe Results**Fiber Support**`tryAsync()` for Fiber-based async operations**PSR-3 Logger**Auto-log failures while preserving the chain**Serialization**Convert Results to/from arrays and JSON**Retry**Built-in retry with configurable attempts and delay**Timeout**Execute with timeout protection**PHPDoc Generics**Full `@template` annotations for static analysisCore Methods
------------

[](#core-methods)

### Creating Results

[](#creating-results)

```
Result::success($value);                    // Success
Result::failure($error);                    // Failure
Result::fromNullable($value, $error);       // null → Failure
Result::fromCondition($bool, $value, $error); // bool → Success/Failure
Result::try(fn () => risky());              // Throwable → Failure
Result::tryCatch(fn () => risky(), \InvalidArgumentException::class);
Result::tryAsync(fn () => fiberWork());     // Fiber-aware
```

### Transforming

[](#transforming)

```
$result->map(fn ($v) => transform($v));     // Transform success value
$result->mapError(fn ($e) => wrap($e));     // Transform error value
$result->flatMap(fn ($v) => validate($v));  // Chain Result-returning operations
$result->filter(fn ($v) => $v > 0, 'err'); // Conditional success
```

### Extracting

[](#extracting)

```
$result->unwrap();                          // Value or throw
$result->unwrapOr('default');               // Value or default
$result->unwrapOrElse(fn ($e) => calc());  // Value or compute from error
$result->unwrapOrNull();                    // Value or null
$result->match(onSuccess: $fn1, onFailure: $fn2); // Pattern match
```

### Side Effects

[](#side-effects)

```
$result->tap(fn ($v) => log($v));          // Inspect without changing
$result->tapError(fn ($e) => log($e));     // Inspect errors
$result->ifSuccess(fn ($v) => notify());   // Execute on success
$result->ifFailure(fn ($e) => alert());    // Execute on failure
```

### Combining

[](#combining)

```
$result->orElse(fn () => fallback());       // Recovery from failure
$result->or(fn () => alternative());        // Try alternative
$result->and($otherResult);                 // Chain to next result

// Batch operations
Result::collect([$r1, $r2, $r3]);           // All succeed or first failure
Result::collectWith($results, CollectStrategy::ALL_FAILURES);
Result::collectAssoc(['name' => $r1, 'age' => $r2]);
Result::all([$r1, $r2]);                    // Collect ALL errors on failure
Result::race([fn () => try1(), fn () => try2()]); // First success wins
Result::sequence([fn () => step1(), fn () => step2()]);
Result::partition($results);                // {successes: [...], failures: [...]}
```

Helper Functions
----------------

[](#helper-functions)

```
use function Result\success;
use function Result\failure;
use function Result\try_;
use function Result\from_nullable;
use function Result\from_condition;

$result = success(42);
$result = try_(fn () => json_decode($json, true));
$result = from_nullable($user, 'User not found');
```

Collect Strategies
------------------

[](#collect-strategies)

```
use Result\CollectStrategy;

// Stop at first failure (default)
Result::collectWith($results, CollectStrategy::FIRST_FAILURE);

// Collect ALL failures
Result::collectWith($results, CollectStrategy::ALL_FAILURES);

// Return only successes, ignore failures
Result::collectWith($results, CollectStrategy::BEST_EFFORT);
```

ResultCollector
---------------

[](#resultcollector)

Fluent batch aggregation with statistics:

```
use Result\Collectors\ResultCollector;

$collector = (new ResultCollector())
    ->add(Result::success(1))
    ->add(Result::failure('err'))
    ->add(Result::success(3));

$collector->count();         // 3
$collector->successCount();  // 2
$collector->failureCount();  // 1
$collector->successRatio();  // 0.67
$collector->allSucceeded();  // false
$collector->anyFailed();     // true

$collector->collect();       // Failure (first failure)
$collector->collectAll();    // Failure (all errors)
$collector->collectBestEffort(); // Success([1, 3])
$collector->partition();     // {successes: [1, 3], failures: ['err']}
```

PSR-3 Logger Integration
------------------------

[](#psr-3-logger-integration)

```
use Result\Interop\PsrResultLoggerBridge;

$bridge = new PsrResultLoggerBridge($logger, 'error');

$result = $userService->find($id);
$bridge->logFailure($result, 'User lookup failed: {error}');

// Or unwrap with automatic logging
$user = $bridge->unwrapOrLog($result, 'Critical: user not found');
```

Serialization
-------------

[](#serialization)

```
use Result\Interop\ArrayResultSerializer;

$serializer = new ArrayResultSerializer();

// To array
$array = $serializer->serialize($result);
// ['status' => 'success', 'value' => 42]

// From array
$result = $serializer->deserialize($array);

// JSON support
$json = $serializer->toJson($result);
$result = $serializer->fromJson($json);
```

Async / Fiber Support
---------------------

[](#async--fiber-support)

```
use Result\Interop\AsyncResultBridge;

// Execute in Fiber
$result = AsyncResultBridge::fromFiber(fn () => heavyWork());

// Concurrent execution
$result = AsyncResultBridge::concurrent([
    fn () => Result::success(fetchA()),
    fn () => Result::success(fetchB()),
]);

// With timeout
$result = AsyncResultBridge::withTimeout(fn () => slow(), 5000);

// With retry
$result = AsyncResultBridge::withRetry(
    fn () => unreliable(),
    maxRetries: 3,
    delayMs: 100,
);
```

API Reference
-------------

[](#api-reference)

Full API documentation: [docs/api-reference.md](docs/api-reference.md)

Requirements
------------

[](#requirements)

- PHP 8.2 or higher
- No external dependencies

Testing
-------

[](#testing)

```
composer test              # Run tests
composer test:coverage     # Run with HTML coverage
composer analyse           # PHPStan level 9
composer psalm             # Psalm level 1
composer infection         # Mutation testing
composer ci                # Full CI pipeline
```

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.

License
-------

[](#license)

The MIT License (MIT). Please see [LICENSE](LICENSE) for more information.

###  Health Score

38

—

LowBetter than 82% of packages

Maintenance90

Actively maintained with recent releases

Popularity4

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity46

Maturing project, gaining track record

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

46d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/155282015?v=4)[Fitri HY](/maintainers/fitri-hy)[@fitri-hy](https://github.com/fitri-hy)

---

Tags

phpresultfunctionalerror handlingmonadtype-safeeither

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan, Psalm

Type Coverage Yes

### Embed Badge

![Health badge](/badges/hypothesisphp-result/health.svg)

```
[![Health](https://phpackages.com/badges/hypothesisphp-result/health.svg)](https://phpackages.com/packages/hypothesisphp-result)
```

###  Alternatives

[mpetrovich/dash

A functional programming library for PHP. Inspired by Underscore, Lodash, and Ramda.

10430.7k1](/packages/mpetrovich-dash)[transprime-research/piper

PHP Pipe method execution with values from chained method executions

174.7k2](/packages/transprime-research-piper)[patricksavalle/slim-rest-api

Production-grade REST-API App-class for PHP SLIM, in production on https://zaplog.pro (https://api.zaplog.pro/v1)

101.4k](/packages/patricksavalle-slim-rest-api)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
