fake sendmail for windows を使って、PHP でごく普通に( mb_send_mail で )メール送信

fake sendmail は、XAMPP に同梱されています

メールサーバは、フリーメールで可能で、簡単な設定で利用できます。以下のサンプルは、さくらインターネット で動作確認しました。

▼ 手順

🔴 ダウンロード

配布元より、sendmail.zip をダウンロードして、解凍します。
( XAMPP には sendmail フォルダがあります )

🔴 sendmail.exe の動作テスト

まず、sendmail.ini をエディタで開いて、四つのエントリを指定します
▼ さくらインターネットを使う場合の設定

smtp_server=初期ドメイン
smtp_port=587
auth_username=ユーザ@初期ドメイン
auth_password=パスワード

※ smtp_ssl=auto となっており、デフォルトで ssl で実行され、使え無い場合は TLS を使おうとします

🔴 php.ini の設定

sendmail_path = "C:\tools\sendmail\sendmail.exe"

実際の sendmail.exe のパスを “” で囲んで指定します。
※ この場合、エラーログは “C:\tools\sendmail\error.log” です( デフォルト )

🔴 PHP のコード ( UTF8 BOM なしで記述します )

HTML の input 要素で type=”text” name=”fld_to”、name=”fld_subject”、name=”fld_body”、を form 要素の中に記述し、form 要素に method=”post” action=”以下の PHP のファイルのパス” を指定し、form 要素の中に input 要素で type=”submit” を指定したボタンを記述します。

01.mb_language("Japanese");
02.mb_internal_encoding("UTF-8");
03. 
04.$from_header = "From: " . mb_encode_mimeheader( mb_convert_encoding("差出人","iso-2022-jp") );
05. 
06.$from_header .= " <{$GLOBALS["mail"]}>";
07. 
08.$result = mb_send_mail($_POST["fld_to"], $_POST["fld_subject"], $_POST["fld_body"], $from_header);
09.if ( $result ) {
10.        $error = "OK";
11.}
12.else {
13.        $error = "ERROR";
14.}
15. 
16.print mb_language() . "<br>";
17.print $error;

$GLOBALS[“mail”] は、メール形式であれば良いですが、通常ならば auth_username で指定したメールアドレスを記述します。

PHP, 記録

TCPDF をダウンロードして、Windows の任意のフォルダで TCPDF 同梱のサンプルを実行する為の設定

2024/08/04 時点で 6.7.5

テスト環境

❶ Windows11 64ビット + XAMPP  8.2.12 + php ( 8.2.12 )
❷ TCPDF ( C:\app\TCPDF-main として解凍 )

テスト内容

TCPDF-main.zip を解凍すると、TCPDF-main\examples というフォルダがあり、そこにサンプルが沢山ありますが、それを他のフォルダでも同様に動作させます。

手順

❶ example フォルダを全て 任意のフォルダにコピー( アプリの実行環境になります )
❷ そのフォルダを XAMPP で、PHP が動作するようにする
❸ tcpdf_include.php を以下のように書き換える
( set_include_path までを先頭に、require_once('tcpdf.php'); は追加、または全部以下に書き換え )
1.<?php
2.$path = "/app/TCPDF-main";
3.set_include_path(get_include_path() . PATH_SEPARATOR . $path);
4.require_once('config/tcpdf_config_alt.php');
5.require_once('tcpdf.php');
6.?>

barcodes は、tcpdf_barcodes_1d_include.php と tcpdf_barcodes_2d_include.php の
中身の配列の参照パスの一つを realpath('/app/TCPDF-main/tcpdf_barcodes_1d.php')
のように変更すれば動作します( tcpdf_barcodes_2d_include.php も同じ )
01.// Include the TCPDF 1D barcode class (search the class on the following directories).
02.$tcpdf_barcodes_1d_include_dirs = array(
03.    realpath(dirname(__FILE__) . '/../../tcpdf_barcodes_1d.php'),// True source file
04.        realpath('/app/TCPDF-main/tcpdf_barcodes_1d.php'),// Relative from $PWD
05.        '/usr/share/php/tcpdf/tcpdf_barcodes_1d.php',
06.        '/usr/share/tcpdf/tcpdf_barcodes_1d.php',
07.        '/usr/share/php-tcpdf/tcpdf_barcodes_1d.php',
08.        '/var/www/tcpdf/tcpdf_barcodes_1d.php',
09.        '/var/www/html/tcpdf/tcpdf_barcodes_1d.php',
10.        '/usr/local/apache2/htdocs/tcpdf/tcpdf_barcodes_1d.php'
11.);

