5-2:NewsのCRUD機能の実装

2019/05/31

概要

CRUD機能とは、新規登録(Create)、読み込み(Read)、更新(Update)、削除(Delete)機能のことです。
管理画面でNewsの新規登録・詳細表示・更新・削除機能を作成し、コーポレートサイトに自動反映させるように実装していきます。

フォルダ階層

完成イメージ

新規登録

一覧

編集

全体の手順

手順の流れは以下の通りです。
すべてローカルで行います。

  1. mysqlについて
  2. mysqlでデータベース・テーブルの作成
  3. 新規登録機能実装
  4. 読み込み機能実装
  5. 更新機能実装
  6. 削除機能実装

mysqlについて

CRUD機能を実装するには記事を管理しなければいけません。
そこでデータを整理して保存することができるデータベースを使っていきます。
mysqlとは世界で最も利用されているというデータベース管理システムです。
オープンソースで開発されているため無料で利用することができます。
データベース管理方法はリレーショナルデータベースと言われるもので、エクセルの表に似た形で管理されています。

※イメージ図です

yamadaさんのemailを知りたいときは、データベース1に接続して、SQL文でテーブル1のnameがyamadaのemailを取得することができます。

データベースとテーブルの作成

データベースを作成し、その中でNewsを管理するテーブルを作成していきます。
まず、mysqlにログインします。

データベースの作成

console

$ mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 613
Server version: 5.7.23 MySQL Community Server (GPL)

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

mysql>が最後の行に表示されていればmysqlログインOKです。
データベースを作成します。
今回はコーポレートサイト用のデータを扱うデータベースとなるので、corporate_dbという名前にします。
console

mysql> create database corporate_db;
Query OK, 1 row affected (0.03 sec)

Query OKと表示されていれば作成成功です。

テーブルの作成

テーブルを作成します。
今回はnewsを管理するテーブルなので、newsというテーブルを作成します。
テーブルは、事前にどんな項目にデータがどんな形で入ってくるかを指定して作成する必要があります。
テーブルの項目を決めなければいけません。
newsを管理するために必要な情報を考えます。
newsにはタイトル・内容が必要です。
その他にnewsの作成日時・更新日時が最低限必要でしょう。
また、テーブルを作成するときはidを必ず作成します。
idは登録順に1,2,3と重複しない数字をセットし、主キーとして設定します。
重複しない数字を設定することで、データ検索でIDを使ってただ一つのデータを取得することができるようになります。
以上を含めて作成するテーブルの内容は以下の通りです。

内容 型(データの形式) オプション オプション
id ID int primary key(主キー) auto_increment(自動で増える)
title タイトル varchar(256)
content 内容 text
created_at 作成日時 datetime
updated_at 更新日時 datetime

それではコンソールで作成していきます。
corporate_dbを使います。
console

mysql> use corporate_db
Database changed
mysql>

Database changedと表示されていればOKです。

create table文を入力します。
create table文は、create table テーブル名(カラム名 カラムの型 オプション,カラム名 カラムの型 オプション...);となっています。
console

mysql> create table news(id int primary key auto_increment,title varchar(256),content text,created_at datetime,updated_at datetime);
Query OK, 0 rows affected (0.02 sec)
mysql>

Query OKと表示されていればOKです。

テーブルが作成されているか確認してみましょう。
console

mysql> show tables;
+-----------------------+
| Tables_in_corporate_db |
+-----------------------+
| news                  |
+-----------------------+
1 row in set (0.01 sec)

こう表示されていればnewsテーブルが作成できたことが確認できます。

念の為、newsテーブルの中身も確認してみます。
console

mysql> desc news;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | int(11)      | NO   | PRI | NULL    | auto_increment |
| title      | varchar(256) | YES  |     | NULL    |                |
| content    | text         | YES  |     | NULL    |                |
| created_at | datetime     | YES  |     | NULL    |                |
| updated_at | datetime     | YES  |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)

カラムと型が指定通りに作成出来ているか確認して、上記のようになっていればOKです。

新規登録

テーブルも完成したので、新規登録用画面を作成していきます。

新規登録画面の作成

adminフォルダにcreate_news.phpを作成します。
ちなみに、「createnews」のように単語を「(アンダーバー)」でつなげる命名規則をスネークケースと呼びます。
「createNews」のようにつながる単語の頭文字を大文字にする命名規則をキャメルケースと呼びます。
今回はスネークケースでファイル名付けていきます。

