MAMPのMySQLを使ったPHPのTODOアプリ

みなさんこんにちは、WEB制作のエンジニアのjonioです。
MAMPのMySQLを使ったPHPのTODOアプリの作り方をこの記事で勉強しました。
この記事はPHPをProgateで勉強した後に勉強するのにめちゃくちゃおすすめです。
記事を書いた方ありがとうございます、あなたは神です。
勉強した内容を他の場面でも使いたいので忘れないための自分へのメモとして作り方の解説を書きます。
この記事を書いた方の内容をパクろうとかそんな気持ちはないので自分なりに調べた情報も多少追加して画像で情報を補ったりしています。
自分が後で思い出せるように書くので他の人が見ても理解できると思います。
それではメモの開始です。
目次
- 1 必要なファイル
- 2 MAMPのMySQLに接続
- 3 ファイルからデータベースに接続
- 4 例外処理の書き方
- 5 ログインページ(login.php)の作成
- 6 if分の必要性
- 7 ざっくりとしたログインの流れ
- 8 ログインの流れ
- 9 ログインした後に移動するページ(main.php)の作成
- 10 タイトルと中身を追加するページ(create.php)の作成
- 11 表示している内容の削除(delete.php)の実装
- 12 レコードを削除した時のURLの確認
- 13 表示している内容の編集機能(edit.phpとedit_done.php)の実装
- 14 ログインパスワードのハッシュ化
- 15 名前とパスワードを追加するページ(signUp.php)の作成
- 16 ハッシュ化した状態でログインする
- 17 簡単な検索機能
- 18 実装の考え方
必要なファイル
todoフォルダの中に↓のファイルを作成します。
ログイン画面や編集画面で画像を使うのですが画像に関しては素材サイトで取ってください。
MAMPのMySQLに接続
MAMPのMySQLにターミナルから接続しますが↓の記事で解説しています。
ターミナルでMySQLに接続したら↓のSQLを入力します。
そしてターミナルで「show databases;」と入力するとデータベースの一覧が表示されて「todo」があるのでそれを使うためにターミナルで「use todo;」と入力するとtodoデータベースを使う状態になります。
そしてpostsテーブルとusersテーブルの中身 を見るために↓のSQLをターミナルで入力します。
そうするとターミナルで↓の表示がされるはずです。
↑の赤枠が表示されれば上手くいってます。
ファイルからデータベースに接続
データベースに接続してpostsテーブルとusersテーブルのカラムの中に何が入っているかをターミナルで見ましたがファイルからデータベースに接続できるようにします。
大元のphpのファイルをデータベースに接続できるようにして他のphpのファイルは大元のファイルの情報を使うことができるようにします。
大元のファイルはdb_connect.phpです。
データベースに接続するためのコード
db_connect.phpのコードを↓にします。
このファイルはWEB上のページは必要ないのでHTMLを含んでいません。
データベースにファイルから接続するためのコードは↓です。
これはdb_connect.phpのコードの9行目になります。(「try」や「catch」などありますが後で説明します)
9行目の「new PDO(DSN,DB_USERNAME,DB_PASSWORD);」と3行目〜5行目が対応しているのですが対応は下記となります。
- 9行目のDB_USERNAME:3行目
- 9行目のDB_PASSWORD:4行目
- 9行目のDSN:5行目
3行目〜5行目を9行目のDB_USERNAME・DB_PASSWORD・DSNに代入しているのですが2行目〜5行目の説明をします。
2行目の「todo」はデータベース名です。
ターミナルでデータベースを作成したときに↓のSQL文を入力したはずです。
この時にtodoデータベースを作っています(↑のSQL文の1行目)がそれです。
3行目はユーザー名を「root」にして4行目はパスワードを「root」にしているということですがデフォルトだとrootになっています。
パスワードが分からない場合MAMP上で確認する方法はこうします。(パスワードが分かっている場合はこの部分の説明は見なくていいです)
MAMP上でのパスワードの調べ方
MAMPを起動した状態で↓の赤枠をクリックします。
すると↓のページになりますがページの下の方にある赤枠をクリックします。
するとユーザー名(赤枠)とパスワード(青枠)が表示されますのでユーザー名とパスワードを確認してください。
コードの2行目・3行目・4行目ですが「todo」・「root」以外の部分はそのまま書いて大丈夫です。
5行目の「‘mysql:host=localhost;charset=utf8;dbname=’.DB_DATABASE」ですがこういう意味です。
- mysql:host=localhost:データベースはMySQLでlocalhostを使う
- charset=utf8:文字コードはutf8を使う、これで日本語の文字化けを防げる
- dbname=’.DB_DATABASE:コードの2行目にあるtodoデータベースを使う
5行目の「define(‘DSN’,‘mysql:host=localhost;charset=utf8;dbname=’.DB_DATABASE);」は「‘mysql:host=localhost;charset=utf8;dbname=’.DB_DATABASE」の内容をDSNに代入するという意味です。
これでデータベースにファイルから接続できますがプログラムを実行中に何らかの不具合が出た場合はアプリが止まってしまいこれはまずいので不具合が出た時の処理をしてアプリが止まらないようにします。(これを例外処理といいいます)
例外処理の書き方
例外が起きそうな部分を「try」と「catch」で挟み例外が起きた時の処理をcatchの後に書きます。
ざっくりコードにすると↓になります。
db_connect.phpのコードの説明に戻りますが例外処理があります。
8行目〜15行目です。
7行目のdb_connect関数の中に入っていますが他のファイルで使うためです。
9行目はデータベースに接続するための記述です。
10行目はこれがあるとエラーが見やすくなるので書いていてこんなもんと思っていいです。
11行目で「return」を使っていますがこれは他のファイルで使うためです。
12行目の「PDOException $e」は例外処理のオプションでとりあえず書いています。
13行目の「$e -> getMessage()」はエラーの内容を生成するための記述です。
14行目は例外の処理を終了するための記述です。
これでファイルからデータベースに接続するための設定ができたので次はログインページ(login.php)の説明をします。
ログインページ(login.php)の作成
login.phpのコードを↓にします。
これで↓と表示されます。
login.phpのコードの2行目ですがデータベースに接続するファイル(db_connect.php)のコードをlogin.phpでも使うためにあります。
4行目・5行目・6行目の「$_POST」ですがフォームの値を受け取るために使っていてlogin.phpの45行目・46行目・47行目の値を受け取ります。
対応は下記になります。
4行目:45行目のname=”name“のname
5行目:46行目のname=”password“のpassword
6行目:47行目のname=”submit“のsubmit
4行目・5行目・6行目の情報はログインするために必要な情報(名前とパスワード)を入力する時に使い今は必要ないのでとりあえずこのままにします。
11行目〜20行目に例外処理のtryとcatchがあり前の行(8行目)にif文がありますがtryとcatchはデータベースに接続してログインする部分です。(それについては後で説明します)
db_connect.phpではif文がなかったのにlogin.phpではtryとcatchがif文に入っているかの説明をします。
if分の必要性
ログインのページはlogin.phpで名前とパスワードを入力して「Log in」ボタンを押して名前とパスワードが正しい場合はmain.phpのページに飛ぶのですが今のままだとURLにmain.phpを入力してもmain.phpに移動することができます。
これだとログインページ(login.php)の意味がないですよね。
そこでログインするのにlogin.phpの「Log in」のボタンを押して名前とパスワードが正しい場合のみログインできる(main.phpに移動できる)ようにします。
↓がlogin.phpのページですが「Log in」(赤枠)がどこにあるかを確認してください。
「Log in」を押した時を条件に使いif文でログインできるようにするのですがそれが8行目の「if(!empty($submit))」です。
if(empty())で空っぽの場合という意味になるのですが「!」が付くことで空っぽではない場合ということになります。
empty($submit)の「$submit」が「Log in」を押した時で「$submit」はlogin.phpの6行目の事です。
これでひとまずLog inを押した時以外はログインできない(main.phpに移動することができない)のですが何を入力してもいいわけではなく決められた言葉を入力しないといけません。
今からログインの流れついて解説します。
ざっくりとしたログインの流れ
ログインのおおまかな流れはこうなります。
login.phpのページに入力した名前とパスワードをtodoデータベースのusersテーブルの中にある名前とパスワードと照し合わせて同じならログインする。
todoデータベースのusersテーブルの中にある名前とパスワードですがターミナルでデータベースを作成した時に作っています、↓の2つの赤枠の下の部分です。
nameカラムが「田中太郎」でpasswordカラムが「taro」のやつです。
ログインする時の情報ですがnameカラムの「田中太郎」を名前にしてpasswordカラムの「taro」をパスワードにします。
ログインする時にデータベースに接続してMySQLの中の田中太郎とtaroを使うのでdb_connect.phpを使わないといけません。
そのためにlogin.phpの2行目があります。
9行目の「db_connect();」はdb_connect.phpのdb_connect関数の事です。
12行目の「select * from users where name = :name and password = :password」はSQL文ですが「:name」と「 :password」はとりあえずSQL文を作ってどんな値になるかは後で決める時に使います。
13行目ですが「$sql」は12行目のことでダブルコーテーション(” “)に挟まれているのでダブルコーテーションの中身は文字列ですがこのままではSQL文と認識されなく認識するために「$pdo -> prepare($sql);」と書くことで認識できるようにしています。
14行目と15行目の「bindParam」ですが「:name」・「:password」の値を決めています。
対応は下記になります。
- 14行目の「bindParam」:14行目の「:name」を「$name」にしていて「$name」は4行目の「$name」
- 15行目の「bindParam」:15行目の「:password」を「$password」にしていて「$password」は5行目の「$password」
16行目の「execute()」でSQL文を実行しています。
以上12行目〜16行目がログインの部分で流れをまとめると下記になります。
ログインの流れ
12行目でusersテーブルのname(名前)とpassword(パスワード)を後で決める事ができるようにする。
13行目で12行目のSQL文を認識できるようにする。
14行目でフォームから受け取った名前を12行目のnameに代入して15行目でフォームから受け取ったパスワードを12行目のpasswordに代入する。
16行目で12行目のSQL文を実行してフォームで入力した名前とパスワードがusersテーブルの中の名前とパスワードと一致したらログインする。
一致するかの確認は22行目ですがすぐ説明するので少し待ってください。
これでログインは完成しましたがログインしたらログインした後のページを移動しないといけません。
また名前かパスワードが間違っていたら「名前かパスワードが違います」みたいな表示がされないといけないですがそのための記述が22行目〜27行目です。
名前かパスワードが間違っている場合の表示は25行目〜27行目でログインした後のページの移動は22行目〜24行目です。
23行目ですが「header(“Location:〜”);」で〜のページにリダイレクトします。
24行目の「exit」はここで処理が終了するという意味です。
22行目の「$stmt -> fetch(PDO::FETCH_ASSOC)」は$stmt(12行目のSQL文の中身)の中から結果(フォームの名前とパスワードがpostsテーブルの名前とパスワードが同じという事)を取り出すことができるという意味です。
$stmt -> fetch(PDO::FETCH_ASSOC)を$rowと置いているのは後で使うためです。
次はログインした後に移動するページ(main.php)の作成です。
ログインした後に移動するページ(main.php)の作成
main.phpのコードを↓にします。
これで↓と表示されます。
1行目〜13行目のコードですがlogin.phpに似てますよね。
main.phpのコードの6行目ですがpostsテーブルの中身を新しい内容から全て表示したいのでこの書き方です。
1行目〜13行目の残りの内容はlogin.phpを理解していれば理解できると思います。
postsテーブルの中身の表示するためのコードを見ます。
postsテーブルの中身の表示するためのコード
38行目〜47行目です。
while文でpostsテーブルの中身を表示しますが38行目の「$row = $stmt->fetch(PDO::FETCH_ASSOC)」はlogin.phpの22行目です。
「$stmt->fetch(PDO::FETCH_ASSOC)」でpostsテーブルの中身を全て取り出してそれを「$row」と置いて$row[“id”]・$row[“title”]・$row[“content”]・$row[“time”]と書く([ ]の中にカラム名を書く)ことでテーブルの中身を表示します。
postsテーブルは↓ですのでカラム名を見てください。
次はpostsテーブルのtitleカラムとcontentカラムに内容を追加するページ(create.php)の作成をしてmain.phpでの表示を解説します。
タイトルと中身を追加するページ(create.php)の作成
create.phpのコードを↓にします。
これで↓と表示されます。(「メイン画面に戻る」は後で記述を追加します)
データベースに接続する部分のコードがlogin.php・main.phpに似ています。
create.phpの13行目ですがinsert文でpostsテーブルのtitleカラムとcontentカラムに内容を追加するのですが追加する内容を後で決める(create.phpのフォームで入力した内容を使う)ため追加する内容を「values(:title,:content)」としています。
新規登録が終わったらpostsテーブルのカラムの内容を表示するページ(main.php)に移動しないといけないので19行目があり「header(“Location:〜.php”);」で「〜.php」に移動します。
3つ登録したmain.phpの表示が↓になります。(お好きな内容を登録して表示の確認をしてください)
最後に「メイン画面に戻る」ボタンを作ります。
create.phpのコードを↓にします。
また↓のcssも追加してください。
これで新しく内容を追加するページは完成したので次はpostsカラムの内容を表示しているページ(main.php)で表示している内容の削除の実装(delete.php)をします。
表示している内容の削除(delete.php)の実装
delete.phpのコードを↓にします。
削除は機能だけにしたくてdelete.phpは表示するページは必要ないのでHTMLはありません。
削除機能ですがtodoデータベースの中にあるpostsテーブルのidを探して該当するidの内容を削除します。
idは↓を見てください。
idを探せるようにするためにmain.phpを編集します。
コードを↓にしてください。
main.phpの45行目を編集しています。
aタグですが「href=”delete.php?id=<?php echo $row[‘id’];?>”」の意味が分からないと思うので説明します。
href=”delete.php?id=<?php echo $row[‘id’];?>”
説明する前にdelete.phpの4行目の「$_GET[‘id’];」から説明しないといけません。
PHPで情報を受け取るのに「$_POST」と「$_GET」があります。
今回は$_GETを使いますがこれを使うことで↓の様にURLに受け取った情報が表示されます。(この部分はまた後で説明します)
$_POSTと$_GETの使い分けですが説明すると長くなるので↓の記事を読めば分かると思います。
それでは「href=”delete.php?id=<?php echo $row[‘id’];?>”」の説明ですがいきなり説明すると理解できないかもしれないのでまずは↓の例で説明します。
$_GETを使ってからaタグで<a href=”〜?id=1″>とすると「〜」のファイルにデータベースのテーブルのidカラム1のレコードの内容を渡すという意味になります。
「id=1」を「id=2」にすればidカラム2の内容をレコードに渡すという意味です。
「id=●」の●を数字にすると何を削除しようとしても同じidのレコードを削除することになって意味がありません。
図にすると↓です。
そこで選択したidのレコードの内容を削除するためにmain.phpの45行目の様に「<a href=”delete.php?id=<?php echo $row[‘id’];?>“>」にしています。
次はレコードを削除した時のURLの確認です。
レコードを削除した時のURLの確認
削除を押したら<a href=”delete.php?id=<?php echo $row[‘id’];?>“>が起動してレコードの内容が削除されますがそれと同時にリダイレクトが動いてURLの確認ができないのでdelete.phpの14行目・15行目(リダイレクトのコード)をコメントアウトしてください。
これで↓の「id=6」のレコードを削除します。
するとURLに↓が表示されて「id=6」のレコードが削除されたのが確認できます。
今のままではmain.phpに新しい内容を表示する際にURLにcreate.phpを入力して登録のページに移動しないといけません。
それは面倒なのでmain.phpに登録のページのリンクの追加をします。
登録のページのリンクの追加
main.phpのコードを↓にします。
これで表示が↓になり登録のページのリンクが追加されているのが確認できます。
次はmain.phpのページのタイトルと本文の内容の編集機能です。
表示している内容の編集機能(edit.phpとedit_done.php)の実装
コードの解説の前に編集の流れはこうなります。
main.phpのページの「編集」をクリック。
「記事ID3」をクリックしたとします。
するとtodoデータベースの中のpostsテーブルの中の記事ID3の「タイトル」の中身の情報と「本文」の中身の情報がedit.phpに渡されて編集のページ(edit.php)に表示されます。
そして編集が終わって「更新」を押したら更新完了のページ(edit_done.php)に移動します。
その際に編集する「タイトル」と「本文」の情報がedit_done.phpに渡されてpostsテーブルのidが3の記事の内容が変更されて編集が完了です。
この流れを実装します。
edit.phpのコードを↓にします。
またcssに↓を追加してください。
次にedit_done.phpのコードを↓にします。
それではコードの解説をしますがmain.phpのページの↓の赤枠をクリックした時に「タイトル」と「本文」の情報をedit.phpに渡さないといけません。
そのためにmain.phpのコードを少し修正します。
これで「編集」ボタンを押した時に指定したIDの編集画面に飛べるようになりました。
edit.phpですがmain.phpで「編集」ボタンを押した時に該当するIDの情報を受け取らないといけません。
だからedit.phpのコードの9行目のwhere句は「id = :id」となっています。
edit.phpの後のコードは今まで出てきたコードが理解できれば理解できると思います。
edit.phpの9行目ですがidを指定しただけで今の状態のSQL文は「SELECT」であって「UPDATE」ではないので更新していません。
そこでedit_done.phpのSQL文を「UPDATE」にしますがそれがedit_done.phpの12行目です。
これで編集機能が完成と思いたいですがやらないといけないことがあります。
main.phpからedit.phpにpostsテーブルのidの情報を渡す時にmain.phpに44行目の「<?php echo $row[‘id’];?>」がありましたがedit.phpにはないのでedit_done.phpにidの情報を渡すことができません。
そこでedit.phpに「<?php echo $row[‘id’];?>」を加えますがこれを書く部分は入力欄が必要ないのでなしにします。
edit.phpのコードを↓にします。
次はログインパスワードのハッシュ化です。
ログインパスワードのハッシュ化
ログインする時のパスワードですがデータベースにそのまま保存されている状態です。
↓はusersテーブルです。
今の私のスキルでは他人のデータベースに接続する方法を知りませんがデータベースに接続したらパスワードが丸見えの状態になっているのはセキュリティ上良くないです。
だからパスワードを別の文字列に変換するのですがこれをハッシュ化といいます。(文字列への変換はランダムに行われるのではなく一定の規則に基づいて変換されるので同じパスワードを何度ハッシュ化しても同じ文字列になります)
ちなみに私が「taro」をハッシュ化すると「$2y$10$CED/5u4LW4B3wKS92yUE..HldJsdBk0ZJHeHQ5LRckH.S3ft27U3」になります。(ハッシュ化した値はパソコンによって異なります)
ハッシュ化後のパスワードからハッシュ化前のパスワードを辿ることもできます。
図にすると↓です。
ハッシュ化してログインする流れ
今回実装するのに新しい内容は下記になります。
- パスワードのハッシュ化:password_hash( )
- ハッシュ化される前と後の値が等しいのか確認できる:password_verify( )
これをふまえた上でハッシュ化したパスワードでログインする流れはこうなります。
- 新規登録する時にパスワードをハッシュ化する(このアプリを作った時に最初に設定した名前(田中太郎)とパスワード(taro)はハッシュ化されていません)
- フォームに入力したパスワードと「password_hash( )」でハッシュ化したパスワードが「password_verify( )」を使って一致するかを確認する
- フォームに入力したパスワードとハッシュ化したパスワードが同じならログインする
今の状態では名前とパスワードは「名前:田中太郎・パスワード:taro」しかなくてパスワードのハッシュ化ができないので名前とパスワードを登録するページ(signUp.php)も作りまずは名前とパスワードを登録するページを作ります。
名前とパスワードを追加するページ(signUp.php)の作成
新たにsignUp.phpを作ります。
signUp.phpのコードを↓にします。
cssに↓を追加してください。
これで↓と表示されます。
名前を「田中太郎2」・パスワードを「taro2」と入力して「新規登録」のボタンを押してください。
すると新規登録ができず「SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column ‘password’ at row 1」
のエラーが表示されて新規登録ができません。
「select * from password;」でパスワードカラムを見るとパスワードが追加されていないのが分かります。
このエラーの意味は「passwordカラムに登録する値は文字が長すぎます」という意味です。
ターミナルでpasswordカラムに登録できる文字数の確認を「desc users;」でします。
すると↓になりますがvarchar(8)となっているので8文字しかパスワードには8文字しか使えないとなっています。
使える文字数を長くすればいいので80文字にしますがターミナルで「ALTER TABLE users MODIFY password varchar(80);」と入力します。
そして新規登録のページ(signUp.php)で名前を「田中太郎2」・パスワードを「taro2」と入力して登録すると↓の様に登録できます。(赤枠)
データベースのusersテーブルを見ると名前とパスワードが追加されているのが分かります。(パスワードがハッシュ化されていますがハッシュ化はすぐに説明します)
13行目ですがテーブルのカラムに値を追加するのでINSERT文を使っています。
ハッシュ化は16行目で第二引数に「PASSWORD_DEFAULT」のはこんなもんと思っていいです。
password_hash( )についてはこの記事に詳しい説明があります。
残りのコードは今までと同じなので省略します。
これでハッシュ化は完成したのですがログインページ(login.php)でログインします。
ハッシュ化した状態でログインする
パスワードが「taro2」をハッシュ化すると「$2y$10$CED/5u4LW4B3wKS92yUE..HldJsdBk0ZJHeHQ5LRckH.S3ft27U3.」ですがパスワード欄にこれを入力するのは嫌ですよね。
一般的に登録した人がパスワードをハッシュ化されているなんて知らないし普通は「taro2」を使うので「taro2」でログインできるようにしないといけません。
そこで「password_verify()」を使って入力したパスワードをハッシュ化した時に正解のパスワードになるかの確認をすることでログインできる様にします。
「password_verify()」は「password_verify(ハッシュ化する前の値,ハッシュ化した後の値)」と書きますがこの記事が詳しく説明してます。
login.phpのif文を↓にします。
これでログインしてもまだ上手くいきません。
理由はlogin.phpの「try{ }」の中の「$sql = “select * from users where name = :name and password = :password”;」 ですが「password = :password」があります。
これはハッシュ化する前のパスワードを使うことになるのですがハッシュ化した後のパスワードを使いたいのでこのままではエラーになります。
それでは意味がないので「password = :password」を取ります。
これで「try{ }」は↓になります。
これだとパスワードは違うけど名前は同じ人がいる場合ログインはどうなるんだろう?と思いますがif文でパスワードの認証ができるので問題ないです。
これでパスワードのハッシュ化の完成です。
最後に簡単な検索機能の追加をします。
簡単な検索機能
簡単な検索機能というのは「同じ語句を含むレコードの内容を表示する」という意味です。
同じ語句でもひらがな・カタカナ・漢字の識別はできません。
だから「でもタイトル」なら検索に引っかかりますが「でもたいとる」では検索に引っかかりません。
普通はひらがな・カタカナも認識できるので検索の機能としては弱過ぎるので商品としては使えないです。
main.phpには投稿した内容が表示されています。
私の場合だと↓です。
「タイトル」・「本文」の内容で検索して検索した内容のレコードを表示できるようにします。
↓が例ですが「でもタイトル1」と入力しています。
実装の考え方
↓の流れで実装します。
- main.phpに検索のフォームを設置
- フォームから送られた情報を変数として受け取る
- 受け取った変数の中身をデータベースのタイトルカラム・本文カラムにあるか探しあった場合は該当するレコードの内容を表示する
- 検索の機能はSQLの「LIKE文」で行う
それでは実装しますがまずはmain.phpにフォームを設置します。
フォームの設置
main.phpのコードを↓にします。(HTMLの部分だけを載せます)
次はフォームから送られた情報を変数(main.phpのHTMLのコードの41行目のname属性の値)として受け取ります。
main.phpのコードを↓にします。(PHPの部分だけを載せます)
6行目の「search」・7行目の「search_word」が変数です。
受け取った変数をデータベースから探す
main.phpのコードを↓にします。(PHPの部分だけを載せます)
検索する時ですが何も語句を入力せずに検索できたら意味がないです。
そこで19行目のif文があります。(login.phpを初めて作った時のログインの実装方法と考え方は同じです)
22行目は本文とタイトルの内容で検索できますが本文だけで検索するなら「or title like ‘%$search_word%’」を取ってタイトルだけで検索するなら「content like ‘%$search_word%’ or」を取ればいいです。
これで実装は完成です。