承接 valbeat/result 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

valbeat/result

Composer 安装命令:

composer require valbeat/result

包简介

A Result type implementation for PHP inspired by Rust

README 文档

README

Packagist Version codecov

A Result type implementation for PHP inspired by Rust's Result<T, E> type.

This library provides a robust way to handle operations that might fail, without relying on exceptions. It encourages explicit error handling by making the error case part of the return type, so failures are visible in function signatures instead of being hidden control flow.

Installation

composer require valbeat/result

Requirements

  • PHP 8.3 or higher (tested on PHP 8.3, 8.4 and 8.5)
  • Composer

Basic Usage

use Valbeat\Result\Ok;
use Valbeat\Result\Err;

// Creating Results
$success = new Ok(42);
$failure = new Err("Something went wrong");

// Pattern matching with the match() method
$message = $success->match(
    ok: fn($value) => "Success: $value",
    err: fn($error) => "Error: $error"
);
echo $message; // "Success: 42"

// Checking if a Result is Ok or Err
if ($success->isOk()) {
    echo "Operation succeeded!";
}

if ($failure->isErr()) {
    echo "Operation failed!";
}

// Unwrapping values (throws exception on error)
$value = $success->unwrap(); // 42
// $failure->unwrap(); // throws UnwrapException

// Safe unwrapping with default values
$value = $failure->unwrapOr(0); // 0
$value = $failure->unwrapOrElse(fn($err) => strlen($err)); // 20

Advanced Usage

Transforming Results

// Map over success values
$result = (new Ok(5))
    ->map(fn($x) => $x * 2)
    ->map(fn($x) => $x + 1);
echo $result->unwrap(); // 11

// Map over error values
$result = (new Err("error"))
    ->mapErr(fn($e) => strtoupper($e));
echo $result->unwrapErr(); // "ERROR"

Chaining Operations

// Chain operations that might fail
$result = (new Ok(10))
    ->andThen(fn($x) => $x > 5 ? new Ok($x * 2) : new Err("Too small"))
    ->andThen(fn($x) => new Ok($x + 5));
echo $result->unwrap(); // 25

// Short-circuit on first error
$result = (new Ok(2))
    ->andThen(fn($x) => $x > 5 ? new Ok($x * 2) : new Err("Too small"));
echo $result->unwrapErr(); // "Too small"

Each step in a chain may fail with a different error type. The error types are composed into a union, so PHPStan tracks every error the chain can produce:

final class ValidationError {}
final class NotFoundError {}

/** @return Result<int, ValidationError> */
function validateUserId(string $raw): Result
{
    return ctype_digit($raw) ? new Ok((int) $raw) : new Err(new ValidationError());
}

/** @return Result<string, NotFoundError> */
function findUserNameById(int $id): Result
{
    return $id === 42 ? new Ok('Alice') : new Err(new NotFoundError());
}

// PHPStan infers Result<string, ValidationError|NotFoundError>
$userName = validateUserId('42')->andThen(findUserNameById(...));
echo $userName->unwrap(); // "Alice"

Combining Results

// Use first Ok value
$result = (new Err("first error"))
    ->or(new Err("second error"))
    ->or(new Ok(42));
echo $result->unwrap(); // 42

// Use first Ok or call function
$result = (new Err("error"))
    ->orElse(fn($e) => new Ok(strlen($e)));
echo $result->unwrap(); // 5

Side Effects

// Inspect values without consuming the Result
$result = (new Ok(42))
    ->inspect(fn($x) => print("Value is: $x\n"))
    ->map(fn($x) => $x * 2);

// Inspect errors
$result = (new Err("oops"))
    ->inspectErr(fn($e) => error_log("Error occurred: $e"));

Helpers (Results class)

use Valbeat\Result\Results;

// Wrap exception-throwing code: Ok on success, Err<Throwable> on throw
$result = Results::try(fn() => json_decode($raw, flags: JSON_THROW_ON_ERROR));

// Combine many Results: Ok with all values, or the first Err
$result = Results::combine([new Ok(1), new Ok(2), new Ok(3)]);
echo implode(',', $result->unwrap()); // "1,2,3"

// Flatten a nested Result<Result<T, E2>, E1> into Result<T, E1|E2>
$result = Results::flatten(new Ok(new Ok(42)));
echo $result->unwrap(); // 42

Type Safety

