CakePHP3で Ajaxを使う方法の解説。3.6以降対応。Successとthenの両方を解説。
2020/01/02
CakePHP3で Ajaxを使う方法の解説-3.6以降対応
CakePHP 3.6で CsrfProtectionMiddlewareがデフォルトで有効になった
CakePHP3で Ajaxを使ってデータベースからデータを取得する処理を、サンプルソースを使って解説します。
今回の解説のサンプルソースは CakePHP 3.6以降対応となっています。
CakePHP 3.6以降では、「CsrfProtectionMiddleware」がデフォルトで有効になるようになりました。
そのため、Ajaxで POSTするときも CSRFトークンを一緒に送信しないと「Error: [Cake\Http\Exception\InvalidCsrfTokenException] CSRF token mismatch.」のエラーが出るようになりました。
そのため、CakePHP 3.6より前のサンプルソースはそのままでは動かないものが多数存在することになってしまいました。
対処方法としては、実験環境であるならば「CsrfProtectionMiddleware」をオフにするという方法も取れますが、本番運用を想定している場合はセキュリティホールになりえますので、CSFRトークンも一緒に送信する方法を実装すべきでしょう。
この記事では、CakePHP 3.6以降でも動作するよう、CSRFトークンも一緒に送信する仕様になっています。
また、サンプルソース中に出てくる「Topics」のテーブルは、「お知らせ情報」が入っているテーブルで、テーブルレイアウトは「CakePHP3で保存前にバリデーション結果を取得する2つの方法」の記事にあるような内容を想定してください。
テーブルからデータを取得するという処理になっていますが、サンプルソースでは「print_r()」で出力するだけですので、テーブル構造はほぼ関係はありません。
CakePHP3で Ajaxでレコードを取得するサンプルソースの処理概要
上記の画像のプルダウンの数値を変更すると、その数値に該当する IDのレコードを取得して、「初期表示」と書かれている場所にそのレコードの情報を表示する、という仕組みの処理です。
例えば、
「商品の検索システムで、「カテゴリ」を変更すると「検索」ボタンを押すことなく直ぐに検索結果の商品一覧の内容が切り替わる」
というような仕組みのものを想定しています。
このサンプルは、「AjaxSamples」というコントローラー名で処理を作成しています。
また、Ajaxを処理する方法として、「Success/Error」でレスポンスを受ける方法と、「then()」メソッドで受ける方法と、2つを記述しています。
実際に作成する場合はどちらかだけで OKですので、記述する内容は半分になります。
テンプレートファイル「index.ctp」
まず最初に、テンプレートファイルとなる「index.ctp」を作成します。
パス付きのファイル名は以下になります。
/src/Template/AjaxSamples/index.ctp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
<?php $this->assign("title", "CakePHP3 で Ajax を利用してリストを取得"); ?> <?php $this->assign("h1", "CakePHP3 で Ajax を利用してリストを取得"); ?> <?= $this->Html->script('//code.jquery.com/jquery-1.11.3.min.js',['block' => true]) ?> <?= $this->Html->script($ajax_name) ?> <nav class="large-3 medium-4 columns" id="actions-sidebar"> <ul class="side-nav"> <li class="heading"><?= __('Actions') ?></li> <li><?= $this->Html->link(__('Ajax Samples'), ['action' => 'index']) ?></li> </ul> </nav> <div class="caseStudies form large-9 medium-8 columns content"> <div> <?= $this->Form->create ("null",[ "type" => "post"]); ?> <select name="select_number_success" id="change-topics-success"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> <option value="4">4</option> <option value="5">5</option> <option value="6">6</option> <option value="7">7</option> <option value="8" selected>8</option> <option value="9">9</option> </select> <?= $this->Form->end(); ?> </div> <div id="index-area-success"> 初期表示・success </div> <div> <?= $this->Form->create ("null",[ "type" => "post"]); ?> <select name="select_number_then" id="change-topics-then"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> <option value="4">4</option> <option value="5">5</option> <option value="6">6</option> <option value="7">7</option> <option value="8" selected>8</option> <option value="9">9</option> </select> <?= $this->Form->end(); ?> </div> <div id="index-area-then"> 初期表示・then </div> </div> |
前半が「Success/Error」でレスポンスを受ける処理の部分で、後半が「then()」メソッドで受ける処理の部分です。
「id=”index-area-success”」「id=”index-area-then”」と記述している箇所をそれぞれ置換する仕組みになっています。
また、CakePHP 3.6以降では、CSRFトークンも一緒に送信する必要がありますので、「$this->Form->create()
」~「$this->Form->end()
」を利用して、CSRFトークンが出力されるようにしておく必要があります。
また、titleタグや h1タグを編集する方法については下記の記事を参考にしてください。
CakePHP3で /Layout/defult.ctpにある titleタグ、h1タグを編集する方法
JavaScriptをページごとに指定する方法については下記の記事を参考にしてください。
CakePHP3でページごとに読み込むJavaScript、CSSを変える処理の解説
置換するテンプレートファイル「replace_index_area.ctp」
前項の「index.ctp」はベースになるテンプレートファイルで、こちらは、Ajaxで差し替えるテンプレートファイルになります。
パス付きのファイル名は以下になります。
/src/Template/AjaxSamples/replace_index_area.ctp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?php if($templateType): ?> <div id="<?= $templateType ?>"> <?php else: ?> <div id="index-area-success"> <?php endif; ?> <?php if(isset($topicsList)){ print_r($topicsList); } ?> <br> <?php if(isset($selectNumber)){ print_r($selectNumber); } ?> <p>replace_index_area.ctp</p> </div> |
今回はサンプルということで、Topicsテーブルから取得した変数「$topicsList」を「print_r()」で出力すると言うだけのシンプルなテンプレートです。
このテンプレートの内容を、事項の JavaScriptで「index.ctp」の該当の場所に編集します。
Ajaxで呼び出されるアクションのコントローラー「AjaxSamplesController.php」
最初の画面を表示する「index」アクションと、Ajaxで呼び出される「replaceIndexArea」アクションの 2つの処理が記述されているコントローラーです。
パス付きのファイル名は以下になります。
/src/Controller/AjaxSamplesController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
<?php namespace App\Controller; use App\Controller\AppController; use Cake\Event\Event; use Cake\ORM\TableRegistry; // 他のテーブルの読み込み class AjaxSamplesController extends AppController { public function index() { // Ajax で使用する JSファイルを指定 $this->set("ajax_name","send_ajax_samples.js"); } // Ajax で呼び出すアクション public function replaceIndexArea() { // Ajax からのリクエストか、否かを確認 if($this->request->is("ajax")){ // select_number の有無を確認 if(!$this->request->getData(["select_number"])){ return false; } // 各値を取得 $selectNumber = $this->request->getData(["select_number"]); $templateType = $this->request->getData(["template_type"]); // Topics テーブルから値を取得 $this->Topics = TableRegistry::getTableLocator()->get("Topics"); $topicsList = $this->Topics->find()->where(["id"=>$selectNumber])->toArray(); // 各値をテンプレートに渡す $this->set(compact("topicsList","selectNumber","templateType")); } } } |
各処理にコメントを書いていますので問題ないかと思いますが、Ajaxから「replaceIndexArea」アクションを呼び出します。
CSRFトークンを受け取ってチェックする処理は、ミドルウェアとして処理されますので、コントローラーでなにか特別な処理を記述する必要はありません。
Ajaxとして処理を実行する JavaScriptファイル「send_ajax_samples.js」
プルダウンを変更した際、Ajaxとして処理を実行する JavaScriptファイルです。
パス付きのファイル名は以下になります。
/webroot/js/send_ajax_samples.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
$(document).ready(function() { // Topicsc のレコードを取得(success、error で処理) $('#change-topics-success').change(function(e){ // Selectボックスの値を取得 var select_number = $('[name=select_number_success] option:selected').val(); // 取得した値をアラートで表示(デバッグ用) alert( select_number ); // CSRF対策用の CSRFトークンの値を取得 var csrf = $('input[name=_csrfToken]').val(); $.ajax({ type: "POST", datatype:'JSON', // 処理をする Ajaxの URLを指定。自サーバ内であればドキュメントルートからのパスでも OK // url: "/cake3_9/ajax-samples/replace_index_area", url: "http://localhost:8099/cake3_9/ajax-samples/replace_index_area", // CakePHP に送る値を指定(「:」の前が CakePHPで受け取る変数名。後ろがこの js内の変数名。) data: { "select_number" : select_number, }, // beforeSend を利用して CSRFトークンのチェックを実行 beforeSend: function(xhr){ xhr.setRequestHeader("X-CSRF-Token",csrf); }, // 正常に処理が実行された場合は、1つ目のパラメータに取得した HTMLが返ってくる success: function(html, status, xhr){ // 返ってきた HTMLを置換 $("div#index-area-success").replaceWith(html); // 返ってきたステータスコードなどをアラートで表示(デバッグ用) alert('Success\n' + status + "\n" + xhr.status + "\n" + xhr.statusText ); }, // 正常に処理が行われなかった場合の処理 error: function(XMLHttpRequest, textStatus, errorThrown) { // 返ってきたステータスコードなどをアラートで表示(デバッグ用) alert('Error : ' + errorThrown + "\n" + XMLHttpRequest.status + "\n" + XMLHttpRequest.statusText + "\n" + textStatus ); } }); }); // Topicsc のレコードを取得(then で処理) $('#change-topics-then').change(function(e){ var select_number = $('[name=select_number_then] option:selected').val(); alert( select_number ); var csrf = $('input[name=_csrfToken]').val(); $.ajax({ type: "POST", datatype: 'JSON', url: "http://localhost:8099/cake3_9/ajax-samples/replace_index_area", data: { "select_number": select_number, "template_type": "index-area-then", }, beforeSend: function(xhr){ xhr.setRequestHeader("X-CSRF-Token",csrf); } }) .then( // 正常に処理が実行された場合は、1つ目のパラメータに取得した HTMLが返ってくる function(html, status, xhr){ $("div#index-area-then").replaceWith(html); alert('Success\n' + status + "\n" + xhr.status + "\n" + xhr.statusText ); }, // 正常に処理が行われなかった場合の処理 function(XMLHttpRequest, textStatus, errorThrown) { alert('Error : ' + errorThrown + "\n" + XMLHttpRequest.status + "\n" + XMLHttpRequest.statusText + "\n" + textStatus ); } ); }); }); |
この処理も各コマンドごとにコメントで説明を書いていますので、分かってもらえるのではないかと思いますが、Ajaxを処理する方法として、前半が「Success/Error」でレスポンスを受ける方法、後半が「then()」メソッドで受ける方法の処理になっています。
詳しい内容は下記の記事を参考にしていただければ、と思いますが、「Success/Error」でレスポンスを受ける方法は、jQuery1.4の頃の古い書き方なのだそうです。
この記事のサンプルで書いてあるような単純な処理ならば、「Success/Error」で受け取る処理でも特に問題は発生しませんが、複数の Ajaxの処理が絡む場合は、「then()」メソッドで受ける方法を用いると、スッキリした記述をすることができるそうです。
https://qiita.com/tonkotsuboy_com/items/0722ad92f370ab0c411b
CakePHP 3.6以降の処理の場合は、CSRFトークンを「beforeSend」で送信する処理が必要になります。
また、「(デバッグ用)」とコメントで書いている「alert()」の処理は、正しく処理がされていることを確認するための処理ですので、必要ない場合は削除してしまって問題ありません。
エラーの場合の処理も本番環境では不要な場合も多々あるでしょう。
CSRFコンポーネントを無効にする方法
今回のサンプルは、CSRFコンポーネントが有効な状態での対処方法を解説しましたが、CSRFコンポーネントを無効にする方法も用意されています。
具体的には下記の内容を Controllerに追加します。
1 2 3 4 |
public function beforeFilter(Event $event) { $this->getEventManager()->off($this->Csrf); } |
詳しくは下記の Cookbookを参照してください。
CakePHP3 Cookbook クロスサイトリクエストフォージェリ
特定のアクションで CSRF コンポーネントを無効にする
https://book.cakephp.org/3.0/ja/controllers/components/csrf.html#id2
記事内に「非推奨ですが」とある様に、可能であれば CSRFを有効にする仕組みを導入するように作るべきですが、一時的に、もしくは、違う方法で安全が確保されている場合などは CSRFコンポーネントを OFFにして使う場合もあるでしょう。
CakePHP3の関連記事
CakePHP4のCSS、JavaScript、画像のブラウザへのキャッシュをコントロールするCakePHP3でレコードを保存(追加、更新、Insert、Update)する複数の方法を紹介
CakePHP3でモデルなしフォームからCSVをアップロードしレコードを更新する方法解説
CakePHP3でPHP Simple HTML DOM Parserを使ってスクレイピングする方法
CakePHP3のInsert On Duplicate Key Update(upsert)構文を解説・バルク処理も
CakePHP3の1対多での連携を中間テーブルを使った多対多の連携に変更するときの手順
CakePHP3でデフォルトのソート条件を設定してユーザの選択肢たソート条件を有効にする方法
CakePHP3で Ajaxを使う方法の解説。3.6以降対応。Successとthenの両方を解説。
CakePHP3でパンくずの指定は HTMLヘルパーを使って指定する方法を解説
CakePHP3にOGPをfetch、asignを利用してテンプレートごとに指定する方法を解説
その他の「CakePHP3」に関する記事一覧
GoogleAdwords
GoogleAdwords
この記事が参考になったと思いましたらソーシャルメディアで共有していただけると嬉しいです!
関連記事
-
CakePHP3で「SQLSTATE[23000]: Integrity constraint violation」「SQLSTATE[42S22]: Column not found」などのエラーが出たときの確認するポイント
CakePHP3の開発で発生する「SQLSTATE[23000]: Integrity constraint violation」「SQLSTATE[42S22]: Column not found」のエラーには特有の原因もあるため、その説明と対処方法の解説。
-
CakePHP3でアソシエーション先のカラムでデータをソートして取得したい
アソシエーション先のテーブルのカラムをキーにソートをしたデータを取得したい!ときの記述方法を解説。「orderAsc()」ではなく「contain()」の中にSort条件を記述する。
-
CakePHP3チュートリアルで日付と時刻のDateTimeでエラーが出たときの対処方法
CakePHP3のブックマークチュートリアルには記載ミスもあり、そのまま動かない個所もある。CakePHP3では namespaceを使うようになったので、classを呼び出すときに¥を追加する必要が!
-
CakePHP 2.3 コマンドラインからPHPのシェル実行の方法解説
CakePHP 2.3でコマンドラインから CakePHPで記述した処理を実行する方法を解説します。
-
cakephp3 カスタムバリデーションを簡易的に実装する方法
CakePHP3の独自のバリデーションをテーブルクラス内に簡単に記述する方法を解説。他のテーブルクラスでは使えないが、記述する量は少なく実装できるため、他で使わない処理を書くのには便利。
-
CakePHP 2.3 ログイン、操作履歴、アクセスログ出力
CakePHPでログインや操作履歴などのアクセスログ出力処理を作成します。
-
CakePHP3で静的ページの作成は webrootか pagesを使う。トップページを参考に解説
CakePHP3で静的なページを設置する場合の方法(webrootとpagesとを活用する方法)を解説。pagesの解説はデフォルトのトップページがどう表示されているかを参考に解説。ルーティングの機能も。
-
CakePHP3の更新画面でUpload Plugin 3.0を使う方法、viewで使う方法解説・その3
CakePHP3でファイル、画像をアップロードするプラグイン、upload plugin 3を導入する手順を解説した記事。3部作のその3で記事を更新する際のファイルの取り回しなどについてを解説。
-
CakePHP4のcake cache clear_allでPermission deniedはパーミッションの変更が必要
CakePHP4でキャッシュをクリアするコマンドcake cache clear_allでPermission deniedのエラーが出るのはcakeにパーミッションが足りないとき。その対処方法を解説。
-
CakePHP3の検索プラグイン「friendsofcake/search」の様々な検索の仕方の実装方法
CakePHP3で検索をするプラグイン「friendsofcake/search」の検索条件のカスタマイズ方法の解説。検索項目を増やしたり、以上、以下での検索や、チェックボックスによる検索の方法などを解説。