20211128のPHPに関する記事は5件です。

「例外処理」と「バリデーション」の使い分けを自分なりに理解

背景 当初の実装 以下の一連の処理を実行するプログラム 1, 「製品」リストから「注文」ボタン押下 2, 同じルートにPOST送信を実行 3, 2,のリクエストの中にorder_idを一緒に送る 4, そのidをもとに「製品」テーブルからデータを取得 5, 4,で取得した「製品」データを「カート」テーブルに格納 6, 5,の保存処理が終了すると、「カート」一覧に移動 この状況で3, での想定外処理であるリクエスト値の書き換えに対して、4, の実装時に例外処理を実装していた 例外処理にしていた理由は以下の考えによる 基本的にユーザーはここで入力処理をすることはないので、エラーが起きる可能性は非常に低い ここでエラーが発生するのは基本的に「ユーザーに悪意がある場合」のみ この「ユーザーに悪意がある場合」というのはもはや開発者の想定外の事象 開発環境 ※注意 状況をイメージできるために書いているので、正常に動くかは検証していないです。ご了承ください。 環境 PHP 8.0.4 Laravel 8.x DB 必要最低限だけ書いてます。 Userテーブル カラム名 内容 補足 id ユーザーID PK ... ... ... 製品(Product)テーブル カラム名 内容 補足 id 製品ID PK name 製品名 path 画像パス ... ... ... カート(Cart)テーブル カラム名 内容 補足 id カートID PK product_id 製品ID FK ... ... ... ※前提 userとcartはリレーション構築済み ソースコード ルート // 製品一覧表示 Route::get('/products', [App\Http\Controllers\ProductController::class, 'index'])->name('products.index'); // 製品をカートに保存 Route::post('/products', [App\Http\Controllers\ProductController::class, 'store'])->name('product.store'); コントローラ カート関連のコントローラはビューファイルをcart/index.blade.phpを表示するだけなので割愛 class ProductController extends Controller { /** * 製品一覧表示 */ public function index() { // 製品一覧情報 $products = Product::get(); return view('products.index', compact('products')); } /** * 製品をカートに保存 */ // フォームリクエストバリデーション実装 public function store(Request $request) { $validated = $request->validated(); // ユーザー情報取得 $user = User::findOrFail($validated['user_id']); // ★例外処理開始 try { // ★製品idに紐づく製品情報を取得 // Note: ココで製品IDが異常時もキャッチできるようにする $product = Product::findOrFail($validated['product_id']); // 製品をカートに保存 // Note: userとcartはリレーション構築済み $user->cart->fill([ 'product' => $product, ]); $user->cart->save(); DB::commit(); } catch (Throwable $e) { DB::rollback(); throw $e; } // カート一覧に遷移 return redirect()->route('cart.index'); } ビュー カート関連のビューファイルは本題と関係ないので割愛 ↓カート一覧 cart/index.blade.php <body> <div> @foreach ($products as $product) <form action="" method="POST"> // 画像表示 <img src= "/img/{{ $product->path }}"> // 製品名表示 <div>{{ $product->name }}</div> <div> // hiddenでリクエスト時に製品IDを一緒に送る <input type="hidden" name="productId" value="{{ $product->id }}"> <input type="submit" value="送信"> </div> </form> @endforeach </div> </body> 指摘 これに対して、レビュワーから 「リクエスト値にはバリデーションを実装しようか」 という指摘をもらった レビュワーの考えは以下の通り 「『ユーザーに悪意がある場合』というのはもはや開発者の想定外の事象」という認識は合っている ただ、これだと『ユーザーに悪意がある場合』と『システム要因によるエラー(DBの障害など)』が一緒くたになるのは問題 この時Throwableを投げてるのみで例外処理をしていた さらに、そもそも、リクエストの異常はバリデーションで防げるからこっちのが理想的 フォームリクエストバリデーションを使用すれば、次のビジネスロジック(Controller)の処理に入る前にレスポンスを返せる 特に 『ユーザーに悪意がある場合』と『システム要因によるエラー(DBの障害など)』が一緒くたになるのは問題 と 「リクエストの異常はそもそもバリデーションで防げる」 の2点については納得して修正。 だが、同時に 「じゃあ、そもそも『例外』と『バリデーション』が想定する使われ方ってなんだろ?」 と素朴な疑問が浮かんだので、メモ 今回解決したいギモン 例外 「当初実装していた例外処理の範囲ってどこまで?例外ってどういう状況?」 バリデーション 「『入力』以外のアクション(今回のような『ボタン押下』)に対してバリデーションを使うべきか」 内容 例外 概要 コーディングを支える技術 ~成り立ちから学ぶプログラミング作法 プログラムも失敗します。 … 大惨事になる前に気付くことができるようにプログラミング言語にもガス漏れ検知器のような「失敗を伝えるしくみ」が必要なのです。 IT用語辞典 e-Words プログラムが通常の処理では想定していない事態や事象を「例外」(exception)と呼び、例外が生じた時の対応を記述したコードを例外処理という https://e-words.jp/w/例外処理.html 個人的な解釈 e-Wordsで述べられる「『想定外の処理』が実行されたときの処理」が正しそう 以下認識であながち間違いなさそう 想定できる処理:バリデーション 上記以外(=想定できない処理):例外 本題について、コーディングを支える技術 ~成り立ちから学ぶプログラミング作法には以下2つの状況を「例外を投げる状況」としている 関数呼び出し時に不足している場合 配列の範囲外を取得しようとした場合 ただ同書は言語ごとに例外の扱いが違う(返り値が違う、など)ことから 「何が『例外的状況か』は正解がない」 と結論付けている 結論 「当初実装していた例外処理の範囲ってどこまで?例外ってどういう状況?」 ? ? ぶっちゃけ「コレ!」という正解はない。 しいて言うならバリデーション以外でリクエストが飛んでくる状況 バリデーション 概要 いくつか情報ソースがあったので、列挙 ISO 9000 「観察・測定・試験などの手段によって得られた客観的証拠を提示して、利害関係者が意図する用途・適用に関する要求事項が満されていることを実環境あるは模擬環境で確認すること」 ※参考 https://www.itmedia.co.jp/im/articles/1111/07/news145.html IEEE Std 610.12 「開発プロセスの途中または最後に、利用者のニーズや意図された利用法などの要求事項を満たしているかを決定するために、システム/コンポーネントを評価するプロセス」 ※参考 https://www.itmedia.co.jp/im/articles/1111/07/news145.html IT用語辞典 e-Words 記述・入力されたデータが、あらかじめ規定された条件や仕様、形式などに適合しているかどうかを検証・確認することを表す。 ※参考 https://e-words.jp/w/バリデーション.html 個人的な解釈 ISO 9000やIEEE はプロダクト全体のことを指して述べられているが、具体化して同じことが言えると思う 特に、IEEEの「利用者のニーズや意図された利用法などの要求事項を満たしているか」という点を抜粋すれば、バリデーションの対応範囲はe-Wordsが述べている「記述・入力されたデータ」のみではなさそう 以上の文献からバリデーションとは データの受け手が受け取る対象が、想定する形式のモノか否かを正否判断する という一つの解釈を導けて、送られる形式は問わない、と言えそう。 結論 「『入力』以外のアクション(今回のような『ボタン押下』)に対してバリデーションを使うべきか」 ? ? ボタン押下でリクエスト値が送られてくるので、これも一つの入力値と捉え、バリデーションの管轄内である これにより、冒頭のレビュワーの指摘である 「リクエストの異常はそもそもバリデーションで防ぐ」 というスタンスはより説得力を帯びました。 なので、「全てのエラー系を例外処理で処理」から「バリデーションも使用」という方向性に修正し // 追加 : フォームリクエストバリデーション実装 public function store(ProductStoreRequest $request) { ... ... ... } とし、製品IDの異常値をコントローラ処理に入る前にはじくようにしました。 所見 具体的事象を乗せたとしても、かなり抽象的なアプローチをしたため解釈の余地、及び至らぬ点があるかと思います。 「私は~と思う」「~な処理の仕方もあるんじゃない?」など、忌憚ないご意見あればコメントいただけると幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP で PDO を使用し SQL から取得したデータを表にして見やすくする方法

