初心者が月10万稼ぐ為の記録５（掲示板作成１）

#シンプルな掲示板作成

###目的
**機能をシンプルにしてPHPの基礎をしっかりみにつける
自分のHPにも活用する
コードの1行1行をしっかり理解する**


```php
<?php

//メッセージを保存するファイルのパス設定
define( 'FILENAME' , './message.txt');

//タイムゾーン設定
date_default_timezone_set('Asia/Tokyo');

//変数の初期化
$now_date = null;
$data = null;
$file_handle = null;
$split_data = null;
$message = array();
$message_array = array();
$success_message = null;
$error_message = array();
$clean = array();


//データが渡されたか確認
if( !empty($_POST['btn_submit']) ) {
	// var_dump($_POST);
	
	//表示名の入力チェック
	if( empty($_POST['view_name']) ) {

		$error_message[] = '表示名を入力してください。';

	} else {

		$clean['view_name'] = htmlspecialchars($_POST['view_name'],ENT_QUOTES);

		$clean['view_name'] = preg_replace('/\\r\\n|\\n|\\r/','',$clean['view_name']);
	}

	//メッセージの入力チェック
	if( empty($_POST['message']) ) {

		$error_message[] = 'メッセージを入力してください。';

	} else {

		$clean['message'] = htmlspecialchars($_POST['message'],ENT_QUOTES);

		$clean['message'] = preg_replace('/\\r\\n|\\n|\\r/','<br>',$clean['message']);
	}

	//未入力項目があるかチェック
	if( empty($error_message) ) {
	
		//ファイルへアクセス
		if( $file_handle = fopen(FILENAME, "a") ) {
			
			//書き込み日時を取得
			$now_date = date("Y-m-d H:i:s");

			//書き込むデータを作成
			$data = "'".$clean['view_name']."','".$clean['message']."','".$now_date."'\n";

			//書き込み
			fwrite($file_handle, $data);

			//ファイルを閉じる
			fclose($file_handle);

			$success_message = 'メッセージを書き込みました。';
		}
	}
}

//ファイルへアクセス
if( $file_handle = fopen(FILENAME, "r") ) {

	while ( $data = fgets($file_handle) ) {

		$split_data = preg_split( '/\'/', $data);

		// var_dump($split_data);
		// echo "<br>";

		//$messageに連想配列の形で取得したデータを格納する
		$message = array(
			'view_name' => $split_data[1],
			'message' => $split_data[3],
			'post_date' => $split_data[5]
		);

		array_unshift($message_array, $message);
	}

	//ファイルを閉じる
	fclose($file_handle);
}


?>

<!DOCTYPE html>
<html>
<head>
	<title>掲示板</title>
	<meta charset="utf-8">
</head>
<body>

<h1>掲示板</h1>

<?php if( !empty($success_message) ): ?>
	<p><?php echo $success_message; ?></p>
<?php endif; ?>

<?php if( !empty($error_message) ): ?>
	<ul>
		<?php foreach ($error_message as $value): ?>
			<li><?php echo $value; ?></li>	
		<?php endforeach; ?>
	</ul>
<?php endif; ?>	

<form method="post">
	<dir>
		<label for="view_name">表示名</label>
		<input id="view_name" type="text" name="view_name" value="">
	</dir>

	<div>
		<label for="message">メッセージ</label>
		<textarea id="message" name="message"></textarea>
	</div>
	<input type="submit" name="btn_submit" value="書き込む">
</form>

<hr>

<section>
<?php if( !empty($message_array) ): ?>
<?php foreach ($message_array as $value): ?>

<h2><?php echo $value['view_name']; ?></h2>
<time><?php echo date('Y年m月d日 H:i', strtotime($value['post_date'])); ?></time>

<p><?php echo $value['message']; ?></p>

<?php endforeach; ?>
<?php endif; ?>	

</section>

</body>
</html>
```
👆のコードを理解する為に

👇１行１行理解する。

##受け取ったデータを表示する

```php
var_dump($_POST);
```
$_POSTにデータが代入されると次のように表示される。

**array(3) { ["view_name"]=> string(6) "太郎" ["message"]=> string(27) "おいしかったです。" ["btn_submit"]=> string(12) "書き込む" }**

👆
「書き込む」ボタンが押されると、POST形式による通信で「表示名」と「メッセージ」が送信されます。
そして、先ほどは空だった$_POSTにデータが自動的に代入されるため、👆のように入力した内容が表示されます。