まず、このページも管理者のみが閲覧できるページなので、phpのセッションでログインチェックをします。
その下にhtmlでフォームを作成していきます。
formのmethodはPOSTでactionはstore_news.phpとします。
必要な項目はタイトルと本文のみです。
admin/create_news.php

<?php
    session_start();

    if($_SESSION['admin_login'] == false){
        header("Location:./index.html");
        exit;
    }

?>
<!DOCTYPE html>
<html>

<head>
    <!-- Global site tag (gtag.js) - Google Analytics -->
    <script async src="https://www.googletagmanager.com/gtag/js?id=UA-13xxxxxxxxx"></script>
    <script>
        window.dataLayer = window.dataLayer || [];
        function gtag() { dataLayer.push(arguments); }
        gtag('js', new Date());

        gtag('config', 'UA-13xxxxxxxxx');
    </script>

    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>記事投稿</title>

    <link rel="icon" href="favicon.ico">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css"
        integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">

    <!-- css -->
    <link rel="stylesheet" href="styles.css">
</head>

<body>
    <header>
        <div class="container">
            <div class="header-logo">
                <h1><a href="dashboard.php">管理画面</a></h1>
            </div>

            <nav class="menu-right menu">
                <a href="logout.php">ログアウト</a>
            </nav>
        </div>
    </header>
    <main>
        <div class="wrapper">
            <div class="container">
                <div class="wrapper-title">
                    <h3>新規作成</h3>
                </div>
                <form class="edit-form" method="POST" action="store_news.php">
                    <div class="form-group">
                        <p>タイトル</p>
                        <input type="text" name="title" required>
                    </div>
                    <div class="form-group">
                        <p>本文</p>
                        <textarea name="content"></textarea>
                    </div>
                    <button type="submit" class="btn btn-blue">公開する</button>
                </form>
            </div>
        </div>
    </main>
    <footer>
        <div class="container">
            <p>Copyright @ 2018 SQUARE, inc</p>
        </div>
    </footer>
</body>

</html>

styles.cssにも追加と修正をします。
admin/styles.css

/* 共通CSS */
-.btn {
-    padding: 10px 30px;
-    font-size: 15px;
-    border-radius: 20px;
-    border: none;
-    margin-top: 20px;
-}

-.btn:hover{
-    cursor: pointer;
-    opacity: .8;
-}

-.btn-submit {
-    background-color: #4c586f;
-    color: #fff;
-}

+a:hover,
+.btn:hover {
+    opacity: 0.6;
+    cursor: pointer;
+}

+.btn {
+    padding: 10px 30px;
+    font-size: 15px;
+    border: none;
+    margin: 5px 0;
+    border-radius: 5px;
+}

+.btn-submit {
+    background-color: #4c586f;
+    border: 1px solid #4c586f;
+    color: #fff;
+}

+.btn-green {
+    background-color: #20c977;
+    border: 1px solid #20c977;
+    color: #fff;
+    padding: 5px 10px;
+    font-size: 15px;
+}

+.btn-red {
+    border: 1px solid #d84950;
+    color: red;
+    padding: 5px 10px;
+    font-size: 15px;
+}

+.btn-blue {
+    background-color: #4a80d6;
+    border: 1px solid #4a80d6;
+    color: #fff;
+    padding: 10px 15px;
+    font-size: 15px;
+}

+.form-group {
+    margin-bottom: 20px;
+    text-align: left;
+}

+input,
+textarea {
+    width: 100%;
+    border: 1px solid #ebeced;
+    border-radius: 5px;
+}

+    input {
+        height: 25px;
+    }

+    textarea {
+        height: 300px;
+    }

/* login*/
- .form-group {
- margin-bottom: 10px;
- }

+/* create_news */
+.edit-form {
+    width: 80%;
+    margin: 0 auto;
+}

+    .edit-form input,
+    .edit-form textarea{
+        width: 100%;
+        border: 1px solid #a2aab0;
+    }

+    .edit-form button{
+        float:right;
+    }

+    .edit-form textarea {
+        height: 300px;
+    }

admin/create_news.phpにアクセスして確認します。
このとき、管理画面にログインしていないとログイン画面にリダイレクトされることも確認しておきましょう。

このようになっていればOKです。

DBにためる