PDO を使用して、SQL から取得したデータはそのままだと、「ただの文字列。」って感じで読みやすくないですよね。 そこで取得したデータを表にして色付けとかするやり方を解説します。 内容としてはめっちゃ簡単。 しかし僕は 2 時間くらい無駄に彷徨ってたので初心者としてはあるあるな悩みかと思います! 今回は簡易的な本のブックマークアプリを作りました。 SQL からのデータの取得はもちろん SELECT を使用しています。 Before それではまずは元のコードを見てみます ↓ <?php // 1. DB接続(エラー処理追加) // 下記がDB使う時のデフォルト的な書き方。とりあえずここの try{}の部分はコピペでいい。 try{ // try とcatch はセット try{}の中にエラーがあった場合、catch でエラー $e を受け取り、exit で $e を表示する $pdo = new PDO('mysql:dbname=gs_db;charset=utf8;host=localhost','root','root'); // PDO を使えるようにするコマンド。mysql と書いているのはこれを呼び出している、他のを使う場合はそれに書き換え。 }catch(PDOException $e){ exit('DbConnectError:'.$e->getMessage()); } // 2. データ登録 SQL 作成 $stmt = $pdo->prepare("SELECT * FROM gs_bm_table"); // MySQL の SELECT 文で全てのデータを取得。INSERT の時のように変数渡したり、登録も何もしていないので、ケースにもよるが基本は bindValue も必要ない。 $status = $stmt->execute(); // 実行 // 3. データを表示 $view = ""; // 事前にこれをグローバル変数で定義 if($status==false){ // 文面 $error = $stmt->errorInfo(); //$stmt の中から errorInfo() にてエラー内容を取得 exit("QueryError:".$error[2]); //errorInfo() の中で文字列として人間が読める文面が[2]番に入っているためこれを取得。 }else{ // エラーじゃない場合。登録の時は header() で元のページにリダイレクトさせるけど、表示の場合は現状 $stmt にデータが入っているからそれを表示させるだけ。 while($result = $stmt->fetch(PDO::FETCH_ASSOC)){ //fetch() 関数はレコードを 1 行ずつ取り出すという意味。100 件データ(レコード)があったら自動で 100 件回る。そして $result に配列(FETCH_ASSOC は配列で返してくれる)で入れてくれる。 $view .= '<p>'; // $view は上にグローバル変数にて定義済みなので、「.=」で追加してる。<p> をつけると改行される。 $view .= $result["indate"].":"; //.= がないと最後のレコードしか表示されない。 $view .= $result["b_name"].":"; $view .= $result["b_url"]; $view .= '</p>'; } } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>本のブックマークアプリ</title> </head> <body> <!-- -------------ここで $view を表示してる ↓ ------------- --> <div> <div><?= $view ?></div> </div> <!-- -------------ここで $view を表示してる ↑ ------------- --> </body> </html> (自分のためでもあるので、やたらコメント多いのはご了承ください) 上記の場合の出力結果は以下の通りです。 「日時:書籍名:書籍URL」 の順に表示されています ↓ 一応 p タグで改行してはいるものの、なんだか寂しい。。 それでは次は表にしてみたり色を付けてみて見やすくしてみたいと思います ↓ After 下記が After のコードです ↓ <?php // 1. DB接続(エラー処理追加) // 下記がDB使う時のデフォルト的な書き方。とりあえずここの try{}はコピペでいい。 try{ // try とcatch はセット try{}の中にエラーがあった場合、catch でエラー $e を受け取り、exit で $e を表示する $pdo = new PDO('mysql:dbname=gs_db;charset=utf8;host=localhost','root','root'); // PDO を使えるようにするコマンド。mysql と書いているのはこれを呼び出している、他のを使う場合はそれに書き換え。 }catch(PDOException $e){ exit('DbConnectError:'.$e->getMessage()); } // 2. データ登録 SQL 作成 $stmt = $pdo->prepare("SELECT * FROM gs_bm_table"); // MySQL の SELECT 文で全てのデータを取得。INSERT の時のように変数渡したり、登録も何もしていないので、ケースにもよるが基本は bindValue も必要ない。 $status = $stmt->execute(); // 実行 // 3. データを表示 $view = ""; // 事前にこれをグローバル変数で定義 if($status==false){ // 文面 $error = $stmt->errorInfo(); //$stmt の中から errorInfo() にてエラー内容を取得 exit("QueryError:".$error[2]); //errorInfo() の中で文字列として人間が読める文面が[2]番に入っているためこれを取得。 }else{ // エラーじゃない場合。登録の時は header() で元のページにリダイレクトさせるけど、表示の場合は現状 $stmt にデータが入っているからそれを表示させるだけ。 $view .= '<table>'; $view .= '<tr>'; $view .= '<th>'."登録日時".'</th>'; $view .= '<th>'."書籍名".'</th>'; $view .= '<th>'."書籍 URL".'</th>'; $view .= '</tr>'; while($result = $stmt->fetch(PDO::FETCH_ASSOC)){ //fetch() 関数はレコードを 1 行ずつ取り出すという意味。100 件データ(レコード)があったら自動で 100 件回る。そして $result に配列(FETCH_ASSOC で配列で返してくれる)で入れてくれる。 $view .= '<tr>'; $view .= '<td>'.$result["indate"].":".'</td>'; $view .= '<td>'.$result["b_name"].":".'</td>'; $view .= '<td>'.$result["b_url"].'</td>'; $view .= '</tr>'; } $view .= '</table>'; } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>本のブックマークアプリ</title> </head> <body> <!-- -------------ここで $view を表示してる ↓ ------------- --> <div> <div><?= $view ?></div> </div> <!-- -------------ここで $view を表示してる ↑ ------------- --> <!-- -------------ここで見た目の整え ↓ ------------- --> <style> table{ border-collapse: collapse; } table th{ border: 2px solid rgb(163, 163, 163); background-color:rgba(253, 196, 89, 0.13); color:orange; } table td{ border: 1px solid rgb(163, 163, 163); } </style> <!-- -------------ここで見た目の整え ↑ ------------- --> </body> </html> After の出力結果がこちら ↓ うん、雰囲気変わりましたね。 色付けは簡単にできるので、お試しあれ~! では!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

