Javascript ⇒ PHP ⇒ データベース をAjaxで実現

仮想サイコロツール作成のプログラム回です。
プログラムに興味ない方はスルー推奨。
主にwordpressのAjaxを用いた連携について。

自分自身がまったくの初心者だったので、初心者目線の記事になります。
好きなこと書いてるうちのブログの方向性はどちらへ向かっていくのか(笑)

どうも、全然ゲームできておらず、ゲーム系の記事が書けません
と言っても最近やっていたのは、仮想ダイス結果共有ツールなので、ゲームみたいなものかも?

ダイスを振って、多人数で結果を共有できるツール
もちろん共有せずに、ただ振るだけも可能です。

ツール自体は単純なので、ツールの内容というより作るのに使った知識などを話題にしたいと思います。
今回は読者の方置いてけぼりで、ajax通信で同じ苦労をしようとしてる人向けの記事(めちゃくちゃ狭い)になります。

目次

フロー

こんな感じ。
サイコロを振った結果をデータベースに登録して、みんなが見れるようにしよう、というところ。
当初はデータベース触るとか思ってなかったですが、javascriptはそもそもローカルストレージを使った個人個人の表示がメインなので、LineとかDiscordみたいなみんなが同じ文字をみれるようにするにはデータベースは必須なんですね~

最初はHTMLのテーブルを作る(Innerhtml)延長線上で行けると思ってました…

プログラム作成はChatGPTとの二人三脚

サイトを始めてから多少はHTML/CSS/javascriptは触りましたが独学でまだまだ初心者。
今回扱うphp/データベースに関してはほぼ初見。
なので、ChatGPTとGoogle検索が命綱。

ChatGPTが強い部分

使用者が多いプログラムに関するもの
今回の例でいうと、一般的なjavascriptやphpの構文、データベースに走らせるSQL文などは一瞬で読み解き答えてくれます。
・基本的に数年で情報が劣化しないものには強い

ChatGPTが弱い部分

使用者が少ないプログラムに関するもの
今回の例でいうと、wordpressの基本機能として提供されるajax.phpの取り扱いはかなり怪しい回答が多かったです。
あとjson関係も苦手です。
・ここ1年で大きな変化があったもの
おそらくChatGPTの参考データは2021年半ば。それ以降のデータはありません。
いずれ更新が入るとは思いますが、2年前の浦島太郎だと思っておいた方がいいです。

サイコロを振る部分

9割くらいはChatGPTに作ってもらいました。
こういう、ただのロジック(ループとかfor文とか)に関する部分はほんと強い。
ChatGPTがプログラム分野で間違いなく輝くのは人間だと時間のかかるコーディングチェック的な部分ですね。
エラー文言の解析力も高いです。
wordpress独自の仕様とかには弱いですが。

ダイス関係のコード(javascript)
/****** global area *****/
var g_dicethrow;
var g_dicevalue;

/***************/


function rollDice() {
    const numDice = document.getElementById("num-dice").value;
    g_dicethrow = numDice;
    if (numDice > 10) {
        alert("サイコロが多すぎます");
        return;
    }
  
    g_dice=0;
    const diceImages = [];
    for (let i = 1; i <= 6; i++) {
        diceImages[i] = new Image();
        diceImages[i].src = `http://tsurezuregames.mods.jp/files/pictures/dice${i}.jpg`;
//  diceImages[i].src = `./picture/dice${i}.jpg`;
  
    }
  
    let total = 0;
    const resultDiv = document.getElementById("dice-result");
    resultDiv.innerHTML = "";
  
    const rollDiceHelper = function(diceNum, currentRoll, rollCount) {
        const rollInterval = 200;
        const rollMax = 10;
        
        setTimeout(function() {
            const roll = Math.floor(Math.random() * 6) + 1;
currentRoll.innerHTML  = `<img class="dice" width="50" src="${diceImages[roll].src}" alt="${roll}">`;
if (rollCount < rollMax) {
                rollDiceHelper(diceNum, currentRoll, rollCount + 1);
            } else {
                total += roll;
                if (diceNum == numDice) {
                    g_dicevalue = total;
                    addTotalToTable();

                    let element = document.getElementById('addtbldata');
                    //チェックボックスがONの時だけデータ登録する
                    if(element.checked){
                        callshortcode();
                    }else{
                        console.log("it's a throwing test.");
                    }

                }
            }
        }, rollInterval);
    };
  
    for (let i = 1; i <= numDice; i++) {
        const currentRoll = document.createElement("div");
        currentRoll.classList.add("dice");
        resultDiv.appendChild(currentRoll);
        rollDiceHelper(i, currentRoll, 0);
    }

}
  
  
  function addTotalToTable() {
    var nameid = document.getElementById("nagetahito").value;

    if (nameid == ""){
        nameid = "名無しさん";
    }
    document.getElementById("name").textContent = nameid;

    var str = g_dicethrow + "D6 -> " +  g_dicevalue;
    document.getElementById("total").textContent = str;

    const now = new Date();
    const formattedDate = formatDate(now);
    document.getElementById("yyyymmddhhmmss").textContent = formattedDate;
  }

  function formatDate(date) {
    const year = date.getFullYear();
    const month = date.getMonth() + 1;
    const day = date.getDate();
    const hours = date.getHours();
    const minutes = date.getMinutes();
    const seconds = date.getSeconds();
    return year + "年" + month + "月" + day + "日" + hours + "時" + minutes + "分" + seconds + "秒";
  }

