PHPackages                             jsoizo/php-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. [Utility &amp; Helpers](/categories/utility)
4. /
5. jsoizo/php-result

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

jsoizo/php-result
=================

A type-safe Result type for PHP 8.1+ with PHPStan support.

v0.2.1(4mo ago)1216[3 PRs](https://github.com/jsoizo/php-result/pulls)MITPHPPHP ^8.1CI passing

Since Jan 18Pushed 2w agoCompare

[ Source](https://github.com/jsoizo/php-result)[ Packagist](https://packagist.org/packages/jsoizo/php-result)[ Docs](https://github.com/jsoizo/php-result)[ RSS](/packages/jsoizo-php-result/feed)WikiDiscussions main Synced today

READMEChangelog (3)Dependencies (6)Versions (15)Used By (0)

php-result
==========

[](#php-result)

[![Latest Version on Packagist](https://camo.githubusercontent.com/ac3192124bc9ae85b9ebb7eca7f63f1d7e43b0c8a05b4350338b13741fce2f18/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6a736f697a6f2f7068702d726573756c742e737667)](https://packagist.org/packages/jsoizo/php-result)[![CI](https://github.com/jsoizo/php-result/actions/workflows/ci.yml/badge.svg)](https://github.com/jsoizo/php-result/actions/workflows/ci.yml)[![License](https://camo.githubusercontent.com/1e433bb48f6ba4d2033cfa6731f7639e609d5b9747ba776a8c2750f1693bc0cc/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6a736f697a6f2f7068702d726573756c742e737667)](https://packagist.org/packages/jsoizo/php-result)

A type-safe Result type for PHP 8.1+ with PHPStan support.

Features
--------

[](#features)

- Zero dependencies
- PHPStan level max support
- Rich composition functions (map, flatMap, mapError)
- Inspired by functional programming Result/Either types

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

[](#installation)

```
composer require jsoizo/php-result
```

Basic Usage
-----------

[](#basic-usage)

```
use Jsoizo\Result\Result;

// Create Success/Failure
$success = Result::success(42);
$failure = Result::failure('error message');

// Transform values
$doubled = $success->map(fn($x) => $x * 2); // Success(84)

// Chain operations
$result = $success
    ->flatMap(fn($x) => $x > 0
        ? Result::success($x * 2)
        : Result::failure('must be positive'));

// Get value with default
$value = $failure->getOrElse(0); // 0

// Catch exceptions
$result = Result::catch(fn() => riskyOperation());

// Handle both cases with fold
$message = $result->fold(
    onFailure: fn($error) => "Error: {$error->getMessage()}",
    onSuccess: fn($value) => "Got: {$value}"
);

// Compose validations with flatMap
$result = validateEmail($input['email'])
    ->flatMap(fn($email) => validatePassword($input['password'])
    ->flatMap(fn($password) => createUser($email, $password)));

// Recover from failure with default value
$recovered = $failure->recover(fn($e) => 'default'); // Success('default')

// Chain fallback operations
$result = fetchFromPrimaryDb()
    ->recoverWith(fn($e) => fetchFromSecondaryDb())
    ->recoverWith(fn($e) => Result::success('cached fallback'));

// Side effects for debugging/logging
$result = validateInput($data)
    ->tap(fn($v) => logger()->info("Valid: $v"))
    ->tapError(fn($e) => logger()->error("Invalid: $e"))
    ->flatMap(fn($v) => processData($v));

// Get value as nullable
$value = $result->getOrNull(); // T|null

// Flatten nested Results
$nested = Result::success(Result::success(42));
$flat = $nested->flatten(); // Success(42)

// Monad comprehension with binding (avoids nested flatMap)
$result = Result::binding(function () use ($orderId) {
    /** @var Order $order */
    $order = yield Result::catch(fn() => $orderRepo->find($orderId));
    /** @var list $items */
    $items = yield Result::catch(fn() => $order->loadItems());
    return $items;
});
// Returns Result - short-circuits on first failure

// Accumulate errors from multiple independent validations
$result = Result::accumulate3(
    fn() => validateName($input['name']),
    fn() => validateAge($input['age']),
    fn() => validateEmail($input['email']),
    fn(string $name, int $age, string $email) => new User($name, $age, $email)
);
// All Success → Success(User(...))
// Any Failure → Failure(['Name required', 'Invalid email']) (non-empty-list of errors)
```

API
---

[](#api)

### Factory Methods

[](#factory-methods)

MethodDescription`Result::success($value)`Create a Success`Result::failure($error)`Create a Failure`Result::catch(callable $fn)`Wrap exception-throwing code`Result::binding(callable $fn)`Monad comprehension using generators`Result::accumulate2($fn1, ..., $transform)`Combine 2 Results, collecting all errors`Result::accumulate3($fn1, ..., $transform)`Combine 3 Results, collecting all errors`Result::accumulate4($fn1, ..., $transform)`Combine 4 Results, collecting all errors`Result::accumulate5($fn1, ..., $transform)`Combine 5 Results, collecting all errors`Result::accumulate6($fn1, ..., $transform)`Combine 6 Results, collecting all errors`Result::accumulate7($fn1, ..., $transform)`Combine 7 Results, collecting all errors`Result::accumulate8($fn1, ..., $transform)`Combine 8 Results, collecting all errors`Result::accumulate9($fn1, ..., $transform)`Combine 9 Results, collecting all errors### Instance Methods

[](#instance-methods)

MethodDescription`isSuccess()`Returns true if Success`isFailure()`Returns true if Failure`getOrElse($default)`Get value or default`get()`Get value or throw`getErrorOrElse($default)`Get error or default`getError()`Get error or throw ResultException`map($fn)`Transform success value`mapError($fn)`Transform error value`flatMap($fn)`Chain Result-returning operations`fold($onFailure, $onSuccess)`Handle both cases and return a value`recover($fn)`Recover from error with a value`recoverWith($fn)`Recover from error with a Result`tap($fn)`Execute side effect on success value, return same Result`tapError($fn)`Execute side effect on error value, return same Result`getOrNull()`Get success value or null`flatten()`Flatten nested `Result` into `Result`PHPStan Integration
-------------------

[](#phpstan-integration)

### Sealed Class Support

[](#sealed-class-support)

Result is marked as a sealed class using `@phpstan-sealed`. This prevents creating custom subclasses of Result outside of Success and Failure.

```
// PHPStan will report an error for unauthorized subclasses:
// "Type CustomResult is not allowed to be a subtype of Result"
class CustomResult extends Result { ... }
```

Requirements:

- PHPStan 2.1.18 or later
- No additional packages needed

See: [PHPStan Sealed Classes](https://phpstan.org/writing-php-code/phpdocs-basics#sealed-classes)

### Type Narrowing with isSuccess/isFailure

[](#type-narrowing-with-issuccessisfailure)

The `isSuccess()` and `isFailure()` methods support [PHPStan type narrowing](https://phpstan.org/writing-php-code/narrowing-types):

```
/** @param Result $result */
function handleResult(Result $result): void
{
    if ($result->isSuccess()) {
        // PHPStan knows $result is Success
        $user = $result->get();
    } else {
        // PHPStan knows $result is Failure
        $error = $result->getError();
    }
}

// Early return pattern
/** @param Result $result */
function getValue(Result $result): int
{
    if ($result->isFailure()) {
        return -1;
    }
    // PHPStan knows $result is Success
    return $result->get();
}
```

### Match Exhaustiveness Check

[](#match-exhaustiveness-check)

This library includes a custom PHPStan rule that ensures match expressions on Result types are exhaustive.

**Setup:**

The rule is automatically enabled when you use PHPStan with this library (via `composer.json` extra config). Alternatively, add to your `phpstan.neon`:

```
includes:
    - vendor/jsoizo/php-result/extension.neon
```

**What it checks:**

```
// Error: Match expression on Result type is not exhaustive. Missing: Failure.
match (true) {
    $result instanceof Success => 'success',
};

// OK: All cases covered
match (true) {
    $result instanceof Success => 'success',
    $result instanceof Failure => 'failure',
};

// OK: default covers remaining cases
match (true) {
    $result instanceof Success => 'success',
    default => 'failure',
};
```

**Note: PHPStan's `match.unhandled` error**

Even when all cases are covered, PHPStan may report `Match expression does not handle remaining value: true`. This is because PHPStan doesn't use sealed class information for match exhaustiveness.

To suppress this error, use the `@phpstan-ignore` comment:

```
/** @phpstan-ignore match.unhandled (Result is sealed: Success|Failure) */
return match (true) {
    $result instanceof Success => 'success',
    $result instanceof Failure => 'failure',
};
```

The custom rule in this library ensures exhaustiveness, so it's safe to ignore `match.unhandled` for Result types.

###  Health Score

39

—

LowBetter than 84% of packages

Maintenance86

Actively maintained with recent releases

Popularity14

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity39

Early-stage or recently created project

 Bus Factor1

Top contributor holds 98.6% of commits — single point of failure

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

Every ~9 days

Total

3

Last Release

147d ago

PHP version history (2 changes)v0.1.0PHP ^8.2

v0.2.0PHP ^8.1

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/3462087?v=4)[jsoizo](/maintainers/jsoizo)[@jsoizo](https://github.com/jsoizo)

---

Top Contributors

[![jsoizo](https://avatars.githubusercontent.com/u/3462087?v=4)](https://github.com/jsoizo "jsoizo (68 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (1 commits)")

---

Tags

PHPStanresulterror handlingfunctional-programmingtype-safe

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

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

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

###  Alternatives

[graham-campbell/result-type

An Implementation Of The Result Type

554414.2M10](/packages/graham-campbell-result-type)[symfony/type-info

Extracts PHP types information.

20069.8M270](/packages/symfony-type-info)[ergebnis/phpstan-rules

Provides rules for phpstan/phpstan.

4579.8M286](/packages/ergebnis-phpstan-rules)[php-stubs/wordpress-stubs

WordPress function and class declaration stubs for static analysis.

20416.0M413](/packages/php-stubs-wordpress-stubs)[php-stubs/woocommerce-stubs

WooCommerce function and class declaration stubs for static analysis.

953.5M103](/packages/php-stubs-woocommerce-stubs)[ejsmont-artur/php-circuit-breaker

PHP Circuit Breaker component

1691.0M4](/packages/ejsmont-artur-php-circuit-breaker)

PHPackages © 2026

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