次に、adminフォルダにstore_news.phpを作成していきます。
store_news.phpでは、create_news.phpで入力した情報をcorporate_dbのnewsテーブルに保存する機能を作成します。

ログインチェック

store_news.phpも管理者のみアクセス可のページなので、最初にログインチェックをします。
admin/store_news.php

<?php

    //sessionでログイン制限
    session_start();

    if($_SESSION['admin_login'] == false){
        header("Location:./index.html");
        exit;
    }

データ受け取り

create_news.phpからPOSTでデータを受け取ります。

    <?php

        //sessionでログイン制限
        session_start();

        if($_SESSION['admin_login'] == false){
            header("Location:./index.html");
            exit;
        }

+       $title = isset($_POST['title'])? htmlspecialchars($_POST['title'], ENT_QUOTES, 'utf-8'):'';
+       $content = isset($_POST['content'])? htmlspecialchars($_POST['content'], ENT_QUOTES, 'utf-8'):'';

本文のデータを整える

本文はテキストエリアで入力されてきます。
記事なので、改行を行って記事を書くこともあります。
しかし、普通にcontentを受け取っただけでは改行コード「\n」は表現されず一行になってしまいます。

テキストエリアに下記を入力して送信したとします。
例)

こんにちは
いい天気ですね

いままでどおり受け取って表示させた例)

$content = isset($_POST['content'])? htmlspecialchars($_POST['content'], ENT_QUOTES, 'utf-8'):'';
echo $content;

表示結果)

こんにちはいい天気ですね

改行されません。
そこで、改行コードの前にHTMLの改行タグを自動的に出力してくれるnl2br関数を使います。
nl2br関数を使った例)

$content = isset($_POST['content'])? htmlspecialchars($_POST['content'], ENT_QUOTES, 'utf-8'):'';
$content = nl2br($content);
echo $content;

nl2br関数を使った表示)

こんにちは
いい天気ですね

となります。
改行を表示させるためstore_news.phpにもnl2br関数を追加します。
admin/store_news.php

    <?php

        //sessionでログイン制限
        session_start();

        if($_SESSION['admin_login'] == false){
            header("Location:./index.html");
            exit;
        }

        $title = isset($_POST['title'])? htmlspecialchars($_POST['title'], ENT_QUOTES, 'utf-8'):'';
        $content = isset($_POST['content'])? htmlspecialchars($_POST['content'], ENT_QUOTES, 'utf-8'):'';
+       $content = nl2br($content);

DB接続

データベースにデータを格納するため、PDO (PHP Data Object)という接続クラスを使います。
最初にPDOオブジェクトを作成し、データベースに接続します。
try-catchで囲んで、例外が発生したときにエラーメッセージを表示させるようにします。
admin/store_news.php

    <?php

        //sessionでログイン制限
        session_start();

        if($_SESSION['admin_login'] == false){
            header("Location:./index.html");
            exit;
        }

        $title = isset($_POST['title'])? htmlspecialchars($_POST['title'], ENT_QUOTES, 'utf-8'):'';
        $content = isset($_POST['content'])? htmlspecialchars($_POST['content'], ENT_QUOTES, 'utf-8'):'';
        $content = nl2br($content);

+       //DB接続
+       try{
+           $dbh = new PDO("mysql:host=localhost;dbname=corporate_db","root","root");
+       }catch(PDOException $e){
+           var_dump($e->getMessage());
+           exit;
+       }

Insertする

コンソールでInsertしてみる

store_news.phpでinsertする前に、コンソールでInsertしてみましょう。
SQLのINSERT文は下記のように表現します。
INSERT INTO テーブル名(カラム名1,カラム名2,カラム名3,) VALUES('カラム1に入れる値','カラム2に入れる値',カラム3に入れる値);
idは自動で採番されるので指定しません。
タイトルを「テスト投稿」、本文を「こんにちは。いい天気ですね。」、作成日時と更新日時は現在時刻と仮定してInsertしてみます。
console

mysql> use corporate_db
Database changed
mysql> INSERT INTO news(title,content,created_at,updated_at) VALUES('テスト投稿','こんにちは。いい天気ですね。',now(),now());
Query OK, 1 row affected (0.10 sec)

Query OK,と表示されればINSERT成功です。

store_news.phpでINSERT