HTMLの方は大したことしてないので省略します。
動作に関しては、ツールの方で見てみて下さい。
「結果を登録」にチェックマークをいれなければ、結果の登録もされませんので、引っ込み思案な人でも大丈夫。

ポイント解説に関しては、行数が見えるエディタにコピペする前提で行数で言っていきます。

正直、ほとんどChatGPTが作ったので自分も理解しきれていません(笑)
なぜ複数のサイコロが同時に回るんでしょうか…
関数内で定義しているrollDiceHelper関数を、自分で何回も呼びまくってるのがテクニカルすぎる…

35行目

currentRoll.innerHTML  = `<img class="dice" width="50" src="${diceImages[roll].src}" alt="${roll}">`;

このサイコロ画像を並べるところが唯一手を加えたところ。
ローカルだと問題なかったんですが、wordpress環境にもっていった途端、画像が縦並びになってしまいました><
今回の場合、プログラム内で動的にimgタグを量産してせいか(imgに対する)CSSもちゃんと効かなかったんですよね。

色々試してもなかなか改善せず(ローカルだと問題なくできていたのに)。
おそらくはwordperssの自動整形機能が原因だろう、という予想をつけて無効にしたりするうまくいかず。

諦めかけてた時に出てきたのか

.flexbox {
  display: flex;
  flex-direction: row;
}

この display: flex;というCSSで、これのおかげでwordpressでもimgの横並びができました。
display: flex;はwordpressを使う人であれば覚えておいた方がよさそうです。

あとは本当にchatGPTが優秀でダイス部分(と個人用のテーブル出力)はサクサクできました。