オブジェクト指向を完全に理解するにはwebサイトを例えにすると良い

はじめに オブジェクト指向が分からない、難しいという人が後を耐えないので、 私が一番わかりやすく説明するためにqiitaに舞い降りてきました。 (普段はnoteで自己啓発を書いています) しかし、ネット記事で全てを説明するのは無理なので基本的なところのみになります。 言語はwebサイトを例えにすると分かりやすいと思ったのでphpでやります。 前提1 以下のコードは入門書を3冊読んで用語がなんとなく分かった人向けになります。 前提2 ・そもそもオブジェクト指向は、チームで開発するときに必須。 ・個人開発でも5000行を超えてくると、オブジェクト指向を使わないとしんどくなってくる印象があります。 個人で1000行以下ぐらいのアプリを作っている方は不要だと思います。 マニュアルを読んでも分からない オブジェクト指向は部品と部品を繋いでいくものなのですが、マニュアルでは部品の部分しか書いていないような気がします。 これが難しくなるポイントです。 どうやってくっつけていくの? というところがいまいち入門書を読んでも書いていないので、そこを中心に書いていきます。 接続の方法が分かってくると、「なるほどね」となると思います。 それだけが狙いです。アハ体験しましょう。 マニュアルリンク マニュアルには、トレイト、無名クラス、オーバーロードなどなど用語が並んでいますが、これはオプションみたいなものです。 自動車にカーナビがなくても動くようにトレイトなどは知らなくてもアプリは動きます。 一応一本アプリを完成させたあとで、他の便利な機能を覚えるようにしてください。 ネット記事はオブジェクト指向の例えが下手 上で車の例えを出してしまったのですが、オブジェクト指向全体を車とかロボットとか人に例えると分からないです。 私はwebサイトを例にすることによって直接分かるように工夫しました。 理解を深める5冊 私がオブジェクト指向を理解するために5冊の本を必要としましたので先に紹介しておきます ・なぜ、オブジェクト指向で作るのか?(歴史を知る) ・オブジェクト指向をきちんと使いたいあなたへ (具体的なコードがある) ・Java言語で学ぶデザインパターン入門 ・かんたん UML入門 ・『ThoughtWorksアンソロジー』 (第5章のみ) https://gist.github.com/sambatriste/5535b1f0f6a73e9b37239d86edf61c70 オブジェクト指向がわからないのは、アプリの全体像が分かっていないから プログラミング初心者でアプリを1本も作ったことがない人がいきなりオブジェクト指向を勉強しても無理だと思います。 絵を描くのに似ています。 顔の輪郭や目の描き方だけ学んだところで、どういう人を描きたいのかが決まっていなければ、よく分からないまま終わるでしょう。 アプリ全体を把握してからコードを書くとオブジェクト指向が段々と分かってきます。 つまり、プログラマではなくシステムエンジニアの仕事を理解した方がいいということです。 今回は、みなさまお馴染みのwebサイトを例にしているのですぐ分かると思います。 headerクラス bodyクラス footerクラスです。 このクラス単体なら初心者でも作れるとは思うのですが、 連携させていくのがよくわからないのではないでしょうか? ということで、ここからはコードを使って説明します。 実用性はありませんが、このwebサイトをオブジェクト指向を使って書き直してみます。 index.php <?php echo "ヘッダー<hr>Body<hr>Footer<hr>"; なぜ、headerだけカタカナなのでしょうか? 意味はありません。 書き直すのも面倒なのでこのまま説明します。 php7.4.21で動作確認しています(MAMP) index.phpです。 これがスタートになるというのもphpなら分かりやすくていいと思います。 index.php <?php require_once("class/indexhtml.php"); require_once("class/header.php"); require_once("class/body.php"); require_once("class/footer.php"); $class_array = array(); $class_array[] = new Header(); $class_array[] = new Body(); $class_array[] = new Footer(); new Indexhtml($class_array); このコードが理解できると一気にオブジェクト指向が分かるでしょう。 一番の注目ポイントは、末尾のIndexhtmlクラスに他のインスタンスを引数に入れているところです。 初心者にありがちな、引数には「数値や文字列しか入れられない」という勘違いがあるとその時点で一生オブジェクト指向は分からないままです。 引数にはなんでも入れることができます。 これがオブジェクトとオブジェクトをつなげるという役割になっています。 また、各インスタンスは、まとめて配列に入れています。 これも大事なポイントになります。 配列にインスタンスを入れているので、ループで処理ができるようになりました。 これが出力している部分になります。 Indexhtml.php <?php class Indexhtml { function __construct($class_array){ foreach($class_array as $value){ echo $value->returnhtml(); } } } header、body、footerは共通の関数を持っていないと困る ループがしたいのでそれぞれのクラスは共通の関数を持っていて欲しいです。 ということでインターフェースが登場します。 I_returnhtml.php <?php interface Returnhtml { public function returnhtml(); } このインターフェースは3行だけのプログラムになっています。 では、インターフェースを使ったクラスはどうなるでしょう。 Header.php <?php require_once("./interface/I_returnhtml.php"); class Header implements Returnhtml { private $html; function __construct() { $this->html = "ヘッダー"; } function returnhtml(){ return $this->html . "<hr>"; } } インターフェースによって、returnhtml関数を必ず定義しなければいけなくなっています。 これによってループを安心して回すことができるようになりました。(大事) インターフェースはプログラマ側が間違わないようにしているものなので、別に書かなくても動きます。 さて、ヘッダーと同じようにbodyとfooterも書きましょう。 body.php <?php require_once("./interface/I_returnhtml.php"); class Body implements Returnhtml { private $html; function __construct() { $this->html = "Body"; //$this->side();//サイドバー追加 } //bodyにサイドバーが欲しくなったら追加する //function side(){ // //requireを関数の中で使うのはいかがなものか? // require_once("class/side.php"); // $side = new side(); // $this->html .= $side->returnhtml(); //} function returnhtml(){ return $this->html . "<hr>"; } } おっと。コメントが書いてありますね? これは後から頭の尖った上司が 「広告を張りたいからサイドバーを追加しろ!」と言ってきたときのためのプログラムです。 つまり、「オブジェクト指向は後から編集しやすいように設計する」というのがミソなんです。 実際にコメントを消しても実行できますので試してみてください。 オブジェクト指向を使わずに書いた場合は、 一度全てのコードを開くことになり、誤入力などでエラーがでてしまう可能性があります。 オブジェクト指向でファイル毎に分けていれば、 sidebarクラスを書いてから、bodyファイルだけ少し編集すればいいことになります。 頭の尖った上司に仕様変更を要求された場合柔軟に対応 サイドバーもクラスにする side.php <?php require_once("./interface/I_returnhtml.php"); class Side implements Returnhtml { private $html; function __construct() { $this->html = "| Sidebar" ; } function returnhtml(){ return $this->html; } } 最後はfooterです。 footer.php <?php require_once("./interface/I_returnhtml.php"); class Footer implements Returnhtml { private $html; function __construct() { $this->html = "Footer" ; } function returnhtml(){ return $this->html . "<hr>"; } } ほとんどheaderと同じですね。 実際はhtmlをゴチャゴチャ書くことになるのですが、今はサンプルなのでシンプルにしています。 一応これでコードの全てを書きました。 ファイル構成はこうなっています。 githubにアップロード? しませんよ classフォルダの中にインターフェースフォルダを入れてもいいのかもしれませんが、本人が分かればどちらでもいいでしょう。 どちらが主流なのでしょうか? 今回はサンプルでありファイル数が少ないのでどちらでもいいと思います。 所詮、オブジェクト指向は、コードを管理してるだけの箱のようなものなので、開発者が分かればいいと思います。 ファイル名の付け方や変数名の付け方も雰囲気で書いたので、あまり細かいところは突っ込まないでいただけると助かります。 実際のwebサイトはどうなっている? ・データベースからheaderのタイトルタグに情報を入れたい ・データベースからbodyの変数に情報を入れたい ・bodyにお問い合わせのフォームが欲しい ・お問い合わせがきたらメールで通知して欲しい などなど仕様は山積みです。 でも1つずつクラス分けして追加していけば大丈夫です。 チームで1つのアプリを作るという前提 1つのファイルでやっていないので、 「俺がbodyやるからお前はheaderとfooterをやって。インターフェース作っといたから後は分かるな」 というように分けることが可能です。 全体像が分かっていれば、後からファイルだけ送ってもらえればガッチャンコするだけですね。 またサイドバーを追加するときもサイドバークラスを作ってbodyに追加するだけです。 1行のシンプルなコードが長くなりました。意味があるのでしょうか? 元のコードから、かなり長いコードになってしまいましたね。 小分けにしていくと、どうしてもこうなってしまうのです。 ただ上記でも書いたのですが5000行ぐらいになってくるとわかりやすくメリットが出てきます。 また、データベースへの接続やメールなどの機能も1つずつ付けていくことが可能です。 オブジェクト指向のメリットが段々分かってくるといいのですが。 まとめ webサイトの場合はMVCモデルでやるのでしょうかね。 またwebサイトだとCSSやjavascriptなども絡んでくるので、少しややこしかったかもしれません。 しかし、例えをロボットや車にするよりもwebサイトで実際のコードを書いた方が直感的にわかりやすかったのではないでしょうか 私が常日頃思っていた、「オブジェクト指向をロボットや車で例えるなや」と思っていたことを解消するために書きました。 ストレス発散の記事です。 ありがとうございました。 この記事は昨日書いた記事の再投稿です sidebarクラスの所を間違えており、修正しました。 どなたかコメントしていただいた方がいたと思うのですが、すいません。 (コメントも読んでいません) qiita初投稿だったがゆえ、焦ってアカウントごと削除してしまいました。 出戻りになります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