INSERT文がわかったところでstore_news.phpで実装させていきます。
INSERT文はコンソールで利用したものとほとんど同じです。
store_news.phpではINSERT文を実行するためにPDO::prepare で文オブジェクトを作成します。
また、VALUES()で指定する値は、POSTで受け取った値が入るのでVALUES()にはいつも変数が入ります。
ですので、VALUESで指定された変数名にパラメータをバインドする必要があります。
バインドできたら$stmtをexecute()で実行します。
admin/store_news.php

    <?php

        //sessionでログイン制限
        session_start();

        if($_SESSION['admin_login'] == false){
            header("Location:./index.html");
            exit;
        }

        $title = isset($_POST['title'])? htmlspecialchars($_POST['title'], ENT_QUOTES, 'utf-8'):'';
        $content = isset($_POST['content'])? htmlspecialchars($_POST['content'], ENT_QUOTES, 'utf-8'):'';
        $content = nl2br($content);

        //DB接続
        try{
            $dbh = new PDO("mysql:host=localhost;dbname=corporate_db","root","root");
        }catch(PDOException $e){
            var_dump($e->getMessage());
            exit;
        }

+       $stmt = $dbh->prepare("INSERT INTO news(
+           title,
+           content,
+           created_at,
+           updated_at
+       ) values(
+           :title,
+           :content,
+           now(),
+           now()
+       )");
+       $stmt->bindParam(':title',$title);   //:titleという変数に$titleをバインド
+       $stmt->bindParam(':content',$content);    //:contentという変数に$contentをバインド
+       $stmt->execute();    //$stmtを実行

確認

ここまでできたら、エラーがないか確認してみます。
admin/create_news.phpにアクセスして、newsを登録してみます。
store_news.phpにPOSTされ、ブラウザは真っ白な画面になるはずです(HTMLを設定していないので)。
そうしたら、コンソールでテーブルを確認してみましょう。
console

mysql > use corporate_db;
Database changed
mysql> select * from news;


登録したnewsの情報が表示されていればOKです。

登録完了したら一覧へ画面へ

まだ、記事一覧画面を作成していませんが、登録できたら記事一覧画面にリダイレクトを設定しておきます。
store_news.php

    $stmt = $dbh->prepare("INSERT INTO news(title,content,created_at,updated_at) values(:title,:content,now(),now())");
    $stmt->bindParam(':title',$title);
    $stmt->bindParam(':content',$content);
    $stmt->execute();

+   header('location:./news.php');

これで新規登録機能は完了です。
store_news.phpはPHPだけで完結するファイルです(HTMLを下に記述したりしないファイル)。
PHPだけで完結するファイルは原則PHPの閉じタグ?>は付けないというのがルールになっていますので、PHPだけで完結するファイルを作成するときは?>を記述しないようにします。

読み込む

一覧画面作成

記事一覧画面を作成していきます。
adminフォルダにnews.phpを作成します。

ログインチェック

news.phpも管理者のみなので、ログインチェックを入れます。
news.php

<?php

    //sessionでログイン制限
    session_start();

    if($_SESSION['admin_login'] == false){
        header("Location:./index.html");
        exit;
    }
?>

データ取得

登録したnewsを表示するためには、データベースからデータを呼び出します。
データベースに接続します。
news.php

    <?php
        session_start();

        if($_SESSION['admin_login'] == false){
            header("Location:./index.html");
            exit;
        }

+       //DB接続
+       try{
+           $dbh = new PDO("mysql:host=localhost;dbname=corporate_db","root","root");
+       }catch(PDOException $e){
+           var_dump($e->getMessage());
+           exit;
+       }

    ?>

文オブジェクトを作ってSQLのSELECT文を作成、実行します。
newsテーブルの中身を全件取得します。
news.php

        //DB接続
        try{
            $dbh = new PDO("mysql:host=localhost;dbname=corporate_db","root","root");
        }catch(PDOException $e){
            var_dump($e->getMessage());
            exit;
        }

+       $stmt = $dbh->prepare("SELECT * FROM news");
+       $stmt->execute();

    ?>

取得した結果を扱いやすくするため、$stmt->fetchAll(PDO::FETCH_ASSOC)を使います。
fetchAll()はすべての結果を配列で返してくれます。
FETCH_ASSOCは結果の行を連想配列にしてくれます。
配列になった結果を$newsに格納します。
news.php

        $stmt = $dbh->prepare("SELECT * FROM news");
        $stmt->execute();
+       $news = $stmt->fetchAll(PDO::FETCH_ASSOC);
    ?>

表示する

