今回より複数回に分けてPHP、MySQLを利用したECサイトの完全自作についてまとようと思います。
けっこうなボリュームがありますので、これからPHP/MySQLを勉強しようとしているかたの参考になれば幸いです。
はじめに、これから作成するECサイトの完成形を載せておきます。↓
サイトは下記URLから飛べますので、是非ログインして架空の商品を購入したり、色々試してみて下さい。
これからまとめる内容を覚えれば、これらの事が可能になります。
なお、参考にさせて頂いた書籍は「気づけばプロ並みPHP」になります。PHPの書籍は何冊か購入しましたが、素人の私にとってこの1冊が一番分かりやすく、PHPとMySQL(データベース)の基礎を身につける事が出来ました。おススメです。↓
それではまず、これから作成するサイトの概要から説明いたします。
目次
概要
全体像
おおまかに分けると、3つのブロックから成り立ちます。
1つは、「公開するECサイトのページ」
2つ目は「スタッフ、商品の管理ページ」
3つ目は「データベース」になります。
図のように、ユーザーがECサイトを閲覧し、商品を購入する流れとしては、
①管理ページの権限があるスタッフが商品情報をデータベースに登録。
②ESサイトは①で登録された情報をデータベースから読みだして表示させる。
③ユーザーがECサイトにてアカウントを登録後、商品の購入を行う。
④ユーザーのアカウント情報、購入情報をデータベースに保存。
⑤購入情報の詳細をECサイトからmailにてユーザーに自動返信。
ざくっとですが、このような流れの仕組みをPHPとMySQLで作ります。
開発環境
PCはWindowsを使用する事前提で進めますが、Macでもほぼ同じだと思います。
WEBサーバーとデータベースは「XAMPP」を使用します。
XAMPPについては過去記事でインストール方法をまとめていますので、分からなければ参照してください。↓
スタッフ登録画面作成の流れ
図のような流れの登録画面を作成します。とりあえず今回は、staff_add.php~staff_add_done.phpまでを作成します。まだdbにスタッフ情報がないので、とりあえず1人作成してから、次回、先頭のlogin画面を作成していきたいと思います。
テーブルの作成
それではまず、スタッフの登録画面から作成します。
ここに登録されたスタッフのみが、サイトに乗せたい商品の情報をデータベースに登録出来るようにします。
XAMPPのMySQLを立ち上げて、データベースを作成します。データベース名は「shop」とします。
続いてshopの配下に、スタッフ管理のテーブルを作成します。テーブル名は「mst_staff」とします。
カラムは3とし、下図のように「code」「name」「password」のカラム名、データ型、AI(AUTO_INCREMENT)を設定します。
カラム名 | データ型 | その他 |
code | int | AI |
name | varchar 値15 | – |
password | varchar 値32 | – |
PHPファイルの作成場所
ここからphpファイルの作成に入ります。
xamppのApache(WEBサーバー)のドキュメントルート(サーバーのアクセス先)は図の通りxamppフォルダのhtdocs配下になります。
デフォルトでindex.htmlが入っていると思うので、ローカルホストのipをブラウザで打ち込めばそれが開くはずです。ちなみにローカルホストは「127.0.0.1」でもアクセス出来ます。
それではここに「staff」というフォルダを作成し、その配下にどんどんphpファイルを今後作成していきます。今回はまず「staff_add.php」~「staff_add_done.php」のファイルを作成します。
※これからphpファイルを作成していきますので、xamppのApacheとmySQLを立ち上げておきます。(作成後の確認で必要な為。)
追加スタッフ情報入力画面
staff_add.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<?php //session_start(); //session_regenerate_id(true); //if(isset($_SESSION["login"]) === false) { // print "ログインしていません。<br><br>"; // print "<a href='staff_login.html'>ログイン画面へ</a>"; // exit(); //} else { // print $_SESSION["name"]."さんログイン中"; // print "<br><br>"; //} //?> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>スタッフ追加</title> <link rel="stylesheet" href="../style.css"> </head> <body> <form action="staff_add_check.php" method="post"> スタッフ追加<br><br> スタッフ名<br> <input type="text" name="name"> <br><br> パスワード<br> <input type="password" name="pass"> <br><br> パスワード再入力<br> <input type="password" name="pass2"> <br><br> <input type="button" onclick="history.back()" value="戻る"> <input type="submit" value="OK"> </form> </body> |
先頭にあるphp文はコメントアウトさせておきます。ここはログインの有無をチェックする部分ですので、とりあえず今は使用しません。
残りはhtmlになりますが、head部は後々cssでスタイリングする事を考えて、viewport設定等しています。
肝心のbody部ですが、<form>タグを使用しています。actionはページのとび先で、methodはデータの送り方になります。
<input type=”text” では、text入力ボックスが表示されます。name=”name”>は、そのtextボックスに入力した値に名前を付ける役割を果たしています。nameに指定した値が変数になって、そこにtextボックスの内容を代入する感じですね。
<input type=”password” は、同じくtext入力ボックスが表示されますが、セキュリティ対策のため入力した値は「****」で表されます。name=”pass”>はtextの時と同じです。
<input type=”button” はボタンを生成します。onclick=”history.back()”で、前のページに戻る機能が加わります。 value=”戻る”>は、ボタンの中に表示される値になります。
<input type=”submit”は、これもボタンが生成されますが、buttonと異なるのは「formタグ内の情報を次ページへ送り出す」性質がある事です。これが重要です。 value=”OK”>は同じくボタン内に表示される値です。
つまり、formタグに囲まれている内容が、下部の<submit>で生成されるボタンを押すことによって、postでstaff_add_check.phpのページに渡されるのです。
※getとpostの違いは過去記事でまとめています。↓
127.0.0.1/staff/staff_add.phpにアクセスするとこのような画面が出るはずです。↓
入力チェック画面
staff_add_check.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
<?php //session_start(); //session_regenerate_id(true); //if(isset($_SESSION["login"]) === false) { // print "ログインしていません。<br><br>"; // print "<a href='staff_login.html'>ログイン画面へ</a>"; // exit(); //} else { // print $_SESSION["name"]."さんログイン中"; // print "<br><br>"; //} ?> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>スタッフ追加チェック</title> <link rel="stylesheet" href="../style.css"> </head> <body> <?php //require_once("../common/common.php"); //$post = sanitize($_POST); //$name = $post["name"]; //$pass = $post["pass"]; //$pass2 = $post["pass2"]; $name = htmlspecialchars($_POST["name"], ENT_QUOTES, "UTF-8"); $pass = htmlspecialchars($_POST["pass"], ENT_QUOTES, "UTF-8"); $pass2 = htmlspecialchars($_POST["pass2"], ENT_QUOTES, "UTF-8"); if(empty($name) === true) { print "名前が入力されていません。<br><br>"; } else { print $name; print "<br><br>"; } if(empty($pass) === true) { print "パスワードが入力されていません。<br><br>"; } if($pass != $pass2) { print "パスワードが異なります。<br><br>"; } if(empty($name) or empty($pass) or $pass != $pass2) { print "<form>"; print "<input type='button' onclick='history.back()' value='戻る'>"; print "</form>"; } else { $pass = md5($pass); print "上記スタッフを追加しますか?<br><br>"; print "<form action='staff_add_done.php' method='post'>"; print "<input type='hidden' name='name' value='".$name."'>"; print "<input type='hidden' name='pass' value='".$pass."'>"; print "<input type='button' onclick='history.back()' value='戻る'>"; print "<input type='submit' value='OK'>"; print "</form>"; } ?> </body> </html> |
このページで、入力に誤りがないかをチェックします。
bodyタグから重要な部分を抜粋し説明します。まず先頭のコメントアウトは後ほど説明します。
初めの$name変数にhtmlspecialcahrs()を代入していますが、これは「クロスサイトスクリプティング」を防ぐ重要な対策になります。必須です。
これは、前ページのテキストフォームに、例えばhtmlで「background: black;」のcssを入力された場合、postでそのまま値を引き受けてしまうのでサイトの画面が真っ暗になってしまいます。つまり、簡単に悪意のあるscriptを埋め込まれる可能性があるのです。
これを防ぐのが「htmlspecialchars()」になります。
引数に $_POST[“name”] がありますが、これがpost送信された値になります。グローバル変数と呼ばれ、phpで元々用意されている特別な変数になります。$_POSTの引数に、submitで送信されたnameを入れることで、その値をpostで受け取る事が出来ます。
つまり、post受信した値は、htmlspecialcharsで悪意ある値を実行されないようにしているのです。必ず入力フォームから送られてくる情報はには、htmlspecialchars+postで受け取るのが必須です。
それでは、postで受け取った値をそれぞれ任意の変数に格納し、入力に誤りがないかチェックしていきます。
empty($name) === true は、引数の値が空であれば真、という記述です。emptyは引数が空の状態かどうかを確認できます。if文にこれを入れることで、値の有り無しで分岐できますね。
if(empty($name) or empty($pass) or $pass != $pass2) { で、入力に一つでも誤りがあれば真の値となり、formタグのbutttonで前のページに戻るよう誘導します。
ちなみにphp内にhtmlや文字列を記述する場合は、
print “htmlの記述、文字列”;
のように、print ” “; で囲ってやる必要があります。
入力に誤りがなければ、次ページに値を送る前に、 md5();で、引数の値を32桁の乱数に変換させてやります。つまりパスワードの盗聴防止ですね。他の値は万が一盗聴されても比較的問題ありませんが、パスワードは重要な情報になりますので、こういった処置が必須です。
後はformタグで次ページstaff_add_done.phpに値をpost送信しますが、新しいinput typeがあります。
<input type=’hidden’ name=’name’ value='”.$name.”‘>、「 hidden」です。
valueに値を乗せる事が出来ますが、画面には表示されないといった特徴があります。
つまり、画面に表示させずに、submitで次ページへ値を送る事ができるので、橋渡し的な感覚で利用出来るのが便利です。
ちなみに、valueの値にphpの変数を入れています。つまり、htmlの中にphpを埋め込んでいます。この場合は、ドット(.)で繋ぐ必要があります。したがってvalue='”.$name.”‘のようなややこしい記述になります。
長くなりましたが最後に、クロスサイトスクリプティングの対策は今後何回も記述する事になりますので、「関数」を作ってやると便利です。
post受信した値を全てクロスサイトスクリプティングの対策してやるには、下記のような関数を作ってやると便利です。
ではhtdocs配下にcommonフォルダを作成し、その配下にcommon.phpという名前のファイルを作成します。
1 2 3 4 5 6 7 8 9 |
<?php function sanitize($before) { foreach($before as $key => $value) { $after[$key] = htmlspecialchars($value, ENT_QUOTES,"UTF-8"); } return $after; } ?> |
function に続くsanitizeが関数名になります。引数$_beforeには$_POSTが入る設定にします。
foreachは、引数の条件が無くなるまで{}の記述を実行します。
引数の条件は、$before($_POST)の$key(配列の添え字)にある値を$valueで取り出しています。
つまり、今回の場合であれば$_POSTはname,pass,pass2,の3つを受け取っているので、配列には3つ、$_POST[x]、$_POST[y]、$_POST[z]、があり、name,pass,pass2の値が入っています。したがって、添え字xの値を$valueで取り出し、{}の記述を実行。添え字yの値を$valueで取り出し、{}の記述を実行。添え字zの値を$valueで取り出し、{}の記述を実行。ここで条件が無くなるのでreturn $after;を実行、となります。
{}の記述は、htmlspecialchars($value, ENT_QUOTES,”UTF-8″);、つまり、$valueの値が$_POSTの値となるので、ここでクロスサイトスクリプティングの対策しています。それを$after[$key]、つまり$afterの同じ添え字に対策した値を格納していきます。今回であればこれが3回続く訳です。
最後のreturn $after;には、[x,y,z]の対策された値が入っていて、それを返している感じです。
では、コメントアウトしていた部分を修正してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<body> <?php require_once("../common/common.php"); $post = sanitize($_POST); $name = $post["name"]; $pass = $post["pass"]; $pass2 = $post["pass2"]; //$name = htmlspecialchars($_POST["name"], ENT_QUOTES, "UTF-8"); //$pass = htmlspecialchars($_POST["pass"], ENT_QUOTES, "UTF-8"); //$pass2 = htmlspecialchars($_POST["pass2"], ENT_QUOTES, "UTF-8"); |
require_onceは、関数をインクルードする記述で、使用する為にはこの記述が必要です。引数に関数のあるファイルの場所を指定します。../で一つ上の階層という意味です。
sanitizeは先ほど作成した関数名ですので、ここで関数sanitizeが実効されます。引数に$_POSTを指定しているので、ここが$beforeに渡され、$afterになって帰ってくるのです。それを$postに格納。
したがって、$postには、対策された値が全て格納されている事になります。非常に、便利です。
それでは間違えた入力をしてちゃんとエラーではじかれるかを一通り試してみて下さい。問題ない入力なら、下のような画面になるはずです。
スタッフ追加をデータベースに登録
staff_add_done.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
<?php //session_start(); //session_regenerate_id(true); //if(isset($_SESSION["login"]) === false) { // print "ログインしていません。<br><br>"; // print "<a href='staff_login.html'>ログイン画面へ</a>"; // exit(); //} else { // print $_SESSION["name"]."さんログイン中"; // print "<br><br>"; //} ?> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>スタッフ追加実効</title> <link rel="stylesheet" href="../style.css"> </head> <body> <?php try{ require_once("../common/common.php"); $post = sanitize($_POST); $name = $post["name"]; $pass = $post["pass"]; $dsn = "mysql:host=localhost;dbname=shop;charset=utf8"; $user = "root"; $password = ""; $dbh = new PDO($dsn, $user, $password); $dbh -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $sql = "INSERT INTO mst_staff(name, password) VALUES(?,?)"; $stmt = $dbh -> prepare($sql); $data[] = $name; $data[] = $pass; $stmt -> execute($data); $dbh = null; } catch(Exception $e) { print "只今障害が発生しております。<br><br>"; print "<a href='../staff_login/staff_login.html'>ログイン画面へ</a>"; } ?> スタッフを追加しました。<br><br> <a href="staff_list.php">スタッフ一覧へ</a> </body> </html> |
最後に、スタッフ情報をデータベースに保存するページです。主要な部分を抜粋して説明します。
データベース接続の手順は、
①データベース接続
②SQL実行
③データベース切断
の流れですので、覚えておきましょう。
bodyタグのphp記述の始めに try { とありますが、これはエラートラップとよばれるもので、データベース接続、SQL実行、データベース切断、の一連の流れを囲んでいます。これは、もしその一連の動作に異常が発生した際に、catch(Exception $e) { にある「異常が発生している」という旨をユーザーに伝えるもので、トップページ等へ誘導するなどの例外処理を行うものです。
データベース接続には必ずエラートラップを使います。
$dsn = “mysql:host=localhost;dbname=shop;charset=utf8”;から始まる5行は、データベース接続の記述です。データベース接続には必須です。
接続するdbのホスト名、dbの名前、登録しているユーザー名とパスワードを記述し、接続しています。ここの内容は実際にオンラインサーバーなどで公開する際は、そこのサーバーにあるdbの設定に合わせる必要がありますが、とりあえずローカルで作成する場合はこれでOKだと思います。(macの場合はパスワードもrootが必要かもです。)
データベース接続の後は、SQLでデータベースから任意の情報を取り出したり、書き込んだりします。今回は追加なので、下記の追加の記述になります。
$sql = “INSERT INTO mst_staff(name, password) VALUES(?,?)”;
これは、mst_staffのテーブルにあるnameとpasswordカラムにVALUESの引数に渡した値を登録させる文です。VALUESが?,?になっているのは、左がname,右がpasswordに該当します。
$stmt = $dbh -> prepare($sql);は、SQLを実行する準備のようなものです。
$data[] は、VALUESの?に左から順番に渡されるので、今回の場合であれば、$data[] = $name;
$data[] = $pass;の順で記述しなければなりません。
$stmt -> execute($data);は、SQLの実行になります。今回であれば、テーブルにnameとpassの追加書き込みとなりますね。
このページではもうデータベースをSQLで操作する事はないので、ここでデータベースを切断します。
$dbh = null;
後はPHPを閉じて、htmlで追加出来た表示と、スタッフ一覧のページへのリンクを貼っています。
それでは、先ほどの画面からOKを押して、DBに登録してみて下さい。下記の表示がでればOKです。
それでは最後に、実際にデータベースを確認してみましょう。
codeはAIが設定されているので、1から順番に自動で番号が割り振られていきます。nameに名前と、passwordに乱数が登録されていればOKです。上のcodeは13になってますが気にしないでください。
ECサイトの自作で悩んでいたらこちらにたどり着きました。PHPとmysqlの勉強を始めてもうすぐ一年なのですが、実際に顧客情報を扱うとなるとやはり不安で、質問させてもらいたいのですが。こちらで紹介されている、ECサイトのコードはセキュリティ的に大丈夫なものなのでしょうか?お忙しいとは思いますが、ご教示いただけると励みになります。
サンペイさま
はじめまして、管理人です。
質問についてですが、xss対策やパスワードの暗号化など、最低限必要なセキュリティしか行っていませんので、正直に申し上げますと対策が万全だとは言えません。
したがって他人の個人情報などを扱う場合等にはもう少しセキュリティの強化が必要かと思われます。
ただ、ブログなど個人で楽しむ分には問題ないかとも思います(それでもセキュリティは甘めなので何か起これば自己責任ですが(-_-;))
おはようございます。お早いお返事ありがとうございます。他人の個人情報を扱うとなれば、どのようなセキュリティ対策が必要なんでしょうか。調べてもインジェクションやXSS、CSRFくらいしか出てこなくて他にどのような対策をすればよいのか状態で。差支えなければ、簡単で良いので教えていただけますでしょうか。
サンペイさま
お返事遅れてしまい申し訳ありません。
セキュリティについては言い出せばキリがありませんので、どこまでやるのかの線引きは公開するサービスによると思うのですが、
とにかくECサイトのように他人の情報(メールアドレスや電話番号、住所など)をで扱うのであれば、先日も申し上げた通りこのサイトの対策では不十分だとしか言えません。
つまり、なにか起こった際に責任は負いかねますので不十分としか言えないのです(-_-;)
セキュリティのより詳しい内容については私も勉強不足であり、とても教えるような立場ではないので、申し訳ありませんが徳丸先生などのセキュリティに詳しい専門書で調べて下さい。
度々のお返事、ありがとうございます。セキュリティについて、徳丸浩さんという人がいらっしゃるのですね。記事とは関係ないことでお時間を取らせてしまいまい申し訳ありませんでした(._.)徳丸先生の参考書をいくつか買って、勉強します。良い情報を教えていただきありがとうございます(^^)とはいえ、こちらのECサイト構築もとても勉強になった為、こちらも参考にさせていただきます。色々とアドバイスいただきまして、ありがとうございました。記事と関係のあることで、わからない点が出てきたら、質問させていただくと思いますがその時は、またよろしくお願いいたします(._.)
コメント失礼します。ドキュメントルートについてお聞きしたいのですがphpファイルはhtdocs配下のためドキュメントルートは127.0.0.1/htdocs/staff/staff_add.phpになるのではないでしょうか。ご教授よろしくお願い致します。
HARUさま
はじめまして管理人です。xamppのApacheの場合デフォルトの設定ではhtdocs配下がドキュメントルートになります。
したがって、「htdocs」ディレクトリを指定する必要はありません。
ローカル環境でWEBサーバーを立てた場合、同一LANからサーバー宛てのIPをブラウザで入力するだけでページが表示されるのはそういう事です。
デフォルトではサーバーIP(ポート70)でhtdocs配下のindexファイルにアクセスしに行きます。