フォントの追加は、tcpdf_addfont.php で作成したフォントファイルを fonts フォルダにコピーします。
埋め込み : c:\php\php tcpdf_addfont.php -t TrueTypeUnicode -f 32 -i xxxxxxxx.ttf 非埋め込み : c:\php\php tcpdf_addfont.php -t CID0JP -f 32 -i xxxxxxxx.ttf -t は省略すると フォントファイルをチェックして通常は TrueTypeUnicode になり、-f は省略すると 32 です。 ※ -b を指定すると、character bounding box の情報を php のフォントファイルに書き込みます( If true includes the character bounding box information on the php font file. )
OTF から TTF の変換はこちらから

ChatGPT に運用で良くあるファイル名の一括変更と画像縮小コードを作成してもらう

その人の普段使っている Windows の運用言語でいいと思いますが、汎用性で言えば PHP が一番でしょう。

ChatGPT にまず以下のお願いをしました。

C:\tmp\004 というフォルダ内に 前半が数字部分で後半が漢字部分のファイル名を持った拡張子 .jpg のファイルか複数あります。これらのファイル名部分を数字部分だけに書き換える PHP の CLI プログラムを作成してください

するとこのようになりました

01.<?php
02.$dir_path = "C:/tmp/004/";
03. 
04.// ディレクトリ内のファイルを取得
05.$file_list = glob($dir_path . "*.jpg");
06. 
07.foreach ($file_list as $file) {
08.  // ファイル名から漢字部分を削除して数字部分のみを取得
09.  $new_filename = preg_replace('/[^0-9]/', '', basename($file, '.jpg')) . '.jpg';
10. 
11.  // 新しいファイル名に変更する
12.  if (rename($file, $dir_path . $new_filename)) {
13.    echo "ファイル名を変更しました:$new_filename\n";
14.  } else {
15.    echo "ファイル名を変更できませんでした:$file\n";
16.  }
17.}
18.?>

ファイル名変更部分のみです。これに続いて画像の縮小を頼んだのですが、こちらの条件設定があいまいだったので、新たに両方を実行するプログラムを書いてくれました。

変換後の画像ファイルを横幅を120ピクセルに縮小するPHPのCLIプログラムを作成してください。ライブラリは GD が使えます。