phpの下にHTMLで一覧テーブルを作成します。
一旦、テーブルをスタティックで作成してみます。
テーブルの中身の内容はDBとは関係なく暫定で記述して見た目を整えます。
news.php

?>
<!DOCTYPE html>
<html>

<head>
    <!-- Global site tag (gtag.js) - Google Analytics -->
    <script async src="https://www.googletagmanager.com/gtag/js?id=UA-13xxxxxxxxx"></script>
    <script>
        window.dataLayer = window.dataLayer || [];
        function gtag() { dataLayer.push(arguments); }
        gtag('js', new Date());

        gtag('config', 'UA-13xxxxxxxxx');
    </script>

    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>記事管理</title>

    <link rel="icon" href="favicon.ico">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css"
        integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">

    <!-- css -->
    <link rel="stylesheet" href="styles.css">
</head>

<body>
    <header>
        <div class="container">
            <div class="header-logo">
                <h1><a href="dashboard.php">管理画面</a></h1>
            </div>

            <nav class="menu-right menu">
                <a href="logout.php">ログアウト</a>
            </nav>
        </div>
    </header>
    <main>
        <div class="wrapper">
            <div class="container">
                <div class="wrapper-title">
                    <h3>記事管理</h3>
                </div>
                <button type="button" class="btn btn-blue" onclick="location.href='create_news.php'">投稿する</button>
                <div class="list">
                    <table>
                        <thead>
                            <tr>
                                <th>id</th>
                                <th>タイトル</th>
                                <th>本文</th>
                                <th>更新日時</th>
                                <th>作成日時</th>
                                <th>操作</th>
                            </tr>
                        </thead>
                        <tbody>
                            <tr>
                                <td>1</td>
                                <td>ホームページをリニューアルしました。</td>
                                <td>サイトを更新しました。</td>
                                <td>2018-01-01 00:00:00</td>
                                <td>2018-01-01 00:00:00</td>
                                <td>
                                    <button type="button" class="btn btn-green">編集</button>
                                    <button type="button" class="btn btn-red">削除</button>
                                </td>
                            </tr>
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </main>
    <footer>
        <div class="container">
            <p>Copyright @ 2018 SQUARE, inc</p>
        </div>
    </footer>
</body>

</html>

cssも追加します。
styles.css

/* news.php */
.list table{
    border-collapse: collapse;
    margin: 20px 0;
    width: 100%;
}

    .list th,
    .list td {
        border:1px solid #a2aab0;
        padding: 20px 10px;
    }

    .list th {
        background-color: #ebeced;
    }

news.phpにアクセスしてみます。

こうなっていればOKです(newsテーブルの内容は反映されない状態です)。

一覧テーブルに、先程取得して$newsに格納したデータを表示させていきます。
配列になっているのでループで回しながら表示させます。
編集ボタンには、編集画面(edit_news.php)を設定しました。また、編集する記事のIDをパラメータで渡す必要があるので、編集ボタンにもループで回したidを付けています。
news.php

    <table>
        <thead>
            <tr>
                <th>id</th>
                <th>タイトル</th>
                <th>本文</th>
                <th>更新日時</th>
                <th>作成日時</th>
                <th>操作</th>
            </tr>
        </thead>
        <tbody>
-           <tr>
-               <td>1</td>
-               <td>ホームページをリニューアルしました。</td>
-               <td>サイトを更新しました。</td>
-               <td>2018-01-01 00:00:00</td>
-               <td>2018-01-01 00:00:00</td>
-               <td>
-                   <button type="button" class="btn btn-green" ">編集</button>
-                   <button type="button" class="btn btn-red">削除</button>
-               </td>
-           </tr>
+           <?php foreach($news as $new): ?>
+           <tr>
+               <td><?php echo $new['id']; ?></td>
+               <td><?php echo $new['title']; ?></td>
+               <td><?php echo $new['content']; ?></td>
+               <td><?php echo $new['updated_at']; ?></td>
+               <td><?php echo $new['created_at']; ?></td>
+               <td>
+                   <button type="button" class="btn btn-green" onclick="location.href='edit_news.php?id=<?php echo $new['id']; ?>'">編集</button>
+                   <button type="button" class="btn btn-red">削除</button>
+               </td>
+               
+           </tr>
+           <?php endforeach; ?>
        </tbody>
    </table>

ブラウザで確認します。