functions.php更新の恐怖(作業時はバックアップ必須

wordperssを触ったことある人ならみんな共感してくれるはず(笑)
触る前に絶対しないといけないのが

[超重要]functions.phpを触る時は、wordpress機能外で復旧できるようにする

functions.phpはthemes/(使用テーマ)/functions.phpにあります。


よくPC系知識を共有するサイトで「バックアップは取っておきましょうね」という謳い文句はよく聞きますが、functions.phpだけは本当の意味でバックアップ必須です。
wordpressの更新ボタン押した瞬間に画面が真っ白になる恐怖

復旧対策もしてないのにwordpressにログインできなくって、wordpressからこんな恐怖メールが来た日には…
「リカバリーモード」なるものを提供してもらえるらしい(期限1日)ですが、やはり手動で復旧できるように、functions.phpを更新する際はFTPを開けたまま作業するくらいの気持ちがいいと思います。

Code Snippetsはfunctions.phpを修正せずにphpによる関数やショートコード等の実装ができますが、どうしてもfunctions.phpを編集しないといけないケースもあります。

wordpressからphpのテーブルinsert関数呼び出し(ajax 通信)

wordpressのケースになります。

javascript:主にクライアント側で行う処理を担う
PHP:主にサーバー側との通信処理を担う

ajax関係の送信&受信コード
  function callshortcode(){

    var gameid = document.getElementById("gameid").value;
    if(gameid == ""){
        gameid = '0';
    }

    var playerid = document.getElementById("nagetahito").value;
    if (playerid == ""){
    playerid = "名無しさん";
 }

     var dicedata = g_dicethrow + "D6:" +  g_dicevalue;
    console.log('[dice_add_data gameid="' + gameid + '" playerid="' + playerid + '" dicedata="' + dicedata + '"]');

var ad_url = {"ajax_url":"https://tsurezuregames.jp/wp-admin\/admin-ajax.php"};

var myData = {
  gameid: gameid,
  playerid: playerid,
  dicedata: dicedata
};

    // AJAXリクエストを送信
    $.ajax({
      type: 'POST',
      url: ad_url.ajax_url,
      data: {
      action: 'dice_add_data_action',
    myData: myData
      },
      success: function(response) {
        // 成功時の処理
        console.log("成功:"+response);
      },
      error: function(jqXHR, textStatus, errorThrown) {
        // エラー時の処理
        console.error(textStatus + " " + errorThrown);
      }
    }); 
//   console.log("end");

  }

大事なのは呼び出す部分
ここさえきちんと記述できれば、javascript⇒PHPは、ほぼ成功。

// AJAXリクエストを送信
$.ajax({
type: ‘POST’,
url: ad_url.ajax_url,
data: {
action: ‘dice_add_data_action’,
myData: myData
},

type: ‘POST’:定型
ad_url.ajax_url:ad_urlを上(16行目)で定義しているのを忘れずに。

16行目:var ad_url = {"ajax_url":"https://tsurezuregames.jp/wp-admin\/admin-ajax.php"};ココ

自分のサイトのURLに合わせて変えて使ってください。
dataの第1引数は「dice_add_data_action」で呼び方がちょっと特殊です。

命名の関係性としては

ややこしい命名規則
dice_add_data:処理が書かれているphpの本関数(=X)
X_action:(javascript)X_actionという処理を投げる
wp_ajax_X_action:ログインユーザーから投げられた「X_action」を受け取り、Xに渡す
wp_ajax_nopriv_X_action:非ログインユーザーから投げられた「X_action」を受け取り、Xに渡す

初心者的イメージとしては、javascript内の関数は距離が近いので手渡しで連携しやすい
javascript⇒php関数は距離が離れてるので投げて中継に受け取ってもらい、本処理に渡してもらう
そんなわけで、「X_action」で投げて、最初に受け取るのは「wp_ajax_X_action」か「wp_ajax_nopriv_X_action」で、実際の処理はまた別関数ということで、javascriptばっかりやってた人からすると、名前が違うのが戸惑う原因となりやすい
ajaxの使い方は、この命名の法則性が一番の鍵です。

第2引数は、引き渡すオブジェクトを指定します。

var myData = {
gameid: gameid,
playerid: playerid,
dicedata: dicedata

};

中はこんな感じでデータが詰まってます。
javascript⇒phpの変数受け渡しは割と楽に行けます。
(たまたま)左右の名前が同じでややこしくなってますが、左が取り出しに使う項目名右が変数名です。
多分左右が違う名前でもいいと思います。
左の項目名は本処理を行うphpの関数でも参照します。

ajaxに関しては、上の形式さえ覚えればそれほど難しくないです。
私は教材がなかなか見つからず苦労しましたが…
chatGPTさんもajaxはイマイチ苦手らしく、誤答されまくりました。

エラーチェックはほぼ定型文

success: function(response) {
// 成功時の処理
console.log(“成功:”+response);
},
error: function(jqXHR, textStatus, errorThrown) {
// エラー時の処理
console.error(textStatus + ” ” + errorThrown);
}

responseにはPHPからの戻り値(echoかreturnで戻される)が入ってきます。

functions.phpに呼び出し先の関数を実装

php側の受信&SQL実行コード(データベースINSERT処理)
// ダイスデータを登録する処理
function dice_add_data() {

// // データベースに接続する
	$servername = "mysqlXXX.XXX.lolipop.XXX";
	$username = "LAAXXXXXXX";
	$password = "YYYYYYYYYYY";
	$dbname = "ZZZZZZZZZZZ";

	$conn = new mysqli($servername, $username, $password, $dbname);
	if ($conn->connect_error) {
	  die("Connection failed: " . $conn->connect_error);
	}

	//引数受け取り
    $myData = $_POST['myData'];
    $atts = array(
        'gameid' => $myData['gameid'],
        'playerid' => $myData['playerid'],
        'dicedata' => $myData['dicedata']
    );	
	
	// 変数の設定
	$id = isset($atts['gameid']) && !empty($atts['gameid']) ? $atts['gameid'] : '0000'; // ゲームID(str)
	$reg_id = isset($atts['playerid']) && !empty($atts['playerid']) ? $atts['playerid'] : '0000'; // プレイヤID(str)
	$dice_sum = isset($atts['dicedata']) && !empty($atts['dicedata']) ? $atts['dicedata'] : '0000'; // ダイス合計(str)
	$reg_date = date('YmdHis'); // 現在日時を取得して14桁のbigint形で登録

	
	// データの登録
	$sql = "INSERT INTO `dice_table` (`ID`, `登録者ID`, `ダイス目合計`, `登録日時`) VALUES ('$id', '$reg_id', '$dice_sum', '$reg_date')";
	
if ($conn->query($sql) === TRUE) {
  return "データを追加しました。";
} else {
  return "エラーが発生しました。" . $conn->error;
}

	// データベースの接続を閉じる
	$conn->close();

}
// ログインしているユーザー向け関数
add_action('wp_ajax_dice_add_data_action', 'dice_add_data');
// 非ログインユーザー用関数
add_action('wp_ajax_nopriv_dice_add_data_action', 'dice_add_data');

短いですが、重要な部分ですし、なによりfunctions.phpなので、細かくみていきます。
更新する時は、必ず復旧準備をしながら行いましょう。
ちなみにcode_snippetsのプラグインで行おうとするとうまくいきませんでした

① 本処理の関数定義

function dice_add_data() {

ここのポイントは引数の設定をしないこと。
function dice_add_data($atts) {
等としても、受け取れません。
chatGPTは受け取れると言い張っていましたが…

②データベースとの接続

// // データベースに接続する
$servername = “mysqlXXX.XXX.lolipop.XXX”;
$username = “LAAXXXXXXX”;
$password = “YYYYYYYYYYY”;
$dbname = “ZZZZZZZZZZZ”;

$conn = new mysqli($servername, $username, $password, $dbname); if ($conn->connect_error) { die(“Connection failed: ” . $conn->connect_error);
}

それぞれ、データベースの「サーバー名」「ユーザー名」「パスワード」「データベース名」を設定します。
その後の$conn = new mysqli…でデータベースとの接続を確立してます。

ロリポップでの確認方法

ロリポップ以外の場合は、各自のサーバー情報から入手して設定して下さい。

下の接続部分($conn = …)は定型文なのでそのままで構いません。
ただ、もしかしたらデータベースのバージョンによって違いがある可能性はあります。

③引数の受け取り

厳密には「引数」とは言わないかもしれませんが、自分にとって分かりやすいので。

<?php
//引数受け取り
$myData = $_POST[‘myData’];
$atts = array(
‘gameid’ => $myData[‘gameid’],
‘playerid’ => $myData[‘playerid’],
‘dicedata’ => $myData[‘dicedata’]);

// 変数の設定 (余計な部分は削りました)
$id = isset($atts[‘gameid’])
$reg_id = isset($atts[‘playerid’])
$dice_sum = isset($atts[‘dicedata’])

ちなみにajaxの呼び出し時の引数設定としては以下のようにやってました。

// javascript
var myData = {
gameid: gameid,
playerid: playerid,
dicedata: dicedata

};
// AJAXリクエストを送信
$.ajax({
type: ‘POST’,
url: ad_url.ajax_url,
data: {
action: ‘dice_add_data_action’, // <- dataの第1引数はフック関数に引っ掛けてもらうために使われる
myData: myData // <- dataの第2引数は受け渡しデータが入っている
},

名称の連携がされてる部分は青く色付けしてあります。

サーバーへSQLを飛ばし、応答をもらう

// データの登録
$sql = “INSERT INTO `dice_table` (`ID`, `登録者ID`, `ダイス目合計`, `登録日時`) VALUES (‘$id’, ‘$reg_id’, ‘$dice_sum’, ‘$reg_date’)”;
if ($conn->query($sql) === TRUE) {
return “データを追加しました。”;  //return 値はjavascript側でresponseという変数に格納される
} else {
return “エラーが発生しました。”  //return 値はjavascript側でresponseという変数に格納される
. $conn->error; //javascript側でerrorで判定される
}
// データベースの接続を閉じる
$conn->close();//javascript側でsuccessで判定される

$conn->query($sql) がさらっとデータベースにSQLを投げているところ。
ここは、return値の設定(echoでも同様)と、javascript側でsuccess:/error:の判定ができることを覚えておきましょう。
return値に関しては、$sql (作成したSQL文が格納されている)を返した方がデバッグには役立ちます

// ダイスデータを登録する処理 function dice_add_data() {

//処理

}
// ログインしているユーザー向け関数
add_action(‘wp_ajax_dice_add_data_action‘, ‘dice_add_data‘);
// 非ログインユーザー用関数
add_action(‘wp_ajax_nopriv_dice_add_data_action‘, ‘dice_add_data‘);

//javascript
// AJAXリクエストを送信
$.ajax({
type: ‘POST’,
url: ad_url.ajax_url,
data: {
action: ‘dice_add_data_action‘,
myData: myData
},

最後のここに関しては間違いやすい部分なので、もう一度少し前の説明をひっぱってきます。
dice_add_dataが共通している命名(もしかしたら一緒にしなくてもいい場所もあるかも)。
dice_add_data_actionがajax通信時にjavascript側でactionで投げられる関数名)。
他の部分は定型語句だと思ってください。
php触ってなかった自分からすると、この命名の部分が一番難しかったです。

javascript⇒php($_POSTで引数受け取り、INSERTのSQLを作成)⇒データベース(INSERT処理)
データベース⇒php(応答)⇒javascript(return)

これでINSERT処理は終了です。
コーディング行数自体は少ないですが、触ったことがないとかなり苦労する部分だと思います(苦労しました)。

SELECT文を同様に作成

ですが、INSERTの方をかなり丁寧にやりましたので、ほぼ端折ります。
基本は前項(INSERT)と同じです。
javascript側は、オブジェクトで引数を渡します。
php側は$_POSTで引数オブジェクトを受け取り、それをもとにSQLを作成し、データベースに流します。

今回はSELECTなので、その後に応答データをjavascriptに戻す処理が必要なので、その部分だけ見ていきます。

//phpのSELECT~応答までの処理抜粋

// SQL実行
$result = $conn->query($sql);
// 結果を配列に変換
$data = array();
while ($row = $result->fetch_assoc()) {
$data[] = $row;
}
// データベースの接続を閉じる
$conn->close();

// 結果を返す
echo json_encode($data);

ポイントはSQLの応答結果が入っている$resultと、応答用のデータである$dataの取り扱いです。
この辺りの取り扱いに関しては、定型だと思った方が楽です。

このコーディングで、SQLのSELECT結果がjson形式にエンコードされて呼び出し元のjavascriptに戻ります
json_encodeは必要な処理で、これをしないと正常に呼び出し元でデータの解析が行えません。

では、次に呼び出し元の処理をみていきましょう。

//javascript 呼び出し元の関数途中から
$.ajax({
type: ‘POST’,
url: ad_url.ajax_url,
data: {
action: ‘dice_select_game_action’,
myData: myData
},
success: function(data) {
generateTable(data); //この先でdataの解析を行う
}

dataには、先ほどのjson_encode($data)が入っています。
dataはjson形式なので、このままでは読めません。

function generateTable(data) {

if (data[data.length – 1] == ‘0’) {
data = data.slice(0, -1);
} else {
console.log(“末尾は0以外”);
}

var jsonData = JSON.parse(data);
//中略
//innerHTMLでテーブルデータをHTMLに出力する処理(一部)
rows += ‘<td>’ + jsonData[i].テーブル1カラム目 + ‘</td>’;
rows += ‘<td>’ + jsonData[i].テーブル2カラム目 + ‘</td>’;
rows += ‘<td>’ + jsonData[i].テーブル3カラム目 + ‘</td>’;
rows += ‘<td>’ + formatDate2(jsonData[i].テーブル4カラム目 ) + ‘</td>’;

最初に末尾の0を削っているのは、なぜか末尾に0が存在していて、jsonデコード関数のJSON.parseが失敗したからです。
理由はChatGPTさんも教えてくれませんでした…
なぜかJSON.parseがエラーを吐いて、中をみたら最後に意味不明の0がついてたので削ったらうまくいったというだけです。

var jsonData = JSON.parse(data);
でjsonData にarray型でデータが入ってから、やっと通常のarray型のように解析できるようになります。
arryayの項目名に関してはテーブルのカラム名となります。

以上、SELECT系で処理が異なっていた部分となります。
jsonDataじゃないとやり取りできないというのがネックになりました(他に方法があるのかもしれませんが)。
あと、なぜ最後に0がついててJSON.parseがエラーになったのかは未だに分かっていません。
(json形式の)data自体はそのままでも表示されるので、もしJSON.parseがエラーを吐いていたら、中をざっと見てみることをオススメします

ajax関数を使ってみて

今回使ったajax関数は、wordpressがサポートしてくれてる関数なので、使い方さえ覚えることができたら割と楽に使えることが分かりました。
ただ、wordpress限定だからなのか、具体的なコーディング方法をのせてくれてるサイトがほとんど見つからなかったので今回の記事を書いた次第になります。

私のブログの記事としては異例のプログラム記事になってしまいましたが、検索などで来ていただいた方のお役に立てば幸いです。

いや、ほんと大変でした…
でもajax通信に触れたことは今後役に立ちそうです。
複雑な処理はできませんが、ミニゲーム作ってランキングつけるくらいはできそうですね。

よかったらダイスツールで遊んでいって下さい(笑)

コメント

コメントする

目次