北村由衣のブログ

コメント機能追加

コメント機能を実装しました

わりとごてごてにコードを書いて仕組みを作り上げました。
フォーム画面はHTML/JavaScript。コメントの受付処理はPHP。各記事へのコメント表示はJavaScriptで実装しました。
JavaScriptはjQueryとかでも使っちゃえばいいのに、ライブラリを使わずに、「素クリプト」で組み上げました。

//jsonファイルを取得する処理
function getJson(){
   const req = new XMLHttpRequest();
   req.onreadystatechange = function(){
      let result = document.getElementById('contentList');
      if(req.readyState == 4){   // 通信完了
         if(req.status == 200){
            result.innerHTML = req.responseText; //整形処理を別途呼び出すこと
         }else{
            result.innerHTML = '取得に失敗しました';
         }
      }else{
         result.innerHTML = '一覧取得中...';
      }
   }
   req.open('GET', 'content_list.json', true);
   req.send(null);
}

この処理はQiitaの記事『JavascriptのAjaxについての基本まとめ』を参考にしました

今回の仕組みは以下の模式図の通りです。

各記事のHTMLとコメントJSONは別で保存しておきます。 ブログ共通JavaScriptにて、各記事が表示された時にコメントJSONを取得し、整形して表示します。
コメントの受付は各記事には直接組み込まず、別タブでフォーム画面を表示します。
フォーム画面からPHP処理を呼び出して、コメント受付メールアドレス宛にメールにして送付します。
届いたメールを、私(北村由衣)がチェックして、各コメントJSONに反映させます。

メールアドレスの取り扱い

フォームにてユーザ識別のためにメールアドレスを入力いただくのですが、これは少々取り扱いに迷いました。 というのは、「受け付けましたメール」を出すかどうか、という点です。
当フォームを踏み台に、第三者のメアドを入力すれば私から受付メールが飛ぶわけです。 迷惑メールとして私のサイトが認識されるような事態になるリスクを負うか、 有効なメアドであることの確認のためにメールを飛ばすか。 検討した結果、リスクを回避するために、「受け付けましたメール」は無しとしました。
私の受理メール受信箱には、Fromとして、入力されたメアドが表示されます。 フォームに記載した通り、そのコメントメールには返信したり、その他用途への転用は致しません。

実装中に発生した技術的問題

処理フローが概ね頭の中でイメージできて、いざコードに起こそうとしていくと、 やはり一筋縄ではいかないというか、問題がいくつか、出てきたり。 そんな解消した問題達を、備忘録的に書いておこうかと思います。

ちなみに実装順は、受付フォーム→受理PHP→コメント表示JavaScript でした

記事とコメントフォームの連動

フォームを全記事統一の1ページにするにあたり、どの記事へのコメントかを正確に処理する必要がありました。 最終的な実装としては、「QueryStringに記事の情報を含めて呼び出す」「hidden項目に持って受理処理へ渡す」の2段階になりました。

クエリストリングの情報を読む

JavaScriptでQueryStringのパラメータを読みだして、<input type="hidden">に格納しました。

//リクエスト ../form.html?key=val
//クエリストリングで渡されたパラメータを読み出してinput#keyに格納する
const queryStr = window.location.search;
const queryParam = new URLSearchParams(queryStr);
document.getElementById('key').value = queryParam.get('key');

locationが現在のURL等を示すのはなんとなくわかっていたのですが、 GETリクエストのパラメタは検索キーみたいな感じなんですね。POSTとの使い分けを考えれば、 分かってはいるのですが、queryとかでもなくsearchに入ってるとは。
続くURLSearchParamsなんてもう、そのsearch感がより強い要素名ですね。 この処理で、queryParamには[key]=valueの連想配列が作成されるようです。

POSTではなくGETでqueryStringに含ませたのは、フォーム呼出しを簡易にしたかったからです。
各記事からリンクをクリックするだけの簡単な操作でフォームが呼び出せるようにしたかったのです。 パラメータがアドレスバーで丸見えになりますが、3つのキー項目、どれもURL等から照会できるものですし、 機密的な要素はないのでいいかな、と。まぁ、値をいじられたらちゃんと元記事と紐づけができなくなる、 というリスクがなくはないですが、そんなことして当サイトで遊んでくれるなら、遊んでくれてもいいんじゃないか、と思いました

hidden項目がPOSTされない

フォーム画面の隠し項目<input type="hidden" id="key">を含むformaction でPHPファイルへ情報をPOSTした時、disabledな項目と、hidden項目がパラメータに含まれていませんでした。
disabledについては、MDNに書かれている通り、

無効になった入力欄はclickイベントを受け取らず、フォームと共に送信されることもありません。
(引用)<input>: 入力欄 (フォーム入力) 要素 disableプロパティ https://developer.mozilla.org/ja/docs/Web/HTML/Element/input#attr-disabled
ということで、送信されないのが、仕様でした。

一方で、hidden項目がPOSTでパラメータに含まれなかったのは、私のコーディングミスに起因していました。