登録したnewsが一覧で表示されていればOKです。

記事の更新

記事の編集をする

news.phpで編集ボタンのリンクを作成したので、edit_news.phpを作成していきます。
adminフォルダにedit_news.phpを作成します。

ログインチェック

ログインチェックを付けます。
edit_news.php

<?php
    session_start();

    if($_SESSION['admin_login'] == false){
        header("Location:./index.html");
        exit;
    }

news.phpから渡されるidを受け取ります。
もし、idが渡されなかったら編集する記事がないので、news.phpに戻ります。
edit_news.php

    <?php
        session_start();

        if($_SESSION['admin_login'] == false){
            header("Location:./index.html");
            exit;
        }

+       $id = isset($_GET['id'])? htmlspecialchars($_GET['id'], ENT_QUOTES, 'utf-8'):'';

+       if($id == ''){
+           header("Location:./news.php");
+           exit;
+       }

記事のidを受け取ったらDBに接続し、idを指定したSELECT文を作成して実行します。
パラメータは:id$id
結果の配列を$newsに格納します。
edit_news.php

    if($id == ''){
        header("Location:./news.php");
        exit;
    }

+   //DB接続
+   try{
+       $dbh = new PDO("mysql:host=localhost;dbname=corporate_db","root","root");
+   }catch(PDOException $e){
+       var_dump($e->getMessage());
+       exit;
+   }
+   $stmt = $dbh->prepare("SELECT * FROM news WHERE id=:id");
+   $stmt->bindParam(":id",$id);
+   $stmt->execute();
+   $news = $stmt->fetchAll(PDO::FETCH_ASSOC);

phpの下にHTMLのフォームで表示します。
フォームのactionは更新を実行するupdate_news.php(このあと作成するファイルです)、methodはPOSTを指定します。
一覧表示では配列をループで回して表示しましたが、idを指定しているので、結果は表示したい記事の1件です。なので、結果は配列の0番目に入っています。表示は$news[0]['id']という形にあります。
idもhiddenで送ります。
edit_news.php

?>
<!DOCTYPE html>
<html>

<head>
    <!-- Global site tag (gtag.js) - Google Analytics -->
    <script async src="https://www.googletagmanager.com/gtag/js?id=UA-13xxxxxxxxx"></script>
    <script>
        window.dataLayer = window.dataLayer || [];
        function gtag() { dataLayer.push(arguments); }
        gtag('js', new Date());

        gtag('config', 'UA-13xxxxxxxxx');
    </script>

    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>記事編集</title>

    <link rel="icon" href="favicon.ico">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css"
        integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">

    <!-- css -->
    <link rel="stylesheet" href="styles.css">
</head>

<body>
    <header>
        <div class="container">
            <div class="header-logo">
                <h1><a href="dashboard.php">管理画面</a></h1>
            </div>

            <nav class="menu-right menu">
                <a href="logout.php">ログアウト</a>
            </nav>
        </div>
    </header>
    <main>
        <div class="wrapper">
            <div class="container">
                <div class="wrapper-title">
                    <h3>編集</h3>
                </div>
                <form class="edit-form" action="./update_news.php" method="POST">
                    <input type="hidden" name="id" value="<?php echo $news[0]['id']; ?>">
                    <div class="form-group">
                        <p>タイトル</p>
                        <input type="text" name="title" value="<?Php echo $news[0]['title']; ?>" required>
                    </div>
                    <div class="form-group">
                        <p>本文</p>
                        <textarea name="content"><?php echo $news[0]['content']; ?></textarea>
                    </div>
                    <button type="submit" class="btn btn-blue">更新する</button>
                </form>
            </div>
        </div>
    </main>
    <footer>
        <div class="container">
            <p>Copyright @ 2018 SQUARE, inc</p>
        </div>
    </footer>
</body>

</html>

news.phpから記事の編集ボタンを押してedit_news.phpを確認してみます。

指定した記事の中身が表示されていればOKです。
ですが、改行が入っている記事は、htmlで改行が入るように付けた改行コードも表示されてしまうのでstrip_tags()を使って改行コードを消します。
edit_news.php

    <div class="form-group">
        <p>本文</p>
-       <textarea name="content" ><?php echo $news[0]['content']; ?></textarea>
+       <textarea name="content" ><?php echo strip_tags($news[0]['content']); ?></textarea>
    </div>
    <button type="submit" class="btn btn-blue">更新する</button>

