【初心者向け】PHPで簡単なCRUD機能を実装するための手順

初めまして、やんと申します。
現在Webエンジニアへの転職を目標に、プログラミングを独学で学習しております。また、Twitterで学習内容について日々発信をしています。

[やん@公務員×独学プログラミング](https://twitter.com/yansabro)【Twitterアカウントのリンク】

<br>
現在PHPを学習中で、初めてCRUD機能をもつ作品を完成させることができました。自分のアウトプットも兼ねて、PHP初心者向けに解説記事を書いていこうと思います。

また、この記事をご覧になる前に下記の留意点について目を通していただくようお願いします。

<br>
**【留意点】**
本記事は、主に筆者がCRUD機能作品を完成させるまでに行ったこと・行った方がよかったことについて記述します。効率的な開発方法を解説するものではございません。プログラミング初心者の立場で解説をしてみるという試みです。
記事の内容に間違いや改善点などございましたら、ぜひコメントでご指摘ください。自分を含め初心者にとってより学びのある機会になると思います。
ご理解ご協力の程よろしくお願い致します。



<br>
# 目次
1. [この記事の対象読者]()

1. [作品の機能紹介]()
    1. [機能概要]()
    1. [機能詳細の紹介]()
1. [本記事の最終目標]()
1. [開発の準備を始めよう]()
    1. [開発環境などについて]()
    1. [設計図を作成しよう]()
    1. [データベース設計をしよう]()
1. [コードを書いていこう]()
    1. [まずは設計図通りにHTMLで画面の基本構成を作ろう]()
    1. [phpMyAdminに接続させよう]()
    1. [変数・セッション・クッキーの値の保存期間について]()
    1. [セッションを活用しよう]()
    1. [formタグで同じ画面を再度呼び出す理由]()
    1. [データベースに入力内容を登録しよう]()
    1. [ログイン画面を作ろう]()
    1. [投稿一覧画面を作ろう]()
    1. [投稿内容詳細画面を作ろう]()
    1. [投稿編集画面を作ろう]()
    1. [投稿削除機能を作ろう]()
    1. [ログアウト機能を作ろう]()
1. [参考にした書籍や教材など]()



<br>
<a id="anchor1"></a>
#この記事の対象読者

- Progate 、 ドットインストール 、 Udemy でPHPの基礎を学習した方

- PHPで初めて何かを作りたいと考えている方　　　　　　　　　　　　　　　など

<br>
※この記事では、基本的なHTML、CSS、PHPのコードの説明は割愛します。強調したい部分や説明が必要であると判断した部分については、適宜補足します。



<br>
#作品の機能紹介



##機能概要
お問合せフォーム兼掲示板のようなものを作成しました。
ユーザー登録、ログイン・ログアウト機能を実装しています。また、各ユーザーが投稿したお問合せ内容は、お問合せ一覧画面で確認することができます。一度投稿した内容は、投稿したユーザーのみ編集・削除が可能です。
<br>
完成品を動画で確認したい方は、下記の自分のツイートをご覧ください。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">お問合せフォーム兼掲示板が完成しました！<br><br>使用言語：HTML・CSS・PHP<br><br>CRUD機能の練習のために作成しました<br>もっと他の機能も追加したかったのですが、今回はひとまずこれで完成にします<br><br>リプ欄に機能詳細を書いておきます
<a href="https://t.co/I0yQOBhOAx">pic.twitter.com/I0yQOBhOAx</a></p>&mdash; やん@公務員×独学プログラミング (@yansabro) <a href="https://twitter.com/yansabro/status/1246775702520643585?ref_src=twsrc%5Etfw">April 5, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>



<br>
<a id="anchor3"></a>
##機能詳細の紹介
1. ユーザー登録が可能。データベースに「名前」、「メールアドレス」、「パスワード」、「アイコン画像」を登録し、ユーザー管理をする。

1. ユーザー登録画面では、メールアドレスが既に登録されているものであれば、エラー文を出しつつ、ユーザー登録を拒否する。
1. ログイン画面でメールアドレス・パスワードを入力することでログインできる。
1. ログイン時に、データベースに既に登録されているメールアドレスが入力された場合、重複していることについてエラー文を出しつつ、ログインを拒否する。ログインが成功したら、セッションにログインユーザー情報を保存し、１時間ログイン状態を保存する。
1. ユーザー自身がお問合せ内容を投稿できる。また、全ユーザーが投稿した内容を、投稿一覧画面で確認できる。
1. 投稿一覧画面で気になる投稿をクリックすると、投稿内容詳細画面に移動する。ログインユーザーと投稿したユーザーが同じ場合のみ、投稿編集画面に移動できる。
1. 投稿編集画面で編集した内容は、データーベースに「最終編集日時」とともに登録される。
1. 投稿一覧画面では、ログインユーザーと投稿したユーザーが同じ場合のみ、投稿削除ボタンが表示される。
1. 投稿一覧画面のログアウトを押すと、セッションのログインユーザー情報が削除される。

<br>

#本記事の最終目標
**★読者の方が、PHPで最低限のCRUD機能を実装できるようになること**

この記事では文章量の関係上、上記機能詳細の１・３・４（セッションのみ）・５・６・８・９の一部のみ解説し、CRUDの最低限の機能の作り方を解説します。
アイコン画像、メールアドレス重複エラー、ログインエラー、ページネーション機能などの説明は割愛させていただきますので、ご了承ください。

その他の機能については、こちらのGitHubのリポジトリにソースコードを全て公開しておくのでご確認ください（※現在準備中です、しばしお待ちください。）。

<br>
#開発の準備を始めよう



<a id="anchor2"></a>
##開発環境などについて

- OSはMac、ブラウザはChromeを使用

- 使用言語は、 HTML　CSS　PHP 
- テキストエディタは、Visual Studio Codeを使用
- MAMP（マンプ）を使用し、ローカルサーバーで開発
- データベースは、phpMyAdminを使用
- PHPのバージョンは 7.1.33 、 MySQLのバージョンは 5.7.26 

<br>
※この記事では、具体的な開発環境構築についての解説は割愛します。「上記のキーワード　＋　ダウンロード」等で検索すると、わかりやすい記事がたくさん出てきます。
どうしても文字だけではわからないという方は、
Taniguchi Makoto さんが[PHP+MySQL（MariaDB） Webサーバーサイドプログラミング入門](https://www.udemy.com/share/101XkACEsZdV9WR3w=/)
というUdemyの教材で動画にて解説しておりますので、そちらをご覧ください。



<br>
##設計図を作成しよう
コードを書き始める前に設計図を作成します。それと同時に、作品の最終目標もざっくりと決めます。この時点で完璧なものを作ることはできないと思いますが、今頭の中にあるものを全て書き出しましょう。
<br>
設計図を作成するメリットは主に２つです。

 - 全体構成を図で書き出すことで、足りないものや実装すべき機能が視覚的に見えてくる
 
 - 開発の指針となり、ブレを少なくすることができる

<br>
<br>
ユーザーが実際に利用する際に、どのような画面をみて、どのような導線で移動していくのかをイメージしながら全体構成を決定し、図で書き出していきます。視覚的な情報にすることで、このページがあったら便利だよな、ここに一文説明書きがあったほうが良いよな等といったアイデアがより湧きやすくなると思います。
<br>
また、開発の途中でもアイデアがどんどん湧いてくると思います。しかし、闇雲に機能を追加すると後で大量のコード改修作業が発生してしまう恐れがあります。
目標を見失なってしまい、余計な機能を追加してしまうことを防ぐためにも、設計図を見直しながら開発方針がブレないように適宜軌道修正していくことが大切です。

<br>
<br>
私は下記のような設計図をノートに書き出しました。最終的には機能が圧倒的に足りなかったので画面を追加していますが、全体構成はほとんどブレずに完成しました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">今回作成するお問い合わせフォームの設計をしてみました！<br><br>大した機能ではないから設計図いらないですが😂<br><br>今回は設計図作成と設計図通りに開発する練習としてやっていきます！
<a href="https://t.co/woCXrMDBKo">pic.twitter.com/woCXrMDBKo</a></p>&mdash; やん@公務員×独学プログラミング (@yansabro) <a href="https://twitter.com/yansabro/status/1238772047800455168?ref_src=twsrc%5Etfw">March 14, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>



<br>
##データベース設計をしよう
設計図が完成したら、次にデータベースにどんな情報を登録していくか決めます。
まず各画面の機能概要と実装したい機能詳細を書き出します。そこから、各機能を実装するためには、ユーザーにどんな情報を入力してもらう必要があるのかを考えます。
<br>
自分の場合、作品の最終目標は「ユーザー同士が投稿した内容を相互に閲覧できる」ことでしたので、

1. ユーザーを識別するための情報

1. 投稿内容を識別するための情報

の２つが必要だと判断しました。

したがって、テーブルは少なくとも２つ必要であり、１については「usersテーブル」、２については「postsテーブル」を作成しました。
<br>
ちなみに、「テーブル名は必ず複数形または複数名詞で書ける」ということに注意してください。テーブルには複数のデータが登録されるので、複数存在するものでないといけません。
例えば、「User」は複数人存在するので「users」と複数形にできますが、「Tokyo」が複数存在することはないので「tokyos」とはできませんよね。
すなわち、設定したテーブル名が複数形にできなければ、テーブルとして適切ではないということです。

<br>
<br>
次に各テーブルにカラムを作成します。言い換えると、テーブルにどんな情報を登録するかを考えることです。

この記事の場合は、下記のとおりカラムを作成することにします。

- usersテーブル

    - id
    - user_name
    - email
    - password
    - user_time
- postsテーブル
    - posts_id
    - user_id
    - message
    - posts_time
    - posts_modify

<br>
先ほどテーブルを作成したときに利用した下記の考えをもとに、必要最低限の情報があるか確認します。最低限をクリアしていれば、他に追加したい情報もこの時点で追加しておきます。

1. ユーザーを識別するための情報

1. 投稿内容を識別するための情報

なお、今回の場合は、どのユーザーが投稿した内容なのかを特定できるようにpostsテーブルに「user_id」を入れています。

<br>
<br>
また、カラムを作成する際は、主キーが入っているか確認します。
>主キー
　
　リレーショナルデータベース（RDB）のテーブル内でレコードを一意に識別することができるように指定される項目（列/カラム）。
　phpMyAdminでは、カラム作成画面のIndex項目でPRIMARYを選択することで、Primaryキー（主キー）を設定できる。

自分の場合は、usersテーブルでは「id」を、postsテーブルでは「posts_id」を主キーとしました。理由は２つあります。

1. AUTO_INCREMENT機能を使用することで、自動的に一意のid番号を割り当てることができる

2. 表記体系の定まったデータにすることで、一意性をより強固なものにできる

<br>
２つ目の理由について補足します。例えば、主キーが「人の名前」であった場合に「田中A太郎」のデータを特定したいとします。
人によっては、「田中　A太郎」や「たなかA太郎」と入力するかもしれません。しかし、PC側はこれを同一のデータとみなすことはできません。
したがって、主キーは「自由度が低いキーワード」である方が良いのです。id番号なら大抵の人は「1」、「2」と入力するので、人的ミスは大幅に削減できるでしょう。
<br>
データベース設計はシステムの品質をもっとも大きく左右するものです。どのようなプログラミングが必要になるのかを決めるのは、どのようなデータを扱うかに依存するからです。開発の方針をブラさないためにも、この時点でしっかりとデータベース設計を固めておきましょう。


<br>
#コードを書いていこう



##まずは設計図通りにHTMLで画面の基本構成を作ろう
最初に設計した画面をHTMLで構築していきます。例えば、ユーザー登録画面の基本構成は下記のとおりです。

```html
<form action="" method="POST">
    
  <label for="name">名前</label>
  <input id="name" type="text" name='name' value="">

　　　　　・
　　　　　・
　　　　　・
            
　　<input id="submit" type="submit" value="確認画面へ">
    
</form>
```

<br>
今回の作品のほとんどのファイルでformタグを使用しています。
また、このときformタグのaction属性は空欄にするのがポイントです。空欄にすることで、フォームの送信内容をPOSTに格納しつつ、同じページを再度呼び出すことができます。
このような処理にする理由は後述します。



<br>
##phpMyAdminに接続させよう
ユーザーが入力フォームに入力した内容をデータベースに登録するためには、まずデータベースに接続させる必要があります。下記のコードにより実現できます。

```php:dbconnect.php
<?php

  try {
    $db = new PDO('mysql:dbname=【データベース名】;host=localhost;charset=utf8', 'root', 'root');
  } catch(PDOException $e) {
    print('DB接続エラー：' . $e->getMessage());
  }

?>
```

<br>
上記のコードは、「データベースに接続させることができ、もしユーザー名かパスワードが間違っていれば、DB接続エラーを表示させる」ものです。【データベース名】には、これから使用するデータベースの名前を入力します。

<br>
このコードは全てのファイルで使用することになるので、「dbconnect.php」という独立したファイルを作成し、コードを共有させます。各ファイルの冒頭に下記のコードを追加することで、「dbconnect.php」のコードを読み込むことができ、データベースに接続されます。

```php
<?php

  require('dbconnect.php');

?>
```


<br>
##変数・セッション・クッキーの値の保存期間について
formタグのaction属性を空欄にして同じページを呼び出す理由を語る前に、変数・セッション・クッキーの保存期間について簡単に解説します。

- 変数　　　　「１つのページ内でのみ値を保持する」

- セッション　「ページを移動しても値を保持しているが、ブラウザを閉じれば消える」
- クッキー　　「ページを移動したりブラウザを閉じても、保存期間内ならば値を保持する」

<br>
変数に関しては、formタグを使ってPOSTに値を格納することで、次のページに値を引き継ぐことができます。
しかし、今回の作品の場合、画面を何度移動してもユーザーのログイン情報を保持し続ける必要があるため、POSTだけでは対応が難しいです。
セキュリティーの関係上クッキーに個人情報を保持するのは好ましくないので、セッションに情報を保持することにします。



<br>
##セッションを活用しよう
セッションを使用することで、下記のようなメリットがあります。

- 何度画面を移動しても、保存期間内であればログイン情報を保持できる

- 非ログインユーザーがユーザー限定画面に入ることを制限する仕組みを実装できる
- 入力内容の書き直し機能を実装する際、すでに入力した内容を表示できるので、ユーザーの手間を削減できる

<br>
セッションを使用するには、下記のコードをファイルの冒頭に記述します。

```php
<?php

  session_start();

?>
```
<br>
セッションに値を保存するには、下記のコードを記述します。この例では、formタグで送信されたPOSTの値を代入しています。【セッション名】は、自分で自由に決めたものを入力します。

```php
<?php

  $_SESSION['【セッション名】'] = $_POST;

?>
```



<br>
##formタグで同じ画面を再度呼び出す理由
これまでに見たように、セッションを使用することで様々な機能を実装することができます。
したがって、formタグの入力内容は、POSTよりもSESSIONに保持した方が都合が良いのです。
<br>
そのため、基本的に画面を移動するときは、

**「formタグ → POSTに値を保存 → ページ移動」**

ではなく、

**「formタグ → POSTに値を保存 → 再度同じページへ → SESSIONに値を保存 → ページ移動」**

という動き方をすることになります。
<br>
これをコードで表すと下記のとおりです。なお、【セッション名】は「query」とします。

```php:ユーザー登録画面
<?php

  session_start();

  require('dbconnect.php');

  // 画面を表示した時、POSTに値が存在する場合、セッションにPOSTの値を保存し、画面を移動させる
  if(!empty($_POST)) {

    $_SESSION['query'] = $_POST;
    header('Location: 【入力内容確認画面のURL】');
    exit();

  }

?>

<!DOCTYPE html>
<html lang="ja">

  <head>

　　　　　・
　　　　　・
　　　　　・

  </head>

  <body>

  　<!-- action="" として移動先のURLを指定していないのは、submitの「確認画面へ」ボタンを
  　押した後、もう一度同じ画面を呼び出すことで、上記の header('Location: '); によって
  　画面を移動させたいから -->
  　<form action="" method="POST">
    
  　　<label for="name">名前</label>
  　　<input id="name" type="text" name='name' value="">

  　　<label for="email">メールアドレス</label>
  　　<input id="email" type="text" name='email' value="">

  　　<label for="pass">パスワード</label>
  　　<input id="pass" type="text" name='pass' value="">
            
  　　<input id="submit" type="submit" value="確認画面へ">
    
  　</form>

  </body>

</html>
```

<br>
自分の場合、SESSIONに値を保存する前に、「メールアドレスがすでに登録されているかどうか」などエラーチェックを行い、チェックを通過したもののみ保存しています。
この記事ではエラーチェックの説明を割愛しておりますので、気になる方は検索してください。



<br>
##データベースに入力内容を登録しよう
セッションに値を保存したのち、入力内容確認画面へと移ります。そこで入力内容に問題がなければ、データベースにその内容を登録しましょう。
確認画面のformタグを使って、再度確認画面を呼び出し、下記のコードを起動させます。

基本的にprepareメソッドによってSQL文を生成することで、データベースへの登録・更新・削除を行います。

```php:入力内容確認画面
<?php

 session_start();

 require('dbconnect.php');

 // 画面を表示した時、POSTに値が存在する場合、セッションの内容をデータベースに登録。
 if (!empty($_POST)) {
   $statement = $db->prepare('INSERT INTO users SET user_name=?, email=?, password=?, user_time=NOW()');

   echo $statement->execute(array(

     $_SESSION['query']['name'],
     $_SESSION['query']['email'],
     sha1($_SESSION['query']['pass'])

  ));

  // セッションを削除する
  unset($_SESSION['query']);

  // 登録完了画面に移動する
  header('Location: 【登録完了画面のURL】');
  exit();
}

?>
```

上記のように、セッションの値を取得する際は、「（ユーザー登録画面の）inputタグのname属性名」まで指定することで、特定の情報のみ取得することができます。

また、使い終わったセッションは必ず削除しておきましょう。



<br>
##ログイン画面を作ろう
ログイン機能は、下記のコードで実現できます。また、ユーザーのログイン情報を保持するためのセッションとして、SESSION['id']とSESSION['time']を新たに作成します。

※なお、formタグの構成は全体を通してほぼ同じなので、今後説明を省略します。

```php:ログイン画面
<?php

  session_start();

  require('dbconnect.php');

  // formタグの「ログイン」ボタンを押した際、メールアドレスとパスワードが入力されているかを確認し、
  // それらがデータベースに登録されているか確認。メールアドレスとパスワードが一致するユーザーが
  // 確認できた場合、$memberにユーザー情報を格納する
  if ($_POST['email'] != '' && $_POST['pass'] != '') {

    $login = $db->prepare('SELECT * FROM users WHERE email=? AND password=?');
    $login->execute(array(
      $_POST['email'],
      sha1($_POST['pass'])
    ));
    $member = $login->fetch();

    // $memberに値が入っている場合、セッションに該当のユーザーのidとログイン時間を
    // 記録し、投稿一覧画面へ移動する。

    if ($member) {

      $_SESSION['id'] = $member['id'];
      $_SESSION['time'] = time();

      header('Location: 【投稿一覧画面のURL】');
      exit();

    } 

  } 

?>
```

<br>
補足ですが、fetch()メソッドとは、「SQL文のSELECTで得られた範囲から、レコード（今回の場合はユーザー情報のこと）を一つ取り出すことができる」ものです。
今回は、そのユーザー情報を変数$memberに代入することで、次のコードが起動する仕組みになっています。



<br>
##投稿一覧画面を作ろう
ログインが完了したら、この作品のメインページである投稿一覧画面を作成していきます。
投稿一覧画面は、下記のコードで実現できます。

```php:投稿一覧画面
<?php

  session_start();

  require('dbconnect.php');

  // ログインしてから１時間を経過していなければ、投稿一覧画面が表示される
  // ログインしてから１時間を経過しているならば、ログイン画面に強制的に移動する
  if (isset($_SESSION['id']) && $_SESSION['time'] + 3600 > time()) {

    $_SESSION['time'] = time();     // セッションタイムを更新する

  } else {

    header('Location: 【ログイン画面のURL】');
    exit();

  }

  // データベースに登録済みのユーザー名、及びpostsテーブルの全データを変数$postsに配列で格納
  $posts = $db->query('SELECT u.user_name, p.* FROM users u, posts p WHERE u.id=p.user_id ORDER BY p.posts_time DESC');

?>

<!DOCTYPE html>
<html lang="ja">

  <head>

　　　　　・
　　　　　・
　　　　　・

  </head>

  <body>
    
  　<!-- 上記のquery()メソッドで、データベースに登録済みのユーザー名、及びpostsテーブルの
  　 全データが$postsに配列で格納されているので、順番に一つずつ取り出し$postに格納 -->
  　<?php foreach ($posts as $post): ?>
    
  　　<p>(<?php print(htmlspecialchars($post['user_name'], ENT_QUOTES)); ?>)</p>
  　　<!-- 投稿内容をクリックすると投稿内容詳細画面に遷移すると同時に、投稿内容詳細画面のURLパラメーターにposts_idの番号を渡す -->
  　　<a href="【投稿内容詳細画面のURL】?posts_id=<?php print(htmlspecialchars($post['posts_id'], ENT_QUOTES)); ?>"><?php print(htmlspecialchars($post['message'], ENT_QUOTES)); ?></a>
  　　<p>
  　　　投稿日時：<?php print(htmlspecialchars($post['posts_time'], ENT_QUOTES)); ?>&emsp;
            
  　　　<!-- ログインユーザーIDと投稿作成者のユーザーIDが一致していれば、「削除」ボタンを表示する -->
  　　　<?php if ($_SESSION['id'] === $post['user_id']): ?>
            
  　　　　[<a href="【投稿削除画面のURL】?posts_id=<?php print(htmlspecialchars($post['posts_id'], ENT_QUOTES)); ?>">削除</a>]
      
  　　　<?php endif; ?>
      
  　　</p>     <!-- 「削除」ボタンを押すと、投稿削除画面に移動しつつ、投稿削除画面のURLパラメーターにposts_idの番号を渡す -->
        
  　<?php endforeach; ?>
  
  </body>

</html>
```

<br>
コードが長いので、一つづつ分解しながら説明していきます。
<br>
セッションにユーザー情報が登録されているユーザーのみ投稿一覧画面に入れる仕組みです。
また、画面を移動する度にセッションタイムが更新されるようになっています。ただし、セッションの保存期間である１時間を越えて放置していると、タイムアウトになりログイン画面に強制的に移動します。

これらの動きを実現するコードを、下記に抽出しておきました。

```php
<?php

  // ログインしてから１時間を経過していなければ、投稿一覧画面が表示される
  // ログインしてから１時間を経過しているならば、ログイン画面に強制的に移動する
  if (isset($_SESSION['id']) && $_SESSION['time'] + 3600 > time()) {

    $_SESSION['time'] = time();     // セッションタイムを更新する

  } else {

    header('Location: 【ログイン画面のURL】');
    exit();

  }

?>
```

<br>
投稿一覧画面には、各ユーザーが投稿した内容が一覧で表示されます。そのため、下記のコードで「全てのユーザー名」と「各ユーザーが投稿した内容の全て」を変数$postsに代入しておき、あとで利用します。

※今回はデータを単に取得するのみなので、prepare()メソッドを使う必要はなく、query()メソッドで十分です。

```php
<?php

  // データベースに登録済みのユーザー名、及びpostsテーブルの全データを変数$postsに配列で格納
  $posts = $db->query('SELECT u.user_name, p.* FROM users u, posts p WHERE u.id=p.user_id ORDER BY p.posts_time DESC');

?>
```

<br>
各ユーザーが投稿した内容を一覧で表示するためには、下記のコードで実現できます。
先ほど作成した変数$postsの中にあるデータを、foreach文で一つずつ取り出し、新たな変数＄postに格納します。その後、printでデータを表示していきます。

なお、投稿は削除することができますが、ログインユーザーIDと投稿作成者のユーザーIDが一致している場合のみ、「削除」ボタンが表示される仕組みになっています。投稿削除機能の詳細は後述します。

```php
<body>
    
　<!-- 上記のquery()メソッドで、データベースに登録済みのユーザー名、及びpostsテーブルの
　全データが$postsに配列で格納されているので、順番に一つずつ取り出し$postに格納 -->
　<?php foreach ($posts as $post): ?>
    
　　<p>(<?php print(htmlspecialchars($post['user_name'], ENT_QUOTES)); ?>)</p>
　　<!-- 投稿内容をクリックすると投稿内容詳細画面に遷移すると同時に、投稿内容詳細画面のURLパラメーターにposts_idの番号を渡す -->
　　<a href="【投稿内容詳細画面のURL】?posts_id=<?php print(htmlspecialchars($post['posts_id'], ENT_QUOTES)); ?>"><?php print(htmlspecialchars($post['message'], ENT_QUOTES)); ?></a>
　　<p>
　　　投稿日時：<?php print(htmlspecialchars($post['posts_time'], ENT_QUOTES)); ?>&emsp;
            
　　　<!-- ログインユーザーIDと投稿作成者のユーザーIDが一致していれば、「削除」ボタンを表示する -->
　　　<?php if ($_SESSION['id'] === $post['user_id']): ?>
            
　　　[<a href="【投稿削除画面のURL】?posts_id=<?php print(htmlspecialchars($post['posts_id'], ENT_QUOTES)); ?>">削除</a>]
      
　　　<?php endif; ?>
      
　　</p>     <!-- 「削除」ボタンを押すと、投稿削除画面に移動しつつ、投稿削除画面のURLパラメーターにposts_idの番号を渡す -->
        
　<?php endforeach; ?>
  
</body>
```



<br>
##投稿内容詳細画面を作ろう
投稿一覧画面で表示された投稿内容をクリックすると、投稿内容を特定するために必要な「posts_id」をURLパラメーターに渡しつつ、該当の投稿の詳細画面に移動する仕組みになっています。

下記のコードによって、送られてきたposts_idをもとに、特定の投稿内容のデータを変数$postsに格納します。

```php
<?php

  // 投稿一覧画面から送られてきたposts_id（URLパラメーターに付いている）が空ならば、
  // 投稿一覧画面に戻す
  if (empty($_REQUEST['posts_id'])) {
    header('Location: 【投稿一覧画面のURL】');
    exit();
  }

  // 投稿一覧画面から送られてきたposts_id（URLパラメーターに付いている）などと一致する
  // データを$postsに格納
  $posts = $db->prepare('SELECT u.user_name, p.* FROM users u, posts p WHERE u.id=p.user_id AND p.posts_id=?');
  $posts->execute(array($_REQUEST['posts_id']));

?>
```

<br>
そして、下記のコードによって、変数$postsに格納されたデータを一つづつ取り出し、変数＄postに代入します。その後、printでデータを表示していきます。

また、ログインユーザーIDと投稿作成者のユーザーIDが一致している場合のみ、「投稿内容の編集」ボタンが表示され、投稿編集画面に移動できます。

```php
<!DOCTYPE html>
<html lang="ja">

　　　　　・
　　　　　・
　　　　　・

  <body>

  　<!-- 上記で$postsに格納したデータを一つづつ取り出し、$postに格納 -->
  　<?php if ($post = $posts->fetch()): ?>
      
  　　<p>(<?php print(htmlspecialchars($post['user_name'], ENT_QUOTES)); ?>)</p>
  　　<p><?php print(htmlspecialchars($post['message'], ENT_QUOTES)); ?></p>
  　　<p>投稿日時：<?php print(htmlspecialchars($post['posts_time'], ENT_QUOTES)); ?></p>

  　<?php endif; ?>
      
  　<!-- ログインユーザーIDと投稿作成者のユーザーIDが一致していれば、「投稿内容の編集」ボタンを表示する -->
  　<!-- 投稿編集画面のURLパラメーターにposts_idの番号を渡しつつ、投稿編集画面に遷移する -->
  　<?php if ($_SESSION['id'] === $post['user_id']): ?>
      
  　　<a href="【投稿編集画面のURL】?posts_id=<?php print(htmlspecialchars($_REQUEST['posts_id'], ENT_QUOTES)); ?>">投稿内容の編集</a>
      
  　<?php endif; ?>

  </body>

</html>
```



<br>
##投稿編集画面を作ろう
投稿内容詳細画面の「投稿内容の編集」ボタンを押すと、投稿内容を特定するために必要な「posts_id」をURLパラメーターに渡しつつ、該当の投稿の編集画面に移動する仕組みになっています。

formタグの「投稿内容を編集する」ボタンを押すと、更新のSQL文が生成され、データベースの登録内容が変更されます。

なお、どのユーザーによる操作であるかを判別するために、画面を移動してきた時にユーザー情報を変数$memberに格納しています。

```php
<?php

  session_start();

  require('dbconnect.php');

  // ログインしてから１時間を経過していなければ、投稿編集画面が表示され、
  // 変数$memberにログインユーザーの登録情報を格納する。
  // ログインしてから１時間を経過しているならば、ログイン画面に強制遷移する。
  if (isset($_SESSION['id']) && $_SESSION['time'] + 3600 > time()) {

    $_SESSION['time'] = time();     // セッションタイムを更新する

    $members = $db->prepare('SELECT * FROM users WHERE id=?');
    $members->execute(array($_SESSION['id']));
    $member = $members->fetch();

  } else {

    header('Location: 【ログイン画面のURL】');
    exit();

  }

  // 「投稿内容を編集する」ボタンが押された場合、投稿内容が空欄でなければ、
  // その内容をもとにデータベースに登録されている内容を変更する。その後、投稿一覧画面に移動する。
  if (!empty($_POST)) {
    if ($_POST['message'] !== '') {

      // WHEREに複数条件を設定する場合は「,」でなく「AND」なので注意！
      $message = $db->prepare('UPDATE posts SET message=?, posts_modify=NOW() WHERE posts_id=? AND user_id=?');
      $message->execute(array(
        $_POST['message'],
        $_REQUEST['posts_id'],
        $member['id']
      ));

      header('Location: 【投稿一覧画面のURL】');
      exit();

    }
  }

?>
```

<br>
また、このとき注意すべきなのは、formタグのaction属性です。ここは空欄ではなく、URLパラメーターのposts_idを再度POSTに保存して送ります。
前のページで送られてきたPOSTの値をそのまま渡すことはできないので、新たに値を保存し直す必要があるからです。

コードは下記のとおりです。

```php
<!-- フォームを送信する際にURLパラメーターのposts_idの番号が消えてしまうので、再度同じ番号をURLパラメーターに送り直す -->
<form action="【投稿編集画面のURL】?posts_id=<?php print(htmlspecialchars($_REQUEST['posts_id'], ENT_QUOTES)); ?>" method="POST">
```



<br>
##投稿削除機能を作ろう
投稿一覧画面の「削除」ボタンを押すと、投稿内容を特定するために必要な「posts_id」をURLパラメーターに渡しつつ、該当の投稿の削除画面に移動する仕組みになっています。

下記のコードによって、ログインユーザーIDと投稿作成者のユーザーIDが一致しているかを確認し、削除のSQL文を生成します。データベースの登録内容が削除されたのち、投稿一覧画面に戻ります。

```php:投稿削除画面
<?php

  session_start();
  require('dbconnect.php');

  // ユーザーのログインIDがセッションにあるか確認。その後投稿一覧画面から渡された
  // URLパラメーターにあるposts_idの番号をもとにデータベースのデータを抽出し、
  // 削除する。最後に、投稿一覧画面に強制遷移する。
  if (isset($_SESSION['id'])) {

    $id = $_REQUEST['posts_id'];

    $messages = $db->prepare('SELECT * FROM posts WHERE posts_id=?');
    $messages->execute(array($id));
    $message = $messages->fetch();

    if ($message['user_id'] == $_SESSION['id']) {
      $del = $db->prepare('DELETE FROM posts WHERE posts_id=?');
      $del->execute(array($id));
    }

  }

  header('Location: 【投稿一覧画面のURL】');
  exit();

?>
```



<br>
##ログアウト機能を作ろう
投稿一覧画面の「ログアウト」ボタンを押すと、セッションに保存されているログイン情報を削除します。その後、ログイン画面に移動することで、ログアウトを完了させます。

コードは下記のとおりです。

```php
<?php

  session_start();

  // セッションを削除し、ログイン画面に遷移する
  unset($_SESSION['id']);

  header('Location: 【ログイン画面のURL】');
  exit();

?>
```



<br>
<a id="anchor4"></a>
#参考にした書籍や教材など
- [PHP+MySQL（MariaDB） Webサーバーサイドプログラミング入門](https://www.udemy.com/share/101XkACEsZdV9WR3w=/)
<br>Taniguchi Makoto さんのUdemy教材です。今回の作品は、この教材の内容の多くを参考にさせていただいております。とても丁寧な解説をされていますので、これからPHPを学習される方にはオススメの教材です。


- [達人に学ぶDB設計 徹底指南書 初級者で終わりたくないあなたへ](https://www.amazon.co.jp/%E9%81%94%E4%BA%BA%E3%81%AB%E5%AD%A6%E3%81%B6DB%E8%A8%AD%E8%A8%88-%E5%BE%B9%E5%BA%95%E6%8C%87%E5%8D%97%E6%9B%B8-%E5%88%9D%E7%B4%9A%E8%80%85%E3%81%A7%E7%B5%82%E3%82%8F%E3%82%8A%E3%81%9F%E3%81%8F%E3%81%AA%E3%81%84%E3%81%82%E3%81%AA%E3%81%9F%E3%81%B8-%E3%83%9F%E3%83%83%E3%82%AF/dp/4798124702)
<br>ミックさんの著書です。データベース設計の基本的な考え方を学べます。また、実務で実際にあったバッドノウハウなどを紹介し、どういった点が問題なのかを細かく解説されています。私を含め初心者には難しい内容も多いですが、DB設計を学ぶ上で非常に有益な本です。