<!-- うまくいかないhidden項目 -->
<input type="hidden" id="key">
<!-- うまくいくhidden項目 -->
<input type="hidden" id="key" name="key">
要するに、idではなく、name属性がform送信で必要な項目だったのです。 MDNの<input>nameの説明を確認すると、はっきりと示されていました。

name
入力コントロールの名前を指定する文字列です。この名前はフォームデータが送信される時に、コントロールの値と共に送信されます。
name は (厳密にはそうではありませんが) 必須の属性と考えてください。 入力欄に name が指定されていなかった場合や name が空欄だった場合、その入力欄の値はフォームと一緒に送信されません。
(引用)<input>: 入力欄 (フォーム入力) 要素 nameプロパティ https://developer.mozilla.org/ja/docs/Web/HTML/Element/input#attr-name

idはあくまでもDOM要素の一意な識別用であって、formの中での項目名というわけではなく、 データを受け渡すためにはnameを指定する必要がある、というわけでした

PHP処理でのトラブル

実はPHPを書くのは初めてだったりしました。サーバで動的にHTMLを処理したい時はJSPとかで今まで乗り切ってきていたので…。
文字列結合演算が'str1' + 'str2'ではなく'str1'.'str2'な辺りは面食らった感じです。 String.Concat('str1','str2')(.NET)とか'str1'&'str2'とかは見覚えがあるのですが。

POSTされてきたデータの文字化け

とまぁ、簡単にいうと、Unicodeデータが受信側でISO-2022-JPに勝手に変わっていた状態でした。
こちらの記事( (PHP)フォームからPOSTで受け取ったデータが文字化けする)を参考にサーバの設定を修正しました。

本サイトは、レンタルサーバ1000を借用しているのですが、 php.iniの変更が認められていましたので、internal_encodingの値を変更しました。
なお、encoding_translationは既定がちゃんとoffでしたので、今回は変更しないで済みました

#php.ini
mbstring.internal_encoding = utf-8
mbstring.encoding_translation = off

改行文字がリテラルになる

\r\nという改行を指定する文字列が、PHPの文字列出力結果として、リテラルの\r\nとなってしまい、 意図したところで改行できない事象が発生しました。

<?php
 //リテラルで出力される例
 $str = 'sample\r\nexample';
 echo $str;	//『sample\r\nexample』と出力される

 //改行されて出力される例
 $success = "sample\r\nexmaple";
 $another = 'sample'."\r\n".'example';
 echo $success;
 echo $another;
 /*『sample
 example』と出力される */
?>
phpで改行文字がリテラルになるか否かは引用符の種類で変わる

シングルクオートではなく、ダブルクオートで囲うことでエスケープしなくても済むようになるそうです

引用符
文字列を指定する最も簡単な方法は、引用符 (文字 ') で括ることです。
引用符をリテラルとして指定するには、バックスラッシュ (\) でエスケープする必要があります。
バックスラッシュをリテラルとして指定するには、二重 (\\) にします。
それ以外の場面で登場するバックスラッシュは、すべてバックスラッシュそのものとして扱われます。
つまり、\r や \n といったおなじみのエスケープシーケンスを書いても特別な効果は得られず、書いたままの形式で出力されます。

二重引用符
文字列が二重引用符 (") で括られた場合、 PHP は、以下のエスケープシーケンスを特殊な文字として解釈します。
エスケープされた文字
記述意味
\n ラインフィード (LF またはアスキーの 0x0A (10))
\r キャリッジリターン (CR またはアスキーの 0x0D (13))
(引用・抜粋)PHP: 文字列 - Manual https://www.php.net/manual/ja/language.types.string.php#language.types.string.syntax.single

PHP:mail メールのヘッダ情報

メールを送信する、便利なメソッドが標準で用意されているPHPでしたが、ちょっと面倒なところがひとつありました。
mb_send_mail($to, $subject, $message, $header) このシンプルな構文でメールが送れちゃうのですが、 最後に書いた$headerがオプションとして必須にはなっていないくせに、環境によっては必須となる点です

メールの発信者の情報が、サーバのオプション(php.ini)で定義されていれば恐らく不要なのですが、 そんな設定がされていないレンタルサーバ等の場合、このheaderにFrom: adr@example.comを含めてあげる必要があるようです。

<?php
  mb_language("Japanese");   //マルチバイト(mb)対応
  mb_internal_encoding("UTF-8");
  
  $to = "$_POST['adr_to']";
  $subject = "$_POST['sbj']";
  $message = $_POST["msg"];
  $mail_header = 'From: '. $_POST["from"];
  mb_send_mail($to, $subject, $message, $mail_header);
?>

コメント機能について

ここまで、実装にあたり突破したトラブルを書き連ねてきましたが、 実装に要した時間はトータルで半日くらいで、初見のPHPをいじったりした割には スムーズに進めることができたな、と思っています。

作ったはいいけども、実際に来訪者の皆様からコメントを投稿していただいてなんぼだと 思っていますので、ぜひぜひ、こちらから 書き込んでみてくださいまし。

返信機能( >>1 みたいなの)は実装していませんが、うまくリンク生成してあげたほうが…いいのかな。 今後のタスクですね


このエントリーをはてなブックマークに追加


コメントはこちらからお寄せください