確認すると、改行タグが消えます。

更新を実行する

adminフォルダに更新を実行するupdate_news.phpを作成します。

ログインチェック

update_news.php

<?php
    session_start();

    if($_SESSION['admin_login'] == false){
        header("Location:./index.php");
        exit;
    }

値受け取り

update_news.php

    <?php
        session_start();

        if($_SESSION['admin_login'] == false){
            header("Location:./index.php");
            exit;
        }

+       $id = isset($_POST['id'])? htmlspecialchars($_POST['id'], ENT_QUOTES, 'utf-8'):'';
+       $title = isset($_POST['title'])? htmlspecialchars($_POST['title'], ENT_QUOTES, 'utf-8'):'';
+       $content = isset($_POST['content'])? htmlspecialchars($_POST['content'], ENT_QUOTES, 'utf-8'):'';
+       $content = nl2br($content);

UPDATE実行

INSERTやSELECT同様にDBに接続して、prepareでupdate文を作って実行していきます。
update文はUPDATE テーブル名 SET カラム1='カラム1に入れる値', カラム2='カラム2に入れる値' WHERE id=updateするid;という形です。
update_news.php

    $id = isset($_POST['id'])? htmlspecialchars($_POST['id'], ENT_QUOTES, 'utf-8'):'';
    $title = isset($_POST['title'])? htmlspecialchars($_POST['title'], ENT_QUOTES, 'utf-8'):'';
    $content = isset($_POST['content'])? htmlspecialchars($_POST['content'], ENT_QUOTES, 'utf-8'):'';
    $content = nl2br($content);

+   //DB接続
+   try{
+       $dbh = new PDO("mysql:host=localhost;dbname=corporate_db","root","root");
+   }catch(PDOException $e){
+       var_dump($e->getMessage());
+       exit;
+   }

+   $stmt = $dbh->prepare("UPDATE news SET title=:title, content=:content, updated_at=now() WHERE id=:id");
+   $stmt->bindParam(":title",$title);
+   $stmt->bindParam(":content",$content);
+   $stmt->bindParam(":id",$id);
+   $stmt->execute();

UPDATE実行したらnews.phpにリダイレクトします。
edit_news.php

    $stmt = $dbh->prepare("UPDATE news SET title=:title, content=:content, updated_at=now() WHERE id=:id");
    $stmt->bindParam(":title",$title);
    $stmt->bindParam(":content",$content);
    $stmt->bindParam(":id",$id);
    $stmt->execute();

+   header('location:./news.php');

news.phpから編集ボタンを押して、記事を編集してみます。
記事一覧にリダイレクトされ、更新した部分と、更新日時がupdateされていれば成功です。

削除機能を実装する

削除ボタンの実装

news.phpの削除ボタンを実装していきます。
削除ボタンは編集ボタンと実装方法が異なります。
編集ボタンはidがパラメータで送られているので、edit_news.php?id=1を直接ブラウザで叩いても編集画面に飛べます。
ですが、削除ボタンが同じ実装方法の場合、誤ってURLにアクセスしてしまった場合に削除が実行されてしまっては困ります。
なので、POSTで実装していきます。

JSで確認アラート表示

まず、削除ボタンをクリックしたら、「本当に削除しますか?」とjavascriptを使って確認アラートを表示させたいと思います。
削除ボタンにjs用のdeleteクラスを追加します。
news.php

    <td>
        <button type="button" class="btn btn-green" onclick="location.href='edit_news.php?id=<?php echo $new['id']; ?>'">編集</button>
-       <button type="button" class="btn btn-red">削除</button>
+       <button type="button" class="btn btn-red delete">削除</button>
    </td>

footerの下にjsを追加します。
そして、クラスdeleteをクリックしたら確認アラートが表示されるように設定します。
OKを押した場合の動作はこれから実装するので今は何も書きません。
キャンセルを押した場合は、return false;にして動作を中止します。
news.php

    <footer>
        <div class="container">
            <p>Copyright @ 2018 SQUARE, inc</p>
        </div>
    </footer>
+   <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
+   <script>
+       $(".delete").click(function(){
+           if(confirm("本当に削除していいですか?")){
+               //OK
+           }else{
+               //キャンセル
+               return false;
+           }
+       })
+   </script>

確認アラートが表示されるか確認します。
どの削除ボタンを押しても確認アラートが表示されることを確認します。