This library is designed to be used with PHPStan at level max and leans on several of its generics features:

  • Sealed interfaceResult is annotated with @phpstan-sealed Ok|Err, so PHPStan knows Ok and Err are the only implementations. A match (true) over instanceof checks is recognized as exhaustive, and the else branch of an instanceof Ok check narrows to Err. Note that instanceof narrowing loses the type arguments (a known PHPStan limitation: Result<int, E> narrows to plain Ok, so unwrap() becomes mixed), so use instanceof in a match (true) purely for exhaustiveness. When you also need the values, the match() method handles both cases and keeps T/E (it requires both an ok and an err arm), or narrow with isOk()/isErr(). These two goals are a trade-off in PHPStan 2.2.2: enforced exhaustiveness — where adding a new Result variant would turn every unhandled site into an analysis error — comes only from instanceof in a match (true), which is exactly the form that drops the type arguments. The match() method and isOk()/isErr() keep the generics but are not checked against variant additions (isOk()/isErr() arms in a match (true) also need a default). So per call site you currently pick one: enforced exhaustiveness or preserved generics. (In practice Result is fixed at Ok|Err, so the match() method covering both is total for all real cases.)
  • Covariant type parametersT and E are declared @template-covariant, so Ok<T> (which is Result<T, never>) and Err<E> (which is Result<never, E>) are assignable to any Result<T, E>. A function declared to return Result<User, DbError> can simply return new Ok($user);.
  • Error-type compositionandThen()/and() widen the error channel to E|F and orElse()/or() widen the success channel to T|U, so chains that mix failure types stay precisely typed. (This deliberately diverges from Rust, whose and/or family keeps the other channel's type fixed.)
  • Type narrowingisOk()/isErr() narrow $result to Ok<T>/Err<E> via @phpstan-assert-if-true; unwrap()/unwrapErr() use conditional return types (never on the impossible side), and unwrapOr()/unwrapOrElse() resolve to T on Ok and to the default's type on Err.
  • Exhaustive error matching — when the error type E is a native enum or a @phpstan-sealed union, the error value can be matched exhaustively (over the enum cases, or instanceof arms for a sealed union) and PHPStan enforces it — a missing case becomes an analysis error. Reach the error through isErr() + unwrapErr(), or the match() method's err arm; narrowing with instanceof Err drops E to mixed and loses the enum/sealed type (the same instanceof limitation as above). As long as the error classes are themselves non-generic, their own instanceof checks have no type arguments to lose (unlike the generic Ok/Err).
  • Precise concrete receivers — when the receiver is statically Ok<T> or Err<E>, no-op methods keep their exact type ($ok->orElse(...) stays Ok<T>, $err->andThen(...) stays Err<E>) instead of widening to a union.

Note: because the templates are covariant, PHPStan preserves constant value types (new Ok(10) is Ok<10>, not Ok<int>). Type a variable or parameter as int if you want the widened type.

API Reference

Result Methods

All Result types (both Ok and Err) implement these methods:

Type Checking

  • isOk(): bool - Returns true if the Result is Ok
  • isOkAnd(callable $fn): bool - Returns true if the Result is Ok and the predicate returns true
  • isErr(): bool - Returns true if the Result is Err
  • isErrAnd(callable $fn): bool - Returns true if the Result is Err and the predicate returns true

Value Extraction

  • unwrap(): mixed - Returns the success value or throws UnwrapException (extends LogicException)
  • unwrapErr(): mixed - Returns the error value or throws UnwrapException (extends LogicException)
  • expect(string $message): mixed - Returns the success value or throws UnwrapException with the given message and a summary of the error value
  • expectErr(string $message): mixed - Returns the error value or throws UnwrapException with the given message and a summary of the success value
  • unwrapOr(mixed $default): mixed - Returns the success value or a default
  • unwrapOrElse(callable $fn): mixed - Returns the success value or computes it from the error

Transformation

  • map(callable $fn): Result - Maps a Result<T, E> to Result<U, E> by applying a function to the success value
  • mapErr(callable $fn): Result - Maps a Result<T, E> to Result<T, F> by applying a function to the error value
  • mapOr(mixed $default, callable $fn): mixed - Maps the success value or returns a default
  • mapOrElse(callable $defaultFn, callable $fn): mixed - Maps the success value or computes a default from the error

Combination

  • and(Result $res): Result - Returns the second Result if the first is Ok, otherwise returns the first Err
  • andThen(callable $fn): Result - Chains another operation that returns a Result
  • or(Result $res): Result - Returns the first Ok or the second Result if the first is Err
  • orElse(callable $fn): Result - Returns the first Ok or calls a function with the error to produce a Result

Side Effects

  • inspect(callable $fn): Result - Calls a function with the success value if Ok
  • inspectErr(callable $fn): Result - Calls a function with the error value if Err

Pattern Matching

  • match(callable $ok, callable $err): mixed - Pattern match on the Result

Results Helpers (static)

  • Results::try(callable $fn): Result - Runs a callable and wraps the outcome: Ok with the return value, or Err with the thrown Throwable
  • Results::combine(iterable $results): Result - Combines iterable<Result<T, E>> into Result<list<T>, E>, short-circuiting on the first Err
  • Results::flatten(Result $result): Result - Flattens Result<Result<T, E2>, E1> into Result<T, E1|E2>

License

This project is licensed under the MIT License - see the LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

valbeat/result 适用场景与选型建议

valbeat/result 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 811 次下载、GitHub Stars 达 7, 最近一次更新时间为 2025 年 07 月 15 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「type」 「functional」 「monad」 「error-handling」 「result」 「rust」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 valbeat/result 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 valbeat/result 我们能提供哪些服务?
定制开发 / 二次开发

基于 valbeat/result 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 811
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 8
  • 点击次数: 18
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 7
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-07-15