##データが渡されたか確認する
```php
if( !empty($_POST['btn_submit']) )
```
入力したデータが「ある」→メッセージを書き込む
入力したデータが「ない」→掲示板を表示する

empty関数は変数が空であればtrueを返す関数
今回は「空じゃなければtrue」にしたいので関数の前に「！」をつける。


##受けっとったデータをファイルに保存する

メッセージを保存するファイルは「message.txt」として設置する。

```php
//メッセージを保存するファイルのパス設定
define( 'FILENAME' , './message.txt');
```
変更することがないので定数を使用する。
defineは閲覧者に表示するメッセージなどを定義するときに便利。

###PHPから「message.txt」にアクセスして、受け取ったデータを書き込む


**fopen関数を使用してファイルにアクセスする。**

```php
if( $file_handle = fopen(FILENAME, "a") )
```

1つ目のパラメータはファイル名を含めたパスを指定する。
（今回defineで宣言した「FILENAME」）

2つ目のパラメータは
ファイルを読み書き（入出力）するのに必要な情報で
「ファイルポンターリソース」と呼ばれる
読み込みだけを行うなら「r」、
書き込みを行うなら「w」「a」がある。
「w」はファイル内容を一旦リセットして書き込む。
「a」は末端から追記する形で書き込む。
今回は「a」を選択。

ファイルを開くと「$file_handle」に「ファイルポインターリソース」と呼ばれるファイルへアクセス情報が代入される。

**「ファイルポンターリソース」とは
ファイルを読み書き（入出力）するのに必要な情報のことで、
アクセスするファイルの目印、位置
今回では「a」のことだと思う。**

**この段階で実行すると「msessage.txt」が作成される。**

###開いたファイルにデータを書き込む

```php
//タイムゾーン設定
date_default_timezone_set('Asia/Tokyo');
```
date_default_timezone_set関数は、メッセージが投稿された時間を日本のタイムゾーンで取得して書き込むために実行している。

```php
//書き込み日時を取得
$now_date = date("Y-m-d H:i:s");
```
メッセージを書き込まれた日時を取得して「$now_date」に代入する。

###ファイルに書き込むデータを作成します。

**'太郎','おいしかったです。','2020-05-12 10:58:08'**
の形式で書き込む。

```php
//書き込むデータを作成
$data = "'".$_POST['view_name']."','".$_POST['message']."','".$now_date."'\n";
```
それぞれのテキストを「’（シングルクォーテーション）」で囲み、「表示名」「メッセージ」「投稿日時」をそれぞれ「，（コンマ）」で区切る。
「\n（改行）」で区切る。

途中の「．（ドット）」は文字列連結子と呼ばれるもので、前後の文字列をくっつける役割を持つ。

**fwrite関数でファイルへ書き込み**

```php
//書き込み
fwrite($file_handle, $data);
```

「fwrite」関数でファイルへの書き込みを行う。
書き込み対象となるファイルを判別するために
1つ目のパラメータに「file_handle」これはfopen関数で指定した「ファイル名を含めたパス」と「ファイルポンターリソース」
2つ目のパラメータは書き込むデータを作成した際の
「$_data」を指定することで、先ほど作成したデータが「message.txt」に追記保存される。

メッセージは新しいものをファイルの未尾にどんどん追記するため、上から古い順に並ぶ。（fopen関数で「a」を指定してる）

**'太郎','おいしかったです。','2020-05-12 11:56:14'
'次郎','まずかった','2020-05-12 11:56:41'**

##ファイルからデータを読み込んで、掲示板にメッセージを表示しする

**メッセージのデータをファイルから取得する**

fopen関数を使用してファイルにアクセスします。

```php
if( $file_handle = fopen(FILENAME, "r") )
```
今回は読み込みなのでパラメータは「r」にする。


**while文の条件式の中で「fgets」関数を呼び出す。**

```php
while ( $data = fgets($file_handle) )
```
コードをを実行すると、「message.txt」のデータが読み込まれて次のように表示される。

![掲示板_fgets関数.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631322/e745b3ae-23d3-8b61-2f80-2e7e384158a1.png)
👆
読み込んだデータが表示される。

「fgets関数」とはファイルから1行ずつデータを取得する関数
fgets関数は1度実行して1行読み込むと、ファイルポインターリソースの位置はそのたびに更新され、ファイルが終わるまで1行づつデータを読み込むとができる。（while文を使用）


###読み込んだデータを配列に格納する