アラートが表示されたので、それぞれの削除ボタンに記事のIDを紐づけます。
とりあえず、削除ボタンを押したときに、「ID:〇〇番の記事を本当に削除していいですか?」表示させたいと思います。
削除ボタンに独自属性を追加して、独自属性にidをもたせます。独自属性はdata-idのように、頭にdata-をつけることで認識されます。今回はdata-idという属性にしました。
news.php

    <td>
        <button type="button" class="btn btn-green" onclick="location.href='edit_news.php?id=<?php echo $new['id']; ?>'">編集</button>
-       <button type="button" class="btn btn-red delete">削除</button>
+       <button type="button" class="btn btn-red delete" data-id=<?php echo $new['id']; ?>>削除</button>

JSでidを取得して確認アラートを編集します。
idはthis.dataset.idで取得できます。
this(クリックしたボタンの).dataset(独自属性の).idという風に取得できます。
news.php

    <footer>
        <div class="container">
            <p>Copyright @ 2018 SQUARE, inc</p>
        </div>
    </footer>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <script>
        $(".delete").click(function(){
+           var id = this.dataset.id;
-           if(confirm("本当に削除していいですか?")){
+           if(confirm("ID:"+id+"番の記事を本当に削除していいですか?")){
                //OK
            }else{
                //キャンセル
                return false;
            }
        })
    </script>

ここまでできたら確認してみます。
アラートでIDが取得できてればOKです。

次に、OKを押して削除を実行するようにします。
それぞれの削除ボタンに応じたフォームを作成するので、ループの中にfromを追加します。
削除は、delete_news.phpで実行するので、actionはdelete_news.php、methodはPOSTを指定します。
そして「delete_form_削除するID」というidを割り当てます。
そうすることで、JSでどのformを実行するか判断できるようになります。
formにはhiddenで削除する記事のidを入れておきます。
news.php

    <td>
        <button type="button" class="btn btn-green" onclick="location.href='edit_news.php?id=<?php echo $new['id']; ?>'">編集</button>
        <button type="button" class="btn btn-red delete" data-id=<?php echo $new['id']; ?>>削除</button>
+       <form method="POST" action="./delete_news.php" id="delete_form_<?php echo $new['id']; ?>">
+           <input type="hidden" value="<?php echo $new['id']; ?>" name="id">
+       </form>
    </td>

JSでformを送信します。
news.php

    <script>
        $(".delete").click(function(){
            var id = this.dataset.id;
            if(confirm("ID:"+id+"番の記事を本当に削除していいですか?")){
                //OK
+               $("#delete_form_"+id).submit();
            }else{
                //キャンセル
                return false;
            }
        })
    </script>

確認します。
確認アラートでOKを押して、delete_news.phpに飛べばOKです。
ここではdelete_news.phpはまだ作成していないのでnot foundになりますが、URLバーで飛び先は確認できます。

削除機能の実装

adminフォルダにdelete_news.phpを作成します。

ログインチェック

delete_news.php

<?php
    session_start();

    if($_SESSION['admin_login'] == false){
        header("Location:./index.php");
        exit;
    }

データの受け取り

delete_news.php

    <?php
        session_start();

        if($_SESSION['admin_login'] == false){
            header("Location:./index.php");
            exit;
        }

+      $id = isset($_POST['id'])? htmlspecialchars($_POST['id'], ENT_QUOTES, 'utf-8'):'';

記事の削除

DBに接続して、DELETE文で削除を実行します。
DELETE文はDELETE FROM テーブル名 WHERE id=削除したいidです。
削除したら、news.phpにリダイレクトさせます。
delete_news.php

    $id = isset($_POST['id'])? htmlspecialchars($_POST['id'], ENT_QUOTES, 'utf-8'):'';

+    //DB接続
+   try{
+       $dbh = new PDO("mysql:host=localhost;dbname=corporate_db","root","root");
+   }catch(PDOException $e){
+       var_dump($e->getMessage());
+       exit;
+   }

+   $stmt = $dbh->prepare("DELETE FROM news WHERE id=:id");
+   $stmt->bindParam(":id",$id);
+   $stmt->execute();

+   header('location:./news.php');

確認してみます。

news.phpから削除ボタンをクリックして、指定した記事が削除されていればOKです。
削除前

削除後

これで一通りのCRUD機能が実装できました。

コード

https://github.com/bluecode-io/web-basic/tree/basic5-2