継続について知りたい

はじめに ずっと理解したいと思って放置していることをこの機会に1つ進めてみよう、ということで、「継続」について今回は書いてみました。schemeの解説を辿るだけだとまた分からないままになりそうなので、普段使ってるphpでの実装を交えつつ。 継続とは ある時点での処理を続けるための情報(残りの処理)のこと。通常これを表立って取り扱うことはしませんが、意図的にこれを値として明示的に渡す形のプログラミングを継続渡しスタイル(continuation passing style)と呼びます。 これを少しでも理解しよう、というのがこの記事の主旨になります。 まずは具体例 素朴に引数の値をそのまま返す関数を定義します。 ※動作環境としてLaravelのtinker上で実施しています。 function id($x) { return $x; } これを呼び出して処理結果を出力する関数を定義、実行してみます。dumpはLaravelのヘルパ関数になります。 function runId() { dump(id('hoge')); } >>> runId() ^ "hoge" 特になんてことないですが、とりあえずこれを継続渡しスタイルにしてみましょう。処理の途中結果を受けて続きの処理を行うのが継続ですから、こうなります。 function cpsId($x, callable $cont) { $cont($x); } 2番目の引数で関数を受けてます。これが継続です。これはこの関数の処理結果を受けて何かをする関数です。 ではこれを呼び出す関数はどうなるでしょうか。 function runCpsId() { cpsId('hoge', fn ($result) => dump($result)); } >>> runCpsId() ^ "hoge" 当然ですが、これはどちらも同じように動きます。何が違うか? id 関数に存在した return が cpsId 関数には無いですね。つまり関数の呼び出し元へ戻って「継続する」、ということと等価なことが渡されている関数によって明示的に行われている、ということになってます。 もう少し複雑な例として再帰を見てみます。指定の数から0までの数字を足し合わせる関数を再帰で書いてみます。通常は、 function recursion(int $x) { if ($x === 0) { return $x; } return recursion($x - 1) + $x; } こんな感じになると思いますが、継続渡しスタイルでは、 function cpsRecursion(int $x, callable $cont) { if ($x === 0) { $cont($x); } else { cpsRecursion($x - 1, fn ($result) => $cont($result + $x)); } } こんな感じになります。実行してみます。 function runRecursion() { dump(recursion(10)); } >>> runRecursion() ^ 55 function runCpsRecursion() { cpsRecursion(10, fn ($result) => dump($result)); } >>> runCpsRecursion() ^ 55 return なしで、数珠つなぎで継続が渡されてそこで足し算が行われることに注意してください。 例外 継続渡しスタイルでは例外をどう扱うのでしょうか。通常は例外を発行してtry-catch構文で function recursionWithException(int $x) { if ($x < 0) { throw new Exception('マイナスは不可です'); } if ($x === 0) { return $x; } return recursionWithException($x - 1) + $x; } function runRecursionWithException($x) { try { dump(recursionWithException($x)); } catch (Exception $e) { dump($e->getMessage()); } } >>> runRecursionWithException(-1) ^ "マイナスは不可です" これは try-catch によって正常系の処理とは別種のフローが存在することを意味しています。つまり例外というのは正常系とは別の 継続 です。ですからこれは継続渡しスタイルでこのように書くことができます。 function cpsRecursionWithException(int $x, callable $cont, callable $throw) { if ($x < 0) { $throw('マイナスは不可です'); } elseif ($x === 0) { $cont($x); } else { cpsRecursionWithException($x - 1, fn ($result) => $cont($result + $x), $throw); } } function runCpsRecursionWithException($x) { cpsRecursionWithException($x, fn ($result) => dump($result), fn ($error) => dump($error)); } >>> runCpsRecursionWithException(-1) ^ "マイナスは不可です" >>> runCpsRecursionWithException(10) ^ 55 単純に例外時の継続関数を渡します。注目すべきは、try-catch 構文がここでは不要になっている、ということです。処理の 継続 を変数として明示的に扱うことで、例外の構文糖衣を脱糖することができます。 なぜ使うのか こういった書き方は通常はしないですが、どういう利点があるのでしょうか。ポイントとしては、上記でも分かったように、この継続渡しなスタイルで書くことでreturnを避けることができる、という点になります。 returnする、ということは呼び出し元はそれを待たなければなりません。しかし、それができないケースは多々あります。例えば、これは皆さんもよく使ってるかと思いますが、Ajaxプログラミングでfetchする際、そのレスポンスを待って他の動作を止めることはできません。この場合はcallback関数をfetch処理に渡して、レスポンスを取得した後に呼び出す仕組みが必要になります。 また、Webアプリケーションは、ユーザーに入力を促すフォーム画面を出力した後、ユーザーの入力を待つことはできません(しません)。ですが、ユーザーがフォームを入力してデータをPOSTしてきたならば、そこで何らかの 継続 をサーバー側で行う必要があります。 こういうケースの場合、通常ではサーバー側に何かしらのセッション情報を保持しておき、ユーザーからのフォームデータとの組み合わせで処理を進める実装をします。もしここで継続渡しスタイルが使えるとするならば、継続を保存することにより簡単に続きの処理を再開できるでしょう。(実際にそういうフレームワークもあります) call/cc ところで、継続というのはこのようにクロージャの連鎖になります。なので、複数の継続渡しの関数を組み合わせようとするとこうなってしまいます。 function cpsAddOne($x, callable $cont) { $cont($x + 1); } function cpsAddTwo($x, callable $cont) { $cont($x + 2); } function cpsAddThree($x, callable $cont) { $cont($x + 3); } function cpsAddOneTwoThree($x, callable $cont) { cpsAddOne($x, function ($result) use ($cont) { cpsAddTwo($result, function ($result) use ($cont) { cpsAddThree($result, $cont); }); }); } これは呼び出しが増えれば増えるほどネストが深くなりちょっと不便です。継続渡しの関数も適宜普通に呼び出せると便利です。 そこで、schemeでは任意のタイミングでの継続を取り出して通常の変数と同じように扱える仕組みが用意されています。 call-with-current-continuation 、略して call/cc という関数がそれになります。 上記のような内容をschemeの call/cc 使って実装すると、こういうのが (cps-add-one 1 (lambda (ret) (cps-add-two ret (lambda (ret) (cps-add-three ret (lambda (ret) ret)))))) こういう書き方も可能になります。 (define ret null) (set! ret (call/cc (lambda (k) (cps-add-one 1 k)))) (set! ret (call/cc (lambda (k) (cps-add-two ret k)))) (set! ret (call/cc (lambda (k) (cps-add-three ret k)))) ret > 7 これは無名関数の引数 k に継続、ここではトップレベルへ戻って set! されるところへ戻るための関数が渡され、それを呼び出すことで戻ることができるようになってます。この k を current-continuation と呼び、call/cc はそれを任意のタイミングで生成して渡してくれます。 ではphpではどうかというと、残念ながら call/cc のような仕組みはありません。ですので、それに近い実装をしてみようと思います。 function callcc(callable $f) { $ret = null; $cc = function ($x) use (&$ret) { $ret = $ret ?: $x; }; $f($cc); return $ret; } これは継続をパラメータとして受ける関数を受けて、関数適用時に渡される値をクロージャの外の変数に設定する継続関数を生成し、渡す関数になります。つまり関数$ccが適用されるとその引数がcallccの戻り値とになる、適用後の継続がスキップされる、ということを意味します。 これを使うと、 function cpsAddOneTwoThreeByCallCC($x, callable $cont) { $result = callcc(fn ($k) => cpsAddOne($x, $k)); $result = callcc(fn ($k) => cpsAddTwo($result, $k)); cpsAddThree($result, $cont); } 上記schemeの例のような書き方ができました。 このコード自体はクロージャ連鎖によるネストを避ける以外に特に何のありがたみも無さそうですが、一つ既視感がある形になってます。つまり、このコードは callcc を使うことで return と等価なことを行っている、return の実装になっている、ということです。 これは次の例を見るとよくわかります。cpsAddThreeがスキップされた計算結果が取得されています。 function cpsReturn($x) { $result = callcc(function ($return) use ($x) { cpsAddOne($x, function ($result) use ($return) { cpsAddTwo($result, function ($result) use ($return) { $return($result); cpsAddThree($result, $return); }); }); }); dump($result); } >>> cpsReturn(1) ^ 4 このphpの callcc ではできないのですが、schemeなどでの実装では任意のタイミングで呼び出し元へ戻る、つまり後続の処理をスキップして return をする、という実装が可能になります。これを応用してループを抜けたり( break )、例外処理( throw )の実装も可能になります。 まとめ ここまでで継続なるものの導入部分を書いてみました。call/ccでreturnが実装できる、というあたりで、Haskellの継続モナド関連などがうっすらと思い浮かびましたが、関連が自分にはまだよく分かってないのでとりあえずここまで。 参考 なんでも継続 Scheme/継続の種類と利用例 Kahuaによる継続渡しスタイルWebプログラミング kahua/Kahua
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