```php
$split_data = preg_split( '/\'/', $data);
var_dump($split_data);
```

**「preg_split関数」は文字列を特定の文字で分割する関数。**

実行すると

**array(7) { [0]=> string(0) "" [1]=> string(6) "太郎" [2]=> string(1) "," [3]=> string(27) "おいしかったです。" [4]=> string(1) "," [5]=> string(19) "2020-05-12 11:56:14" [6]=> string(1) " " } '太郎','おいしかったです。','2020-05-12 11:56:14'**

👆
読み込まれたデータは配列に分割されます。

分かりやすく整理
array(7) { 
[0]=> string(0) "" 
[1]=> string(6) "太郎" 
[2]=> string(1) "," 
[3]=> string(27) "おいしかったです。" 
[4]=> string(1) "," 
[5]=> string(19) "2020-05-12 11:56:14" 
[6]=> string(1) " " 
} 

**表示名[1]**
**メッセージ[3]**
**投稿日時[5]**
からアクセスできることがわかる。

**$messageに連想配列の形で取得したデータを格納する。**

```php
$message = array(
	'view_name' => $split_data[1],
	'message' => $split_data[3],
	'post_date' => $split_data[5]
);
var_dump($message);
echo "<br>";
```
👆実行すると
array(3) { ["view_name"]=> string(6) "太郎" ["message"]=> string(27) "おいしかったです。" ["post_date"]=> string(19) "2020-05-12 11:56:14" }

分かりやすく整理
array(3) { 
["view_name"]=> string(6) "太郎" 
["message"]=> string(27) "おいしかったです。" 
["post_date"]=> string(19) "2020-05-12 11:56:14" 
}

**message_arrayにmessageごと格納します。**

```php
array_unshift($message_array, $message);
```
👆
この操作を投稿されたメッセージの数だけ繰り返すと、$message_arrayに全てのメッセージのデータが入る。
らしい・・・？？

###HTMLにメッセージを出力する

```php
<?php if( !empty($message_array) ): ?>
```
👆
$message_arrayが値がはいってるか、はいってないかをチェックして、そもそも表示するメッセージがあるか確認する。

```php
<?php foreach ($message_array as $value): ?>
```
👆
メッセージがあると、その中のforeach文でメッセージの件数分だけループさせる。

```php
<h2><?php echo $value['view_name']; ?></h2>
```
👆
名前を表示させる。

```php
<time><?php echo date('Y年m月d日 H:i', strtotime($value['post_date'])); ?></time>
```
👆
書き込み日時を表示

文字列形式になっている時間を**「strtotime関数」**でタイムスタンプ形式に変換する。

**「date関数」**で時刻を初期化して「‘Y年m月d日 H:i’」の形で取得し、出力する。


```php
<p><?php echo $value['message']; ?></p>
```
👆
メッセージを表示させる。


##メッセージが無事に投稿できたことを知らせる

**投稿完了メッセージを用意**

```php
$success_message = null;
```
👆
投稿されたメッセージをファイルに書き込んだ後に表示したいメッセージを変数に入れて用意。
今回は**$success_message**に入れる。

```php
$success_message = 'メッセージを書き込みました。';
```
👆
**$success_message**にはファイルへの書き込みがあった場合のみ値が代入されるため、
変数に値が入っているか調べることでメッセージを表示するか判断することができる。

###HTMLにメッセージ出力を設定

```php
<?php if( !empty($success_message) ): ?>
	<p><?php echo $success_message; ?></p>
<?php endif; ?>	
```
👆
**empty関数**で**$success_message**に値がはいってるか確認。
（※最初のボタンが押されたかどうかを調べるときと同じ）
**empty関数**は値が空（入ってない）の場合に**true**を返すが、今回は逆に値が入っていたら**true**としたいため関数の前に**「！」**をつける。

##メッセージがちゃんと入力されているか確認する
今の掲示板だと、「表示名」や「メッセージ」に何も入力しなくて「書き込み」ボタンを押しても、内容がない状態で投稿され、後、「メッセージ書き込みました。」と表示されてしまう。

###未入力のときはエラーメッセージを表示して書き込みができないようにする

###エラーメッセージを用意

```php
$error_message = array();
```
👆
今回は「表示名」と「メッセージ」の２つ以上入る可能性があるため、メッセージが入る変数
**$error_message**は配列形式にします。

