Kintarou'sBlog

プログラミング学習中。学習内容のアウトプットや読書で学んだことなど随時投稿!

【PHP】POST送信による投稿機能の実装

こんにちは😊Kintarouです。

現在エンジニア転職を目指してプログラミング学習中です👨‍🎓
夢はフリーランスエンジニアになって働く人にとって働く事が楽しくなるシステムを作ること!
と、愛する妻と海外移住すること🗽

プログラミングや読んでいる本のことなど、ブログに書いていきます!
twitter : https://twitter.com/ryosuke_angry


今回参考にさせて頂いたサイト様🙇‍♂️ dotinstall.com


前回までの記事はGETメソッドによるデータの送信でしたが、今回はPOSTメソッドによるデータ送信を用いた投稿機能を実装してみます。

投稿機能の雛形を作る

投稿機能の雛形を作成します。
POST形式で送ったテキストデータを使い、messages.txtファイルを編集するという流れです。

ファイルを編集・操作する関数に関しては過去記事にまとめております。

ryosuke-toyama.hatenablog.com

■messages.txt

hello
hi

2行分だけテキストを入れておきました。
まずはこのデータをindex.phpで表示します。

■index.php

<?php

function h($str)
{
  return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}

#messages.txtのテキスト内容を行毎に$messagesに配列形式で格納します。
#FILE_IGNORE_NEW_LINESにより\nは無視されています。
$filename = '../app/messages.txt';
$messages = file($filename, FILE_IGNORE_NEW_LINES);

include('../app/_parts/_header.php');

?>

<!-- $messagesをforeach文で出力します。 -->
  <ul>
  <?php foreach ($messages as $message): ?>
    <li><?= h($message); ?></li>
  <?php endforeach; ?>
  </ul>

  <form action="result.php" method="post">

  <!-- POST形式で新たなテキストデータを送れるようにしておきます。 -->
    <input type="text" name="message">
    <button>Post</button>
  </form>

f:id:ryosuke-toyama:20201105204617p:plain
上記のようにmessages.txtのデータが表示されています。
新たにPOST形式で送ったテキストデータを使い、result.phpでmessages.txtに追記していきます。

■result.php

<?php

#filter_input()関数でPOSTで送られたデータを$messageに代入します。
$message = trim(filter_input(INPUT_POST, 'message'));

#記述が何もない場合は'...'を代入します。
$message = $message !== '' ? $message : '...';

#fwrite()関数で$messageを追記し、\nで改行しておきます。
$filename = '../app/messages.txt';
$fp = fopen($filename, 'a');
fwrite($fp, $message . "\n");
fclose($fp);

include('../app/_parts/_header.php');

?>

  <p>Message added!</p>
  <p><a href="index.php">Go back</a></p>

f:id:ryosuke-toyama:20201105205217p:plain

f:id:ryosuke-toyama:20201105205241p:plain

f:id:ryosuke-toyama:20201105205303p:plain

これで投稿機能が出来ました。

ただこの状態ではresult.phpにmessages.txt編集の記載がされているので、result.phpにURLから直接アクセスしたりページをリロードすると再投稿されるという事になってしまいます。

それを避けるには、index.phpで処理を行ってからresult.phpにリダイレクトする事によって解決できます。

URLの直アクセスやリロードに対応する

先ほどのファイル編集部分の記述をindex.phpに記載し、処理が出来たらresult.phpにリダイレクトするという形にします。

■index.php

<?php

function h($str)
{
  return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}

#下記2箇所で$filenameを使う機会があるのでdefine()で定数化します。
define('FILENAME', '../app/messages.txt');

#$_SERVERという特殊な変数でPOSTでアクセスされた場合に行う処理を記述出来ます。
#これにより、URLでの直アクセスに対応できます。
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  $message = trim(filter_input(INPUT_POST, 'message'));
  $message = $message !== '' ? $message : '...';
  
  $fp = fopen(FILENAME, 'a');
  fwrite($fp, $message . "\n");
  fclose($fp);

  #messages.txtが編集出来たらresult.phpにリダイレクトします。
  header('Location: http://localhost:8080/result.php');
  exit;
}

$messages = file(FILENAME, FILE_IGNORE_NEW_LINES);

include('../app/_parts/_header.php');

?>

  <ul>
  <?php foreach ($messages as $message): ?>
    <li><?= h($message); ?></li>
  <?php endforeach; ?>
  </ul>

  <!-- 同じファイル内で処理をする場合はactionを空欄(もしくはactionの記述なし)で処理できます。 -->
  <form action="" method="post">
    <input type="text" name="message">
    <button>Post</button>
  </form>

ファイル編集部分がindex.phpに移った事により、result.phpの記述は簡単になります。

■result.php

<?php

include('../app/_parts/_header.php');

?>

  <p>Message added!</p>
  <p><a href="index.php">Go back</a></p>

これでURLでの直アクセスや、リロードに対応できます。

補足

この記事では別のサイトからアクセスされるCSRFに対応出来ていません。 CSRFに関しては過去記事の説明を再掲しておきます。

クロスサイトリクエストフォージェリ(CSRF)

ユーザーによる認証が完了したと考えられるWebアプリケーションのページに、悪意のあるコードやリンクを仕込む攻撃方法。
そのWebアプリケーションへのセッションがタイムアウトしていなければ、攻撃者は本来認証されていないはずのコマンドを実行できてしまいます。
GETとPOSTを適切に使い分けることと、上記のセキュリティトークンをリクエストに追加することで対策することができます。
ryosuke-toyama.hatenablog.com

PHPでのトークンの実装は別記事にまとめます。

以上、どなたかの参考になれば幸いです😊