Slide 1

Slide 1 text

PHPでResult型やってみよう @PHPカンファレンス関西2025 ひがき

Slide 2

Slide 2 text

話すこと ● Result型とは ● 各種エラーハンドリングの例 ● PHPでどう実装する?(今日は省略) ● 使いどころ

Slide 3

Slide 3 text

Result型とは??

Slide 4

Slide 4 text

Result型 とある処理が「成功」したこと、あるいは「失敗」したことを表現する 関数結果にアクセスする前に、関数が成功したか失敗したかのチェックが必要 try-catchの例外処理に頼らないエラーハンドリング PHPではデフォルトで実装されていない

Slide 5

Slide 5 text

fn divide(a: f64, b: f64) -> Result { 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 */ 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(僕の実装)

Slide 6

Slide 6 text

fn divide(a: f64, b: f64) -> Result { 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 */ 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(僕の実装)

Slide 7

Slide 7 text

fn divide(a: f64, b: f64) -> Result { 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 */ 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(僕の実装)

Slide 8

Slide 8 text

各種エラーハンドリングの例 ※説明のために愉快なコードも出てきますが🙏

Slide 9

Slide 9 text

1. try-catch 2. nullable 3. union型 4. Result 4つの例

Slide 10

Slide 10 text

1. try-catch 2. nullable 3. union型 4. Result 4つの例

Slide 11

Slide 11 text

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) { // ... 支払金エラー時の処理 }

Slide 12

Slide 12 text

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) { // ... 支払金エラー時の処理 }

Slide 13

Slide 13 text

よくあるPHPの例外処理 ● 複数のエラーパターンを例外で表現可能 ● フローが複雑になりがち(GOTOみたい) try-catch

Slide 14

Slide 14 text

1. try-catch 2. nullable 3. union型 4. Result 4つの例

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

稀によく見る ● シンプルで軽量 ● PHP 7.1〜 ● エラーの詳細情報が得られない ● 複数のエラーパターンを区別できない nullable

Slide 18

Slide 18 text

1. try-catch 2. nullable 3. union型 4. Result 4つの例

Slide 19

Slide 19 text

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) { // ... 支払金エラー時の処理 } // ... 支払い完了時の処理

Slide 20

Slide 20 text

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) { // ... 支払金エラー時の処理 } // ... 支払い完了時の処理

Slide 21

Slide 21 text

成功も失敗も同列で扱う ● 複数のエラーパターンを型で表現可能 ● PHP 8.0 〜 ● どの型が成功か失敗の判断が必要 union型

Slide 22

Slide 22 text

1. try-catch 2. nullable 3. union型 4. Result 4つの例

Slide 23

Slide 23 text

/** * @return Result */ 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 => // ... 支払金エラー時の処理 } } // ... 支払い完了時の処理

Slide 24

Slide 24 text

/** * @return Result */ 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 => // ... 支払金エラー時の処理 } } // ... 支払い完了時の処理

Slide 25

Slide 25 text

関数結果にアクセスする前に、関数が成功したか失敗したかのチェックが必要 ● 複数のエラーパターンを型で表現可能 ● 成功と失敗が明示的 ● PHPStanなどの静的解析がないと辛い Result

Slide 26

Slide 26 text

PHPでどう実装する? (本日は省略)

Slide 27

Slide 27 text

zennの記事を見てください🙏 時間の関係で本日は省略します >< https://zenn.dev/higaki/articles/my-php-result-type

Slide 28

Slide 28 text

使いどころ

Slide 29

Slide 29 text

使いどころ 🤔 そもそも我々が対処すべきエラーハンドリング ● ⭕ビジネス例外 ● ❌技術的例外 技術的例外に関しては、アプリケーションのフレームワークに対応を任せることができ、ビジネス例 外に関しては、クライアントにあらかじめ対処するコードを組み込んでおくことができます。 「PHPの例外処理の参考になる資料」 asumikam: プロダクトコードと OSSに学ぶ例外処理の選択肢 — キャッチするのか、投げっぱなしにするのか プログラマが知るべき 97のこと: 21 - 技術的例外とビジネス例外を明確に区別する

Slide 30

Slide 30 text

使いどころ 😆 ビジネスロジックに取り入れていく ● ビジネス例外の対応にResult型を取り入れる ○ 技術的例外は例外を投げっぱなす ● 予期されるエラーに対して効果的な場合は Result型でエラーハンドリングする ● 他の手法の方が適切なシステム・処理は多々ある 「Result型を取り入れない場所の参考になるブログ」 Kentaro Inomata: 鉄道指向プログラミング(の安易な利用)に反対する

Slide 31

Slide 31 text

● Takuma Kajikawa ○ try-catchを使わないエラーハンドリング!? PHPでResult型の考え方を取り 入れてみよう ● asumikam ○ プロダクトコードとOSSに学ぶ例外処理の選択肢 — キャッチするのか、投 げっぱなしにするのか ● Kentaro Inomata ○ 鉄道指向プログラミング(の安易な利用)に反対する ● プログラマが知るべき97のこと ○ 21 - 技術的例外とビジネス例外を明確に区別する 参考

Slide 32

Slide 32 text

自己紹介 ひがき 🍊PHPConference愛媛2026 実行委員長🐘 2026年9月 or 10月 愛媛県松山市 ペチコン愛媛開催予定!! LIKE:筋トレ・ポーカー 旧 新