Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PHPでResult型を なるべく使いやすい形で実装する

Avatar for higaki higaki
November 07, 2025
86

PHPでResult型を なるべく使いやすい形で実装する

Avatar for higaki

higaki

November 07, 2025
Tweet

Transcript

  1. fn divide(a: f64, b: f64) -> Result<f64, String> { if

    b == 0.0 { Err("ゼロで割ることはできません ".to_string()) } else { Ok(a / b) } } // 呼び出し側 let result = divide(10.0, 0.0); if result.is_ok() { println!("結果: {:?}", result.unwrap()); } else { println!("エラー: {:?}", result.unwrap_err()); } /** * @return Result<float, string> */ function divide(float $a, float $b): Result { if ($b == 0.0) { return new Err("ゼロで割ることはできません "); } return new Ok($a / $b); } // 呼び出し側 $result = divide(10.0, 0.0); if ($result->isOk()) { echo "結果: " . $result->unwrap() . "\n"; } else { echo "エラー: " . $result->unwrapErr() . "\n"; } Rust PHP(僕の実装)
  2. fn divide(a: f64, b: f64) -> Result<f64, String> { if

    b == 0.0 { Err("ゼロで割ることはできません ".to_string()) } else { Ok(a / b) } } // 呼び出し側 let result = divide(10.0, 0.0); if result.is_ok() { println!("結果: {:?}", result.unwrap()); } else { println!("エラー: {:?}", result.unwrap_err()); } /** * @return Result<float, string> */ function divide(float $a, float $b): Result { if ($b == 0.0) { return new Err("ゼロで割ることはできません "); } return new Ok($a / $b); } // 呼び出し側 $result = divide(10.0, 0.0); if ($result->isOk()) { echo "結果: " . $result->unwrap() . "\n"; } else { echo "エラー: " . $result->unwrapErr() . "\n"; } Rust PHP(僕の実装)
  3. fn divide(a: f64, b: f64) -> Result<f64, String> { if

    b == 0.0 { Err("ゼロで割ることはできません ".to_string()) } else { Ok(a / b) } } // 呼び出し側 let result = divide(10.0, 0.0); if result.is_ok() { println!("結果: {:?}", result.unwrap()); } else { println!("エラー: {:?}", result.unwrap_err()); } /** * @return Result<float, string> */ function divide(float $a, float $b): Result { if ($b == 0.0) { return new Err("ゼロで割ることはできません "); } return new Ok($a / $b); } // 呼び出し側 $result = divide(10.0, 0.0); if ($result->isOk()) { echo "結果: " . $result->unwrap() . "\n"; } else { echo "エラー: " . $result->unwrapErr() . "\n"; } Rust PHP(僕の実装)
  4. function completePayment(ProcessingPayment $payment): CompletedPayment { if ($payment->amount <= 0) {

    throw new PaymentAmountRuleError("Payment amount must be positive"); } return new CompletedPayment($payment->amount); } // 呼び出し側 try { $result = completePayment($payment); // ... 支払い完了時の処理 } catch (PaymentAmountRuleError $e) { // ... 支払金エラー時の処理 }
  5. function completePayment(ProcessingPayment $payment): CompletedPayment { if ($payment->amount <= 0) {

    throw new PaymentAmountRuleError("Payment amount must be positive"); } return new CompletedPayment($payment->amount); } // 呼び出し側 try { $result = completePayment($payment); // ... 支払い完了時の処理 } catch (PaymentAmountRuleError $e) { // ... 支払金エラー時の処理 }
  6. function completePayment(ProcessingPayment $payment): ?CompletedPayment { if ($payment->amount <= 0) {

    return null; } return new CompletedPayment($payment->amount); } // 呼び出し側 $result = completePayment($payment); if ($result === null) { // ... 支払金エラー時の処理 } // ... 支払い完了時の処理
  7. function completePayment(ProcessingPayment $payment): ?CompletedPayment { if ($payment->amount <= 0) {

    return null; } return new CompletedPayment($payment->amount); } // 呼び出し側 $result = completePayment($payment); if ($result === null) { // ... 支払金エラー時の処理 } // ... 支払い完了時の処理
  8. function completePayment(ProcessingPayment $payment): CompletedPayment|PaymentAmountRuleError { if ($payment->amount <= 0) {

    return new PaymentAmountRuleError("Payment amount must be positive"); } return new CompletedPayment($payment->amount); } // 呼び出し側 $result = completePayment($payment); if ($result instanceof PaymentAmountRuleError) { // ... 支払金エラー時の処理 } // ... 支払い完了時の処理
  9. function completePayment(ProcessingPayment $payment): CompletedPayment|PaymentAmountRuleError { if ($payment->amount <= 0) {

    return new PaymentAmountRuleError("Payment amount must be positive"); } return new CompletedPayment($payment->amount); } // 呼び出し側 $result = completePayment($payment); if ($result instanceof PaymentAmountRuleError) { // ... 支払金エラー時の処理 } // ... 支払い完了時の処理
  10. /** * @return Result<CompletedPayment, PaymentAmountRuleError> */ function completePayment(ProcessingPayment $payment): Result

    { if ($payment->amount <= 0) { return new Err(new PaymentAmountRuleError("Payment amount must be positive")); } return new Ok(new CompletedPayment($payment->amount)); } // 呼び出し側 $result = completePayment($payment); if ($result->isErr()) { return match (true) { $result->unwrapErr() instanceof PaymentAmountRuleError::class => // ... 支払金エラー時の処理 } } // ... 支払い完了時の処理
  11. /** * @return Result<CompletedPayment, PaymentAmountRuleError> */ function completePayment(ProcessingPayment $payment): Result

    { if ($payment->amount <= 0) { return new Err(new PaymentAmountRuleError("Payment amount must be positive")); } return new Ok(new CompletedPayment($payment->amount)); } // 呼び出し側 $result = completePayment($payment); if ($result->isErr()) { return match (true) { $result->unwrapErr() instanceof PaymentAmountRuleError::class => // ... 支払金エラー時の処理 } } // ... 支払い完了時の処理
  12. • PHPの静的解析ツール ◦ 未使用の変数や型の不一致などを発見してくれる ◦ @templeteでジェネリクスも使用できる • CLIで実行できる ◦ CIでチェックできる

    • レベルがある(0~10) ◦ 10になるほど型が厳密になっていく • baselineで既存のエラーを一旦無視もできる PHPStan
  13. /** * @template T * @template E */ interface Result

    { // 各関数 } /** * @template E * @implements Result<never, E> */ final readonly class Err implements Result { /** * @param E $value */ public function __construct( private mixed $value, ) {} // エラー時の関数 } /** * @template T * @implements Result<T, never> */ final readonly class Ok implements Result { /** * @param T $value */ public function __construct( private mixed $value, ) {} // 成功時の関数 }
  14. /** * @template E * @implements Result<never, E> */ final

    readonly class Err implements Result { // … public function isOk(): false { return false; } } /** * @template T * @implements Result<T, never> */ final readonly class Ok implements Result { // … public function isOk(): true { return true; } } /** * @template T * @template E */ interface Result { public function isOk(): bool; }
  15. /** * @template E * @implements Result<never, E> */ final

    readonly class Err implements Result { // … public function isErr(): true { return true; } } /** * @template T * @implements Result<T, never> */ final readonly class Ok implements Result { // … public function isErr(): false { return false; } } /** * @template T * @template E */ interface Result { // … public function isErr(): bool; }
  16. /** * @template E * @implements Result<never, E> */ final

    readonly class Err implements Result { // … } /** * @template T * @implements Result<T, never> */ final readonly class Ok implements Result { // … /** * @return T */ public function unwrap() { return $this->value; } } /** * @template T * @template E */ interface Result { // … }
  17. /** * @template E * @implements Result<never, E> */ final

    readonly class Err implements Result { // ... /** * @return E */ public function unwrapErr() { return $this->value; } } /** * @template T * @implements Result<T, never> */ final readonly class Ok implements Result { // … } /** * @template T * @template E */ interface Result { // … }
  18. /** * @template E * @implements Result<never, E> */ final

    readonly class Err implements Result { // ... /** * @template D * @param D $default * @return D */ public function unwrapOr(mixed $default) { return $default; } } /** * @template T * @implements Result<T, never> */ final readonly class Ok implements Result { // ... /** * @template D * @param D $default * @return T */ public function unwrapOr(mixed $default) { return $this->value; } } /** * @template T * @template E */ interface Result { // ... /** * @template D * @param D $default * @return ($this is Result<T, E> ? T|D : ($this is Result<never, E> ? D : T)) */ public function unwrapOr(mixed $default); }
  19. /** * @return Result<CompletedPayment, PaymentAmountRuleError> */ function completePayment(ProcessingPayment $payment): Result

    { if ($payment->amount <= 0) { return new Err(new PaymentAmountRuleError("Payment amount must be positive")); } return new Ok(new CompletedPayment($payment->amount)); } // 呼び出し側 $result = completePayment($payment); if ($result->isErr()) { return match (true) { $result->unwrapErr() instanceof PaymentAmountRuleError::class => // ... 支払金エラー時の処理 } } // ... 支払い完了時の処理 使い方
  20. /** * @return Result<CompletedPayment, PaymentAmountRuleError> */ function completePayment(ProcessingPayment $payment): Result

    { if ($payment->amount <= 0) { return new Err(new PaymentAmountRuleError("Payment amount must be positive")); } return new Ok(new CompletedPayment($payment->amount)); } // 呼び出し側 $result = completePayment($payment); if ($result->isErr()) { return match (true) { $result->unwrapErr() instanceof PaymentAmountRuleError::class => // ... 支払金エラー時の処理 } } // ... 支払い完了時の処理 使い方
  21. エラーが一つしか返せない ❌ 一つのエラーしか返却できない 失敗時のジェネリクスで @templateを使用 ⭕ 複数のエラーを返却できる 失敗時のジェネリクスで @template-covariantを使用 /**

    * @template E * @implements Result<never, E> */ final readonly class Err implements Result /** * @template-covariant E * @implements Result<never, E> */ final readonly class Err implements Result
  22. エラーが一つしか返せない @template 非変 @template-contravariant 反変 引用:PHPDocを使ったPHPのジェネリクス[https://www.phper.ninja/entry/2020/03/01/054833] 引数の型は反変(contravariant)でなくてはならない 戻り値は共変(covariant)でなくてはならない @template-covariant 共変

    引用: PHPマニュアル[https://www.php.net/manual/ja/language.oop5.variance.php] 共変性とは、子クラスのメソッドが、親クラスの戻り値よりも、より特定の、狭い型を返すことを許すことです。 反変性とは、親クラスのものよりも、より抽象的な、広い型を引数に指定することを許すものです。
  23. 型のNarrowingできてなかった ❌ 型のNarrowingできない ⭕ 型のNarrowingできる interface Result { public function

    isOk(): bool; } interface Result { /** * @phpstan-assert-if-true Ok<T> $this * @phpstan-assert-if-false Err<E> $this */ public function isOk(): bool; }
  24. IDEがメソッドを補完してくれない 推論してくれない interface Result { public function isOk(): bool; public

    function isErr(): bool; public function unwrapOr(mixed $default); } PHPDocは 省略してます
  25. IDEがメソッドを補完してくれない 推論してくれない interface Result { public function isOk(): bool; public

    function isErr(): bool; public function unwrapOr(mixed $default); } PHPDocは 省略してます
  26. IDEがメソッドを補完してくれない 推論してくれない 推論してくれる interface Result { public function isOk(): bool;

    public function isErr(): bool; public function unwrapOr(mixed $default); } interface Result { public function isOk(): bool; public function isErr(): bool; public function unwrap(); public function unwrapErr(); public function unwrapOr(mixed $default); } PHPDocは 省略してます PHPDocは 省略してます
  27. /** * @template E * @implements Result<never, E> */ final

    readonly class Err implements Result { // … } /** * @template T * @implements Result<T, never> */ final readonly class Ok implements Result { // … /** * @return T */ public function unwrap() { return $this->value; } } /** * @template T * @template E */ interface Result { // … }
  28. /** * @template E * @implements Result<never, E> */ final

    readonly class Err implements Result { // … /** * @return never */ public function unwrap() { throw new LogicException('called Result->unwrap() on an err value'); } } /** * @template T * @implements Result<T, never> */ final readonly class Ok implements Result { // … /** * @return T */ public function unwrap() { return $this->value; } } /** * @template T * @template E */ interface Result { // … public function unwrap() }
  29. /** * @template E * @implements Result<never, E> */ final

    readonly class Err implements Result { // ... /** * @return E */ public function unwrapErr() { return $this->value; } } /** * @template T * @implements Result<T, never> */ final readonly class Ok implements Result { // … } /** * @template T * @template E */ interface Result { // … }
  30. /** * @template E * @implements Result<never, E> */ final

    readonly class Err implements Result { // ... /** * @return E */ public function unwrapErr() { return $this->value; } } /** * @template T * @implements Result<T, never> */ final readonly class Ok implements Result { // … /** * @return never */ public function unwrapErr() { throw new LogicException('called Result->unwrapErr() on an ok value'); } } /** * @template T * @template E */ interface Result { // … public function unwrapErr() }
  31. IDEがメソッドを補完してくれない いい感じにしたい! interface Result { // … /** * @return

    ($this is Result<T, never> ? T : never) */ public function unwrap(); } Okの時 Tが返却される
  32. IDEがメソッドを補完してくれない いい感じにしたい! interface Result { // … /** * @return

    ($this is Result<T, never> ? T : never) */ public function unwrap(); } Ok以外の時 neverが返却される (throwされる)
  33. IDEがメソッドを補完してくれない interface Result { // … /** * @return ($this

    is Result<T, never> ? T : never) */ public function unwrap(); }
  34. IDEがメソッドを補完してくれない interface Result { // … /** * @return ($this

    is Result<T, never> ? T : LogicException&never) */ public function unwrap(); }
  35. 自己紹介 ひがき 愛媛 → 兵庫 → 福岡 → 東京 福岡は約2年住んでました!!

    大好きな街!!! カンファレンス初参加は 「PHPカンファレンス福岡2023」 人生変わりました(本当に!!)