```php
//表示名の入力チェック
if( empty($_POST['view_name']) ) {		
	$error_message[] = '表示名を入力してください。'
}
```
👆
**empty関数**で表示名のデータが入ってる**$_POST['view _ name']**が空いてるかを調べ、
空だった場合に**$error_message**へメッセージを代入する。

###同じように「メッセージ」についても未入力バリデーション（確認）をつける。

```php
//メッセージの入力チェック
if( empty($_POST['message']) ) {
	$error_message[] = 'メッセージを入力してください。';
	}
```
👆
メッセージが入っている**$_POST['message']**が空いてるかを調べ、空だった場合
**$error_message**へメッセージを追加する。

これで「表示名」と「メッセージ」が未入力であるかを確認する機能をつけた。

###HTMLにてメッセージを表示する

先ほど作成した
**$error_message**に値（表示名を入力してください。メッセージを入力してください）が
入っていたら表示するように設定する。

```php
<?php if( !empty($error_message) ): ?>
```
👆
**empty関数**を使って値が入っているか確認する。今回は**「！」**がついてるので値が入ってたら**true**になり、**エラーメッセージ**が表示される。




```php
<ul>
    <?php foreach ($error_message as $value): ?>
	<li><?php echo $value; ?></li>	
    <?php endforeach; ?>
</ul>
```
👆
**今回の変数$error_messageは配列形式になっているため、foreach文を使って配列の値の数だけメッセージを表示する。**

**$error_message**から１つずつ値を取り出して**$value**に代入し、そのまま**li要素**の中に**echo関数**で出力する。
foreach文によって、エラーメッセージが１つでも、２つ以上ある場合でも同じようにメッセージを表示できるようになる。

**これで、「表示名を入力してください」、「メッセージ入力してください」とメッセージを表示できるようになる。**

###未入力項目があると書き込みしなようにする
「表示名」「メッセージ」がいずれかが未入力だった場合に、ファイルへの書き込みを行いわないように設定する。

```php
if( empty($error_message) )
```
👆
未入力の項目があった場合を確認する方法として、**$error_message**が空であるかを確認し行う。
確認後、**$error_message**に値が入ってなければ未入力の項目はなかったと判断することができる。

##投稿されたデータをサイタイズする
**・サイタイズとは入力データを無害化する機能**
**・PHPでは主に「htmlspecialchars関数」を使ってサイタイズを行う**

###不正なメッセージの投稿からシステムを守る

PHPでサニタイズを行う場合は**「htmlspecialchars関数」**を使う。

この関数は記号をHTMLエンティティ（実体）という形式に変換することでコードの無害化を行います。

コード例
**htmlspecialchars( $data, ENT_QUOTES);**

１つ目のパラメータにサニタイズしたい値、
2つ目のパラメータには変換する記号を指定する。

```php
$clean = array();
```
👆
**$clean**にサニタイズした値が入るようにする。



```php
$clean['view_name'] = htmlspecialchars($_POST['view_name'],ENT_QUOTES);
```

```php
$clean['message'] = htmlspecialchars($_POST['message'],ENT_QUOTES);
```
👆

「表示名」「メッセージ」の2か所設定する。

サニタイズの処理自体は、入力チェックの部分で「空じゃなかった場合」
に実行されるように**if文**に**else文**を加える形で追記する。

もし、未入力だった場合はエラーメッセージを作成し、
入力あった場合のみサニタイズを行うようにする。

###入力されたデータの中に改行がある場合の対応

現段階で、入力に改行して書き込みをするとエラー通知が発生する。

表示名については改行を削除し、メッセージは**br要素**へ置き換えるコードをそれぞれ追記する。

```php
$clean['view_name'] = preg_replace('/\\r\\n|\\n|\\r/','',$clean['view_name']);
```
👆
「表示名」は改行コード**「\r\n」「\n」「\r」**を検索し、空文字に置き換え削除する。


```php
$clean['message'] = preg_replace('/\\r\\n|\\n|\\r/','<br>',$clean['message']);
```
👆
メッセージは**「<ｂｒ>」**に置き換える。

###ファイル書き込みのコードにもサニタイズした値を使用するように変更する。

```php
$data = "'".$clean['view_name']."','".$clean['message']."','".$now_date."'\n";
```
👆
**$_POST['view＿name']**から→**$clean['view_name']**に置き換え。

**$_POST['message']**から→**$clean['message']**に置き換え。

ファイルがサニタイズされた値が書き込まれる。

**次回から**
今回までは書き込まれたデータをテキストファイルに書き込んできたが、次からデータベースに保存していくようにする。