エス技研

WordPress、CakePHP、PHP、baserCMSなどの Web系システムを中心に情報を提供します!


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でレコードを取得するサンプルソースの処理概要

 
CakePHP3で Ajaxを使う方法の解説。3.6以降対応。Successとthenの両方を解説。
 
上記の画像のプルダウンの数値を変更すると、その数値に該当する IDのレコードを取得して、「初期表示」と書かれている場所にそのレコードの情報を表示する、という仕組みの処理です。
 
例えば、
「商品の検索システムで、「カテゴリ」を変更すると「検索」ボタンを押すことなく直ぐに検索結果の商品一覧の内容が切り替わる」
というような仕組みのものを想定しています。
 
 
このサンプルは、「AjaxSamples」というコントローラー名で処理を作成しています。
 
また、Ajaxを処理する方法として、「Success/Error」でレスポンスを受ける方法と、「then()」メソッドで受ける方法と、2つを記述しています。
実際に作成する場合はどちらかだけで OKですので、記述する内容は半分になります。
 
 

テンプレートファイル「index.ctp」

 
まず最初に、テンプレートファイルとなる「index.ctp」を作成します。
 
パス付きのファイル名は以下になります。
/src/Template/AjaxSamples/index.ctp
 
 

<?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
 
 

<?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
 
 

<?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
 
 

$(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に追加します。
 

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の関連記事

CakePHPのpostlinkで生成した削除リンクをクリックしても処理が実行されない対処法
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」に関する記事一覧
 
 

 - CakePHP 3.x 4.x 5.x

最後までお読みいただきましてありがとうございます。
この記事が参考になったと思いましたらソーシャルメディアで共有していただけると嬉しいです!

Message

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

下記の空欄を埋めてください。 * Time limit is exhausted. Please reload CAPTCHA.

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)

※入力いただいたコメントは管理者の承認後に掲載されます。

  関連記事

CakePHP3のfriendsofcake/searchでツリーカテゴリーの子階層も含めて検索する方法
CakePHP3のfriendsofcake/searchでツリーカテゴリーの子階層も含めて検索する方法

CakePHP3のツリービヘイビアを使ったツリーカテゴリーの子階層も含めての検索を検索プラグイン「friendsofcake/search」を使って実現する方法を解説しました。

CakePHP 2.3 Search Pluginで検索処理 その2表示件数を動的に変える方法

CakePHPの検索プラグイン Search Pluginの検索処理の中で動的に表示件数を変える方法です。

CakePHP3でComposerでインストールできないプラグイン、外部ライブラリを vendorに入れて手動で読み込む方法
CakePHP3でComposerでインストールできないプラグイン、外部ライブラリを vendorに入れて手動で読み込む方法

CakePHP3にComposerからインストールできないプラグインやライブラリなどを利用する方法、vendorにファイルを設置し、composer.jsonを更新し、それを呼び出す方法を詳細に解説。

CakePHP3で環境変数を設定して本番環境と開発環境を分けて処理をする場合
CakePHP3で環境変数を設定して本番環境と開発環境を分けて処理をする場合

CakePHP3で開発環境と本番環境とで違う設定ファイルを読み込ませて環境ごとに定数を切り替える方法を解説。Apacheのhttpd.confに環境変数を設定し、それを読み込み判別する。

CakePHP2、CakePHP3、CakePHP4、CakePHP5のバージョンを調べる 2つの方法
CakePHP2、CakePHP3、CakePHP4、CakePHP5のバージョンを調べる 2つの方法

CakePHPのバージョンの調べ方2点を紹介。CakePHP3~CakePHP5は共通だが CakePHP2はフォルダ構成が異なるためコマンドのパスもオプションも異なる。

CakePHP3でkeywords、DescriptionをHTMLヘルパーを使って設定する
CakePHP3でkeywords、DescriptionをHTMLヘルパーを使って設定する

CakePHP3のkeywordsとdescriptionを設定する方法の解説。CakePHP3にはmetaタグを編集するHTMLヘルパーが用意されているためそれを利用すればOK!ポイントはブロック化を有効にすること。

CakePHP4のCSS、JavaScript、画像のブラウザへのキャッシュをコントロールする
CakePHP4のCSS、JavaScript、画像のブラウザへのキャッシュをコントロールする

CakePHP4、CakePHP3でブラウザにキャッシュさせる設定の解説。CSS、JavaScript、画像をブラウザにキャッシュさせるのか、定期的にリロードする設定にするのかの設定が可能。

CakePHP3のInsert On Duplicate Key Update(upsert)構文を解説・バルク処理も
CakePHP3のInsert On Duplicate Key Update(upsert)構文を解説・バルク処理も

CakePHP3で Insert … On Duplicate Key Update構文(upsert)を実行する方法を解説。バルク処理の方法も用意されているため大量処理の場合も対応可能。

CakePHP 2.3 Model、Controllerの見たい変数の中身をログ出力

CakePHPの Modelや Controllerの変数の中身をログとして出力して見る方法を提供します。

CakePHPのFlashエラーは出るが入力項目ごとのメッセージが出ないエラーの原因
CakePHPのFlashエラーは出るが入力項目ごとのメッセージが出ないエラーの原因

Bakeして自動生成した入力フォーム処理を元に少し処理を追加したら、入力エラーがあってもエラーメッセージが表示されなくなった。原因はリダイレクトの処理にあった。