php -vで確認したバージョンとは違うバージョンですとエラーに言われて困った話

環境 M1 Macbook Air MAMP Cakephpの環境構築過程でcomposer updateを実施したところ下記のエラーが発生。 $ composer update Loading composer repositories with package information Updating dependencies Your requirements could not be resolved to an installable set of packages. Problem 1 - Root composer.json requires php <8.0 but your php version (8.0.13) does not satisfy that requirement. Problem 2 - phpunit/phpunit[6.0.0, ..., 6.5.14] require php ^7.0 -> your php version (8.0.13) does not satisfy that requirement. - Root composer.json requires phpunit/phpunit 6.* -> satisfiable by phpunit/phpunit[6.0.0, ..., 6.5.14]. composer.jsonで指定しているphpのバージョンの範囲が、私の環境にあるphpのバージョンの範囲外らしい。(こっちは8.0より低いバージョンが必要だけどあなたのphpのバージョンが8.0.13なので無理ですよ〜と言っている) なので下記コマンドでphpのバージョンを確認してみたところ $ php -v PHP 7.4.21 (cli) (built: Oct 1 2021 11:18:30) ( NTS ) Copyright (c) The PHP Group Zend Engine v3.4.0, Copyright (c) Zend Technologies with Zend OPcache v7.4.21, Copyright (c), by Zend Technologies あれ? 7.4.21だぞ? なんで8.0.13じゃないんだ? ここで詰まりました。 解決法 compose.jsonのconfigに、下記のようにphpのバージョンを指定し、再度composer updateすることで解決しました。 "config": { "minimum-stability": "dev", "platform": { "php": "7.4.21" } }, そもそもなぜバージョンのところでエラーが出たのかという話なのですが、私がphp -vで参照した7.4.21のphpは、元々macにインストールされていたphpのバージョンであったのに対し、composer.json側ではHomebrew側のphpを参照していたことが原因のようです。 余談 composer updateが完了した後、ふと思い立ち再度php -vをしたところ $ php -v PHP 8.0.13 (cli) (built: Nov 24 2021 11:11:54) ( NTS ) Copyright (c) The PHP Group Zend Engine v4.0.13, Copyright (c) Zend Technologies with Zend OPcache v8.0.13, Copyright (c), by Zend Technologies 上記のように変化していました。 あれ?いつ変わった。。? Homebrew側のphpのPATHを追加して反映させたタイミング? あれ?でもエラーの前には反映させていたような。。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む