01.<?php
02.$dir_path = "C:/tmp/004/";
03. 
04.// ディレクトリ内のファイルを取得
05.$file_list = glob($dir_path . "*.jpg");
06. 
07.foreach ($file_list as $file) {
08.  // ファイル名から漢字部分を削除して数字部分のみを取得
09.  $new_filename = preg_replace('/[^0-9]/', '', basename($file, '.jpg')) . '.jpg';
10. 
11.  // 画像を横幅120ピクセルに縮小する
12.  $image = imagecreatefromjpeg($file);
13.  $width = imagesx($image);
14.  $height = imagesy($image);
15.  $new_width = 120;
16.  $new_height = $height * ($new_width / $width);
17.  $new_image = imagecreatetruecolor($new_width, $new_height);
18.  imagecopyresampled($new_image, $image, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
19. 
20.  // 新しいファイル名に変更する
21.  if (imagejpeg($new_image, $dir_path . $new_filename)) {
22.    echo "画像を変換しました:$new_filename\n";
23.  } else {
24.    echo "画像を変換できませんでした:$file\n";
25.  }
26. 
27.  // メモリを解放する
28.  imagedestroy($image);
29.  imagedestroy($new_image);
30.}
31.?>

ChatGPT は説明も出力してくれます。

このプログラムは、まず指定されたフォルダ内のすべての .jpg ファイルを取得し、ファイル名から漢字部分を削除して数字部分のみを含む新しいファイル名を設定します。その後、GD ライブラリを使用して、元の画像ファイルを読み込んで横幅を120ピクセルに縮小します。縮小した画像を新しいファイル名に保存し、ファイル名が変更できたかどうかを出力しています。最後に、メモリを解放しています。

シャットダウンダイアログを開く( VBscript, Jscript, C#, C# in PowerShell, PowerShell, PHP, Python, Ruby )

ALT + F4



デスクトップをクリックまたは、デスクトップだけを表示してこのショートカットで開きます。Windows のアプリケーションは通常このショートカットで終了します。(タイトルバーの左上のアイコンをクリックするとメニューが表示されてその中にあります)



VBScript

Shell.ShutdownWindows method

Jscript

C# : VisualStudio
01.using System;
02. 
03.namespace ShutdownDialog
04.{
05.        class Program
06.        {
07.                static void Main(string[] args)
08.                {
09.                        dynamic shell = Activator.CreateInstance(Type.GetTypeFromProgID("shell.application"));
10.                        shell.ShutdownWindows();
11. 
12.                }
13.        }
14.}

PowerShell 内で C#
01.$code = @"
02.using System;
03.public class MyClass {
04.        public static void Main() {
05. 
06.                dynamic shell = Activator.CreateInstance(Type.GetTypeFromProgID("shell.application"));
07.                shell.ShutdownWindows();
08. 
09.        }
10.}
11."@
12. 
13.Add-Type -Language CSharp -TypeDefinition $code -ReferencedAssemblies ("Microsoft.CSharp")
14. 
15.[MyClass]::Main()

PowerShell のみ

PHP

Python
1.import win32com.client
2.shell = win32com.client.Dispatch("shell.application")
3.shell.ShutdownWindows()

pywin32 が必要なので、こちらを参照してください

Ruby





WEBアプリの HTML 側の発射台の FORM と INPUT の属性の関係の整理

現在最も信頼性の高い HTML や JavaScript のオンラインドキュメントは MDN です。

なので、例えば FORM に関して Google で調べる場合には最初に MDN のキーワードを入れて調べます
MDN FORM

▼ MDN Web Docs
https://developer.mozilla.org/ja/docs/Web/HTML/Element/form

ここで出てくる 属性 の内容がとても重要になりますが、『古くなっている仕様』も記述(警告)されている事がとても大事です。

以下は現在必要な情報しか取り出していませんが、例えば『accept』という属性は必要無いという事が解ります。

FORM 属性の解説ページのリンク

🧡 accept-charset

💘 action

     サーバのデータの送り先 を指定します。

🧡 enctype

💘 method

    get か post を指定します。

🧡 novalidate

💘 target

    サーバーがデータを送るウインドウの name を指定します。

method="get"

サーバーへのデータの送り方の違いで get と post は大きく違います。

デモページ

テストページ
01.<script>
02.function checkForm() {
03.        if ( !confirm( "送信しますか?" ) ) {
04.                return false;
05.        }
06.        return true;
07.}
08.</script>
09.<form action="https://www.google.co.jp/search" onsubmit="return checkForm();" target="_blank">
10.        <input type="text" name="q" required list="pg">
11.        <datalist id="pg">
12.        <option value="PHP">
13.        <option value="Java">
14.        <option value="SQL">
15.        </datalist>
16. 
17.        <input type="submit" name="send" value="送信">
18.</form>

GET で送る場合は、リンクと同じ

ブラウザで URL が作成されてからサーバに送られます。ですから、同じ URL を作成できるのならば A 要素の href 属性に指定して呼び出す事が可能です。

❌ 欠点1
この結果、サーバー側で送った元のチェック( 排除 )を行っていない場合はどこからでも自由にデータを受け取ってしまうので、サーバー運営上はあまり好ましくありません。

❌ 欠点2
さらに、URL を介してデータを渡すので無制限にデータを送れません。それはファイルのアップロードはできない事を意味します。

❌ 欠点3
リンクと同じ扱いなので、ブラウザがキャッシュとして保存してしまうので、サーバ側でキャッシュを使わないようにしないと『ウェブアプリケーション』として動作しない事がよくあります。

アプリケーションの テスト中は GET で処理しておけば、QueryString 部分がアドレスバーに表示されて解りやすいかもしれません

POST 時のデータをブラウザで確認

POST メソッドでも、ブラウザの『デベロッパーツール』を使うと参照可能です。

FORM と 送信データ

通常では、FORM 内に定義された3つの要素からデータをサーバへ送る事ができます。

⭐ INPUT 要素
⭐ SELECT 要素
⭐ TEXTAREA 要素

但し、INPUT 要素ではとても多くの種類( type )があり、タイプによってはブラウザのみで動作するものもあります。また、name 属性の無いタイプは、サーバ側でデータを識別できないので送られません。

※ INPUT 要素の disabled 属性があるとサーバーへは送られなくなります

type="submit"

このタイプはサーバ送信用のボタンとなり、クリックする事によってサーバへデータを送ります。( JavaScript を使用すると、このボタン無しでも送る事ができます )

さらにこのボタンに使用できる FORM 用の属性を利用すると、FORM に設定していた属性を上書きする事ができます。

⭐ formaction
⭐ formenctype
⭐ formmethod
⭐ formnovalidate
⭐ formtarget

form="フォームのid"

データをサーバに送るコントロールに、form 属性を指定すると、FORM 要素の中に含めないでも FORM と関連づけできるようになっています。

01.<script>
02.function checkForm() {
03.        if ( !confirm( "送信しますか?" ) ) {
04.                return false;
05.        }
06.        return true;
07.}
08.</script>
09.<form id="frm" action="https://www.google.co.jp/search" onsubmit="return checkForm();" target="_blank"></form>
10. 
11.<input type="text" name="q" required list="pg" form="frm">
12.<datalist id="pg">
13.<option value="PHP">
14.<option value="Java">
15.<option value="SQL">
16.</datalist>
17. 
18.<input type="submit" name="send" value="送信" form="frm">



PHP : mysqli を使用した単純な問合せアプリケーション

ダウンロード

▼ デモページ

データベースの接続に必要な値は、include_path に設定されたディレクトリに config.php を作成してセットします

▼ config.php
<?php
$host = "MySQLサーバ";
$user = "ユーザ";
$pass = "パスワード";
$db = "データベース";
?>

control.php ( エントリポイント )

01.<?php
02.# ***************************
03.# ソースベースの取り込み
04.# ***************************
05.require_once('settings.php');
06.require_once('model.php');
07.require_once('config.php');
08. 
09.# ***************************
10.# MySQL 接続
11.# ***************************
12.$con = @new mysqli($host, $user, $pass, $db);
13.$con->set_charset("utf8");
14. 
15.# ***************************
16.# MySQL 用特殊文字エスケープ
17.# ***************************
18.foreach( $_REQUEST as $key => $value ) {
19.        $_REQUEST["db"][$key] = $con->real_escape_string($value);
20.}
21. 
22.# ***************************
23.# テーブル表示
24.# ***************************
25.build_table($con);
26. 
27.# ***************************
28.# MySQL 接続解除
29.# ***************************
30.$con->close();
31. 
32.# ***************************
33.# 画面定義
34.# ***************************
35.require_once('view.php');
36.?>


view.php ( 画面定義 )

01.<?php
02.# **************************************
03.# js キャッシュ用
04.# **************************************
05.$tm = mktime();
06. 
07.# **************************************
08.# 画面定義
09.# ( Ruby や Python に合わせた画面形式 )
10.# **************************************
11.$out_client = <<<HTML
12.<!DOCTYPE html>
13.<html>
14.<head>
15.<meta charset="utf-8">
16.<meta content="width=device-width initial-scale=1.0 minimum-scale=1.0 maximum-scale=1.0 user-scalable=no" name="viewport">
17.<title>MySQL 問合せ</title>
18. 
19.<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
20.<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap.css" />
21. 
22.<script src="entry.js?{$tm}"></script>
23. 
24.<style>
25.body {
26.        margin: 0;
27.}
28. 
29.#main {
30.        padding: 0px 16px 3px 16px;
31.}
32. 
33.</style>
34.</head>
35.<body>
36.<div class="alert alert-dark">
37.        簡易タイトル
38.</div>
39.<div id="main" class="table-responsive">
40. 
41.        <table class="table table-hover">
42.        {$GLOBALS['table']}
43.        </table>
44. 
45.</div>
46.</body>
47.</html>
48.HTML;
49. 
50.print $out_client;
51. 
52. 
53.?>

model.php ( HTML 作成部分 )
01.<?php
02.# ***************************
03.# モデル用関数を定義する
04.# ***************************
05. 
06.# ***************************
07.# テーブル表示
08.# ***************************
09.function build_table($con) {
10. 
11.        // デバッグ用
12.        file_put_contents("debug.log", "build_table:開始\n" , FILE_APPEND );
13.        file_put_contents("debug.log", print_r( $con ,true ) , FILE_APPEND );
14. 
15.        // TR 内の HTML 文字列
16.        $lines = "";
17.        $sql= <<<SQL
18.                select
19.                        社員コード,
20.                        氏名,
21.                        フリガナ,
22.                        所属,
23.                        性別,
24.                        作成日,
25.                        更新日,
26.                        給与,
27.                        手当,
28.                        管理者,
29.                        DATE_FORMAT(生年月日,'%Y/%m/%d') as 生年月日
30.                from 社員マスタ
31.                where 氏名 like '%{$_REQUEST["db"]["nm"]}%'
32.SQL;
33. 
34.        $rs = $con->query($sql);     // エラー処理省略(本当は必要)
35.        //  デバッグ用
36.        file_put_contents("debug.log", print_r( $rs ,true ) , FILE_APPEND );
37. 
38.        // 列情報を取得( タイトル用 )
39.        $fields_data = $rs->fetch_fields();
40.        foreach( $fields_data as $field ){
41.                $lines .= "<th>{$field->name}</th>";
42.        }
43. 
44.        // 列データを取得
45.        while( $row = $rs->fetch_array(MYSQLI_BOTH) ) {
46.         
47.                $cells  = "";
48.                for( $i = 0; $i < $rs->field_count; $i++ ) {
49.                        $cells .= "<td>{$row[$i]}</td>";
50.                }
51.         
52.                $lines .= "<tr>{$cells}</tr>" . "\n";
53.        }
54. 
55.        // 埋め込み用グローバル変数へセット    
56.        $GLOBALS['table'] = $lines;
57. 
58.        //  デバッグ用
59.        file_put_contents("debug.log", "build_table:終了\n" , FILE_APPEND );
60. 
61.}
62. 
63. 
64.?>

settings.php ( 共通設定 )
01.<?php
02.// ***************************
03.// 共通処理( UTF8N で保存 )
04.// ***************************
05.error_reporting( E_ALL & ~E_NOTICE & ~E_STRICT );
06.ini_set('display_errors', '1');
07.ini_set('date.timezone', 'Asia/Tokyo');
08.ini_set('default_charset', 'utf-8');
09.session_cache_limiter('nocache');
10.session_start();
11.header( "Content-Type: text/html; charset=utf-8" );
12. 
13. 
14.foreach( $_REQUEST as $key => $value ) {
15. 
16.# ここで $_REQUEST 内の文字列のセキュリティ上の処理
17. 
18.}
19. 
20.# デバッグログの初期化
21.file_put_contents("debug.log", "開始\n" );
22. 
23.# メッセージ
24.$check_message = "";
25. 
26.# クライアントコントロール
27.$pass = "1";
28.?>




PHP による超簡易掲示板 ( JSONでデータを保存します ) / CSS でレスポンシブ対応

▼ ノーマル

▼ スマホ




保存データを行単位で区切り文字で分けて投稿データを保存する方法は古くからありますが、JSON 形式で保存しておくと、投稿データ内の改行やクォートなどのデータを自分で処理する必要がなくなる上に、新しい項目も追加するのが容易になります。さらに、データが JSON で作られるので、そのまま http で他のアプリケーションからアクセスする事も容易になります

一応、MVC にのっとり、M(model.php) / V(view.php) / C(board.php) になっています

board.php

error_reporting(E_ALL & ~E_NOTICE); は、$_POST 等の変数の参照時に未定義(ブラウザから送られていない)時にでも、空文字列が入っているとみなして処理できるようにするものです。逆に、全てのエラーを出力するようにした場合、代入されていな い値を使用した場合は、警告を発生します( 必要であれば、php.ini で設定します )

01.<?php
02.error_reporting(E_ALL & ~E_NOTICE);
03.// **************************************
04.// php.ini の output_buffering をチェックして
05.// 有効になっていた場合は、header の前に出力可能です
06.// **************************************
07. 
08.// **************************************
09.// 通常の HTML として出力します
10.// **************************************
11.header( "Content-Type: text/html; charset=utf-8" );
12. 
13.// **************************************
14.// キャッシュを無効にするヘッダ
15.// ※ いろいろあるのは念のため
16.// **************************************
17.header( "Expires: Thu, 19 Nov 1981 08:52:00 GMT" );
18.header( "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0" );
19.header( "Pragma: no-cache" );
20. 
21.// **************************************
22.// 関数の定義を読み込みます
23.// **************************************
24.require_once("model.php");
25. 
26.// **************************************
27.// $_POST['send'] != "" は送信ボタンが
28.// クリックされた事を示します
29.// さらに、テキストエリアに何か入力され
30.// た場合に処理を行います
31.// **************************************
32.$_POST['text'] = preg_replace( "/^[ \s]+/u", "", $_POST['text'] );
33.$_POST['text'] = preg_replace( "/[ \s]+$/u", "", $_POST['text'] );
34.if ( $_POST['send'] != "" && $_POST['text'] != "" ) {
35. 
36.        // データの書き込み処理
37.        post_data();
38. 
39.}
40. 
41.// データの表示処理
42.disp_data();
43. 
44. 
45.// **************************************
46.// ▼ 以下は画面です。$log_text を
47.//    埋め込んでいます
48.// **************************************
49.require_once("view.php");
50.?>

キャッシュ無効は、先頭に 
session_cache_limiter('nocache');
session_start();
でもいいと思います


FORM は一般的な POST メソッドで送信されます。なので、書き込んだ直後にリダイレクトして GET メソッドで呼び出しなおすという処理が入っています。タイトルの『超簡易掲示板 ( JSON )』をクリックすると、GET メソッドでの呼び出しであるリンクとなっています。

投稿データの表示内容は、いったん文字列で作成して後から view.php の該当部分に埋め込む形式です。最新のデータは、array_unshift によって、データの先頭に追加されます。

HTML 要素を無効にする方法としては、htmlentitieshtmlspecialchars がありますが、初心者向けとして最低限の置き換えを str_replace で実装しています。

json_encode による、オブジェクトから文字列の変換では、オプションの JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT でデバッグしやすいように可読性に重点を置いています。

model.php
001.<?php
002. 
003.// **************************************
004.// データの書き込み処理
005.// **************************************
006.function post_data() {
007. 
008.        // データを一括読み込み
009.        $log_text = @file_get_contents("board.log");
010. 
011.        $json = json_decode( $log_text );
012.        // 空のファイルかまたは、JSON データでは無い場合
013.        if ( $json === null ) {
014. 
015.                // JSON 用クラス作成
016.                $json = new stdClass;
017.                // 行データを格納する配列を作成
018.                $json->item = array();
019. 
020.        }
021. 
022.        // 改行コードを \n のみ(1バイト)にする
023.        $_POST['text'] = str_replace("\r","",$_POST['text']);
024. 
025.        // HTML 要素を無効にする
026.        $_POST['text'] = str_replace("<","&lt;",$_POST['text']);
027.        $_POST['text'] = str_replace(">","&gt;",$_POST['text']);
028. 
029.        // HTML 要素を無効にする
030.        $_POST['subject'] = str_replace("<","&lt;",$_POST['subject']);
031.        $_POST['subject'] = str_replace(">","&gt;",$_POST['subject']);
032.        $_POST['name'] = str_replace("<","&lt;",$_POST['name']);
033.        $_POST['name'] = str_replace(">","&gt;",$_POST['name']);
034. 
035.        // 新しい投稿用のクラス作成
036.        $board_data = new stdClass;
037. 
038.        // text プロパティに 入力された本文をセット
039.        $board_data->text = $_POST['text'];
040.        // subject プロパティに 入力されたタイトルをセット
041.        $board_data->subject = $_POST['subject'];
042.        // name プロパティに 入力された名前をセット
043.        $board_data->name = $_POST['name'];
044.        // subject プロパティに 入力されたタイトルをセット
045.        $board_data->datetime = $_POST['datetime'];
046. 
047.        // 配列の先頭に 新しい投稿データをセット
048.        array_unshift($json->item, $board_data);
049. 
050.        // 全ての投稿データを JSON として一括書き込み
051.        file_put_contents("./board.log", json_encode( $json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT ) );
052. 
053.        // GET メソッドで再表示します
054.        header( "Location: {$_SERVER["PHP_SELF"]}" );
055. 
056.        exit();
057. 
058. 
059.}
060. 
061.// **************************************
062.// データの表示処理
063.// **************************************
064.function disp_data() {
065. 
066.        // 埋め込み用データを global 宣言
067.        global $log_text;
068. 
069.        // データを一括読み込み
070.        $log_text = @file_get_contents("./board.log");
071.        // ファイルが存在しない場合
072.        if ( $log_text === false ) {
073.                $log_text = "ここに投稿データが表示されます";
074.                return;
075.        }
076. 
077.        $json = json_decode( $log_text );
078.        // 空のファイルかまたは、JSON データでは無い
079.        if ( $json === null ) {
080.                $log_text = "ここに投稿データが表示されます";
081.                return;
082.        }
083. 
084.        // 表示用の埋め込みに使用される文字列変数
085.        $log_text = "";
086.        foreach( $json->item as $v ) {
087.         
088.                // **************************************
089.                // 本文の改行は br 要素で表現します
090.                // **************************************
091.                $v->text = str_replace("\n", "<br>\n", $v->text );
092. 
093.                // **************************************
094.                // 記事の境界を hr 要素で表現します
095.                // **************************************
096.                $v->text .= "<hr>\n";
097. 
098.                // **************************************
099.                // 行毎に表示 HTML を作成
100.                // **************************************
101.                $log_text .= "<div class='title'>【{$v->subject}】( {$v->name} : {$v->datetime} ) </div>" . $v->text;
102.         
103.        }
104. 
105. 
106.}
107. 
108.?>

投稿時の日付データは、ブラウザ側でセットするようにしています。特に日付に関しては JavaScript ではスマートな方法が無いので、学習のきっかけ用としてこのようになっています。また、送信時のイベント処理としても重要なサンプルとなり、jQuery の基本サンプルでもあります。

※ jQuery は、Google のホスティングを使用しています。

view.php
01.<!DOCTYPE html>
02.<html>
03.<head>
04.<meta charset="utf-8">
05.<meta content="width=device-width initial-scale=1.0 minimum-scale=1.0 maximum-scale=1.0 user-scalable=no" name="viewport">
06.<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
07. 
08.<style>
09.* {
10.        font-family: "ヒラギノ角ゴPro W3","Hiragino Kaku Gothic Pro","メイリオ",Meiryo,"MS Pゴシック",Verdana,Arial,Helvetica,sans-serif;
11.}
12. 
13.textarea {
14.        height: 100px;
15.}
16. 
17.@media screen and ( min-width:480px ) {
18.        input {
19.                width: 400px;
20.        }
21.        textarea {
22.                width: 500px;
23.        }
24.}
25. 
26.@media screen and ( max-width:479px ) {
27. 
28.        input,textarea {
29.                width:100%;
30.        }
31. 
32.}
33. 
34..title {
35.        border: 1px solid #aaa;
36.        padding: 4px;
37.        margin-bottom: 6px;
38.}
39. 
40.</style>
41. 
42.<script>
43. 
44.$( function(){
45. 
46.        // フォーム送信イベント
47.        $("form").on("submit", function(){
48. 
49.                // 日付文字列をクライアントで作成して送信
50.                var dateNow = new Date();
51.                var dateString =
52.                        dateNow.getFullYear() + "/" +
53.                        ("0"+(dateNow.getMonth()+1)).slice(-2)+ "/" +
54.                        ("0"+(dateNow.getDate())).slice(-2);
55.                var timeString =
56.                        ("0"+(dateNow.getHours())).slice(-2) + ":" +
57.                        ("0"+(dateNow.getMinutes())).slice(-2) + ":" +
58.                        ("0"+(dateNow.getSeconds())).slice(-2);
59. 
60.                // hidden フィールドにセット
61.                $("#datetime").val( dateString + " " + timeString );
62. 
63.        });
64.});
65. 
66.</script>
67.</head>
68. 
69.<body>
70.<h3><a href="board.php" style="color:black;">超簡易掲示板 ( JSON )</a></h3>
71. 
72.<form method="POST">
73.        <div>タイトル <input type="text" name="subject"></div>
74.        <div>名  前 <input type="text" name="name"></div>
75.        <div><textarea name="text"></textarea>
76.        <input type="hidden" name="datetime" id="datetime"></div>
77.        <div><input type="submit" name="send" value="送信"></div>
78.</form>
79.<br>
80. 
81.<?= $log_text ?>
82. 
83.</body>
84.</html>

JSON は、item プロパティが配列になり、複数項目の投稿データが格納されます。

JSON データ
01.{
02.    "item": [
03.        {
04.            "text": "最低限の機能を持った掲示板です。\nデータ形式は JSON でとても拡張しやすく便利です。",
05.            "subject": "こんにちは",
06.            "name": "山田 タロウ",
07.            "datetime": "2019\/02\/22 13:48:00"
08.        }
09.    ]
10.}




PHP で MySQL のクエリテストする為のコード( Bootstrap 仕様 )

$_GET['text'] で入力された SQL が引き渡されます。
※ GET コマンドなので、IE11 以外ならば SQLは アドレスバーで直接入力ができると思います。
※ php-mysql-test.php?text=SQL文

テーブルの表現には Bootstrap を使用しています( 一応スマホではテーブル部分のみ横スクロールします )

QueryString に text が無い場合と text に有効な文字が全く無い場合は  show variables でシステム変数の一覧を表示します
001.<?php
002.// キャッシュを使用しない
003.session_cache_limiter('nocache');
004.session_start();
005. 
006.// UTF-8
007.header( "Content-Type: text/html; charset=utf-8" );
008. 
009.?>
010.<!DOCTYPE html>
011.<html>
012.<head>
013.<meta charset="utf-8">
014.<meta content="width=device-width initial-scale=1.0 minimum-scale=1.0 maximum-scale=1.0 user-scalable=no" name="viewport">
015.<title>SQL実行結果</title>
016.<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap.css" />
017.<style>
018./*
019.罫線等のテーブルのレイアウトは、Bootstrap にて適用
020.table {
021.        border: solid 1px #000;
022.        border-collapse: collapse;
023.}
024. 
025.th,td {
026.        border: solid 1px #000;
027.        padding: 5px;
028.}
029.*/
030.</style>
031.</head>
032.<body>
033.<!-- Bootstrap の alert でタイトル -->
034.<div class="alert alert-dark">
035.        MySQL Query TEST
036.</div>
037. 
038.<?php
039.if ( !isset( $_GET['text'] ) || trim($_GET['text']) == "" ) {
040.        // クエリ初期値は システム変数一覧
041.        $_GET['text'] = "show variables";
042.}
043. 
044.// P で挟んだデータの出力
045.print_cell_html( "p", $_GET['text'] );
046. 
047.$server = 'localhost';
048.$dbname = 'lightbox';
049.$user = 'root';
050.$password = 'パスワード';
051. 
052.// ***************************
053.// 接続
054.// ***************************
055.$mysqli = @ new mysqli($server, $user, $password, $dbname);
056.if ($mysqli->connect_error) {
057.        print "接続エラーです : ({$mysqli->connect_errno}) ({$mysqli->connect_error})";
058.        exit();
059.}
060. 
061.// ***************************
062.// クライアントの文字セット
063.// ***************************
064.$mysqli->set_charset("utf8");
065. 
066.// ***************************
067.// クエリ
068.// ***************************
069.$result = $mysqli->query($_GET['text']);
070.if ( !$result ) {
071.        print "\n";
072.        print "<span style='color:#f00'>error : " . $mysqli->error . "</span>";
073.        exit();
074.}
075. 
076.// ***************************
077.// 列数
078.// ***************************
079.$nfield = $result->field_count;
080.if ( $nfield ) {
081.        $ncount = 0;
082.        print "<div class='table-responsive-sm'>";
083.        print "<table class='table table-bordered table-hover'><thead class='thead-dark'>\n";
084. 
085.        // 行番号用タイトル
086.        print "\t<th></th>";
087. 
088.        // 列のタイトルを作成
089.        $field = $result->fetch_fields( );
090.        for( $i = 0; $i < $nfield; $i++ ) {
091. 
092.                // TH で挟んだデータの出力
093.                print_cell_html( "th", $field[$i]->name );
094. 
095.        }
096. 
097.        print "</thead>\n<tbody>\n";
098. 
099.        // ***************************
100.        // 行データ
101.        // ※ 結果の行を数値添字配列で取得
102.        // ***************************
103.        while ($row = $result->fetch_row()) {
104. 
105.                print "<tr>\n\t";
106.                // 行番号
107. 
108.                // TDで挟んだデータの出力
109.                print_cell_html( "td", ($ncount + 1) );
110. 
111.                for( $i = 0; $i < $nfield; $i++ ) {
112. 
113.                        // TDで挟んだデータの出力
114.                        print_cell_html( "td", $row[$i] );
115. 
116.                }
117.                print "\n</tr>\n";
118. 
119.                // 行番号
120.                $ncount++;
121.        }
122. 
123.        print "</tbody></table>";
124.        print "</div>";
125. 
126.}
127. 
128.// ***************************
129.// 接続解除
130.// ***************************
131.$mysqli->close();
132. 
133. 
134.// ***************************
135.// セルの HTML 出力関数
136.// ***************************
137.function print_cell_html( $html, $data ) {
138. 
139.print <<<CELL_HTML
140.<{$html}>{$data}</{$html}>
141.CELL_HTML;
142. 
143.}
144. 
145.?>
146. 
147.</body>
148.</html>