今回はタイトル通り、PHPとMySQLを使ったユーザー登録機能の作り方についてまとめてみました。
アウトプットする良い練習台だったものの、今回はかなりの長文です。
いちおう目次から興味のある部分まで読み飛ばせるんで、最初からでも途中からでも読めるようにはしときました。
プログラミング初学者なので、もしコード間違いなどあればご指摘いただけるとありがたいです。
【下準備】データベースを準備する
ウェブカツでPDOを使ったデータベース(以下、DBとする)への保存方法を勉強したので、今回はPDOを使用してMySQLのDBにデータ保存をしていきます。
まずはサンプルデータを保存するため、phpMyAdminを利用してDBを作成しましょう。
MAMPを起動すると下記のようなスタートアップ画面が開くので、「Tools」タブから「phpMyAdmin」を選択します。
するとphpMyAdminが開くんで、ここで新たにDBを用意します。
phpMyAdminを初めて利用する場合は画面左横にある「新規作成」を押すと、DB作成画面がこんな感じで表示されます。
DBを既に作成済みの場合には、画像のようにDB一覧が表示されます。
今回のように新たに作成する場合は、「データベースを作成する」の直下にあるウィンドウに新たに作成したいDB の名称を入力します。
今回私は「php_sample1」というDBを新規作成します。
その右横では文字コードを選択できるので、今回はウェブカツで教わった通りに「utf8_general_ci」を選択しておきます。
日本語でWEBサイトを作るときは上のやつを選択しておくのが無難なようです。
最後に「作成」ボタンを押すと今度はテーブル作成画面に切り替わってくれます。
実際にテーブルの作成画面で入力するとこんな感じ。
最後に「保存する」ボタンを押したら、作成したテーブルが新たに保存されます。
画像上では「テーブル名」をうっかり入力し忘れてますけど、テーブル名を入力するのをお忘れなく 笑。
今回はテーブル名「users」と入力し、カラム数についてはテーブルで使用するデータの種類分だけカラムを追加します。
具体例として、実際に作成したusersテーブルのデータ種別を下記に記してみました。
データ名称 | データ型 | 文字数制限 | nullを許容するか | AI(Auto Increment) |
---|---|---|---|---|
id | INT(数値) | なし | しない | あり |
VARCHAR(文字列) | 100 | しない | なし | |
pass | VARCHAR(文字列) | 100 | しない | なし |
login_time | DATETIME(日時) | なし | しない | なし |
今回はemail・passカラムの文字数を100にしましたが、上限は255まで設定できるようになってます。
またidカラムにのみAIをつけてますが、これは自動で番号を割り振ってほしい場合にチェックをつけるものです。
usersテーブルのようにテーブル構造が画面表示されたら、これでPHPでコードを書く準備は完了です。
【下準備】HTML・CSSを先に書いておく
今回はPHPでユーザー登録機能を作成するというのが本来の目標ですが、まずは先にHTML・CSSからコードを書いておきます。
PHP以外のコードについて、まずHTML部分から。
<!DOCTYPE html>
<html lang='ja'>
<head>
<meta charset='utf-8'>
<title>PHP Sample01</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Jacquard+24&family=M+PLUS+Rounded+1c&family=Madimi+One&family=Noto+Serif+JP&display=swap" rel="stylesheet">
</head>
<body>
<h1>ユーザー登録</h1>
<form class='user_form' method='post'>
<input type='text' name='email' placeholder='メールアドレス'>
<input type='password' name='pass' placeholder='パスワード'>
<input type='password' name='pass_re' placeholder='パスワード(確認用)'>
<input type='submit' value='submit'>
</form>
<a href='sample1_mypage.php'>マイページへ</a>
</body>
</html>
今回はPHPで入力フォーム上部にエラーメッセージを表示させる予定ですが、現時点ではHTML部分のみ作っておきます。
それからCSSはstyleタグで括って、headタグ内にまとめて書いておきました。
<style>
body{
margin: 0 auto;
padding: 0;
width: 50%;
font-family: "M PLUS Rounded 1c", sans-serif;
font-weight: 400;
font-style: normal;
}
h1{
text-align: center;
margin-right: 30px;
}
.user_form{
width: 90%;
}
input[type='text']{
text-align: center;
margin-bottom: 20px;
padding: 10px 10px;
width: 100%;
border: 1px solid #545454
}
input[type='password']{
text-align: center;
margin-bottom: 20px;
padding: 10px 10px;
width: 100%;
border: 1px solid #545454
}
input[type='submit']{
padding: 10px 20px;
border: 1px solid #545454
letter-spacing: 0.1em
width: 50%;
margin-bottom: 20px;
font-size: 16px;
margin-left: 25%;
margin-right: 25%;
}
.err_msg{
font-size: 14px;
color: red;
}
</style>
PHPに特に関連するCSSは、.err_msgの部分くらいですかね。
入力内容に不備がある場合には、エラーメッセージが赤色でフォーム上部に表示される仕様になってます。
ここまで書けたら、いよいよPHPを使ってユーザー登録機能を実装していきます。
【PHP実践】エラーメッセージ表示部分を作る
ユーザー登録機能を作るにあたり、入力フォームの内容が正しいかどうかをDB保存前に確認する必要があります。
ということでまずはバリデーション部分から順に書いていきましょう。
しかしここで注意点がひとつ。
PHPではコードを一気に書いてしまうと、エラーが起こった際の原因特定にかなーり時間がかかってしまいます。
実際私が1時間くらいパソコンとにらめっこするハメになりました 笑。
PHPを書く際の個人的おすすめは、画面表示が問題なくできるか確認しながら少しずつコードを追加するのが一番安心。
今回のユーザー登録機能の実装でもけっこうif文使うんで、インデントミスやスペルミスなどを少しでも減らす意味合いでも大事です。
1.PHPコードを書く準備をする
PHPを書く際には専用のタグの書き方があるんで、書き忘れないうちに外側から固めていきます。
<?php
?>
何度も言いますが、特に複数行にわたる場合はインデント構造に要注意です。
画面上では全くエラーが出なくても、headerメソッドでページ遷移できない、DB保存できないなどの不具合が出てきます。
以下では見やすさ重視でPHPタグの中身だけ書くことにしましょう。
2.エラーメッセージ表示部分を追加する
PHPではコードを書く側専用のエラーメッセージも用意されてますが、デフォルトだとまず表示されません。
まずはお決まりのエラーメッセージ表示部分から追加しておきましょう。
error_reporting(E_ALL); //全てのエラーを対象とする
ini_set('display_errors','On'); //画面にエラーを表示させる
内容はコメントにある通りなんで、ここは説明を省略します。
3.一番外枠のif文を書く
入力された内容が正しいかどうかをバリデーションでチェックするんですが、値が会って初めてバリデーションチェックが行えます。
ということで一番外枠のif文の条件は、「入力フォームに何らかの値が存在すること」です。
if(!empty($_POST)){
}
ちなみに上のコードで出てくる「$_POST」については下記のとおり。
$_POST(ポスト変数)は、HTMLのformタグでactionは指定せず、「method=’post’」のみ選択して使います。
formで受け取った値が連想配列形式となるため、キーをinputのname属性で指定し、今回はif文の条件分岐やエラーメッセージ部分で使用していきます。
$_POST自体は「自動グローバル変数」なので、PHPコードのどこでも使える便利なやつ、くらいに覚えておくといいでしょう。
PHPに限らずプログラミング言語では、if文の先頭に「!」を使うことで否定の条件文を作れます。
今回だと「if(!empty($_POST))」で、「$_POSTが空じゃない=値が存在する時」という条件文設定になります。
これが一番外枠のif文で、この中にバリデーションごとの条件を設定したif文を入れ子構造で書いていくというわけですね。
4.エラーメッセージを定数で定義する
バリデーションのエラーメッセージ自体は、別にechoの直後に文字列で直接書いてもいいわけです。
とはいえユーザー登録時のバリデーションは複数あるんで、今回はエラーメッセージを定数としてif文の書き出し近くで先に定義しておきます。
define('MSG01','入力必須です'); // 各フォーム共通
define('MSG02','Emailの形式で入力してください'); // email専用
define('MSG03','パスワード(再入力)が合っていません'); // pass専用
define('MSG04','半角英数字のみご利用いただけます'); // pass専用
define('MSG05','8文字以上で入力してください'); // pass専用
PHPで定数を定義する時にはdefineもしくはconstのどちらかを使用しますが、今回はdefineで定数名と実際の値をセットで定義しました。
ただしdefineで定義した定数は以降で変更できないものになるので、その点は注意が必要です。
defineでは第一引数に定数名、第二引数に定数の値をセットしときます。
5.エラーメッセージを入れる変数を用意する
エラーメッセージの文言を定数として用意しても、HTML内でエラーメッセージを表示させるための変数がなければ使えません。
そこで次に、空の配列である「$err_msg」を用意しときます。
$err_msg = array();
いったん中身なしで用意しておき、必要になれば定数を使用してエラーメッセージの中身を入れ込んでいくイメージです。
HTML部分に表示するためのPHPタグは後ほど追加するので、ここでは割愛して。
ここまででエラーメッセージを表示するための土台が完成しました。
6.入力フォームが空の場合のバリデーションをかける
バリデーションのif文を追加する一番最初のやつは、入力フォームが空の場合の条件から追加していきましょう。
このバリデーションはemail/pass/pass_reフォームの全てに使いまわせるんで、各種コードを一気に追加しちゃいます。
if(empty($_POST['email'])){
$err_msg['email'] = MSG01;
}
if(empty($_POST['pass'])){
$err_msg['pass'] = MSG01;
}
if(empty($_POST['pass_re'])){
$err_msg['pass_re'] = MSG01;
}
$_POSTのキーをinputのname属性で指定すれば、各種入力フォームに対応可能です。
また$err_msgでも同じくキーをname属性で指定してるんで、どの入力フォームに対応したエラーメッセージであるかが一目でわかります。
今回の場合だと、該当するフォームの中身が空の場合にエラーメッセージが表示される、という内容です。
7.外から2番目のif文を追加する
「1つ目のバリデーションを通過してエラーメッセージの値が何もないこと」、これが2番目のif文の条件。
if(empty($err_msg)){
}
ちなみに、ここまでの全体像はこんな感じ。
この記事を読む人の中には、「if文を入れ子にしなくても、elseifで分岐すればいいんじゃないの?」と思われた人もいるかもしれません。
けれどif文自体は、いずれかの条件に合致した場合には以下の分岐処理は行われない、というのが原則です。
つまり本来なら複数のバリデーションに引っかかる場合でも、一番上で該当したエラーメッセージだけ表示され、それ以下で該当したはずのバリデーションエラーは表示されずに素通り、なんてことも。
if文の入れ子構造でややこしくなりがちですが、自分で実践する場合でも外側から少しずつ追加、画面表示されるか確認のルーティンで乗り越えていきましょう。
8.入力情報を入れる変数を用意する
今度は入力された情報を入れられる変数を各種用意します。
$email = $_POST['email'];
$pass = $_POST['pass'];
$pass_re = $_POST['pass_re'];
$_POST使用時にキーを指定することで、配列の中身を指定して渡せるのが便利ですよねー。
PHPではDB保存時にも使える自動グローバル変数なるものが後にも登場します。
「こういったものがあるんだなー」くらいに覚えておけば、忘れてもネット検索できるんで特に問題ないですね。
9.メールアドレス専用のバリデーションを追加する
メールアドレスでは正しい形式で入力されてるかをチェックするため、正規表現を使用してバリデーションチェックを行います。
ここで登場する正規表現、まずはコードで確認してみましょう。
//3.emailの形式でない場合
if(!preg_match("/^([a-zA-Z0-9])+([a-zA-Z0-9\._-])*@([a-zA-Z0-9_-])+([a-zA-Z0-9\._-]+)+$/", $email)){
$err_msg['email'] = MSG02;
}
ウェブカツの部活でも言われたことですが、正規表現については丸暗記しなくても大丈夫かと。
ネット検索してコピペすれば済む話ですし、正規表現覚える労力はPHPのコードの書き方を覚えるほうに回した方が無難ですね。
PHPで正規表現チェックをしたい場合は、preg_matchメソッドを使用します。
また今回は正規表現に該当しない形式のみエラーで弾きたいんで、if文先頭に「!」をつけて否定の条件文にしました。
10.パスワード不一致のバリデーションを追加する
上と同じく完結型のバリデーションチェックとして、パスワード・パスワード(確認用)が一致しない場合のバリデーションチェックも追加します。
//4.パスワードとパスワード再入力が合っていない場合
if($pass !== $pass_re){
$err_msg['pass'] = MSG03;
}
この場合だとパスワード、ならびにパスワード(確認用)のどちらにもエラーメッセージを表示させられますよね。
とはいえエラーメッセージを重複させなくてもユーザーに意図が伝わるだろうということで、今回はパスワード入力フォーム部分のみに絞って表示させてます。
11.外から3番目のif文を追加する
ここまでで$err_msgに何の値も入っていなければ、また新たにif文を追加します。
if(empty($err_msg)){
}
条件文もわかりやすく、「エラーメッセージが空であること」としています。
インデント構造がそろそろわかりにくくなってきたので、いったん全体像を確認してみます。
構造的にはさっき追加したメールアドレスの正規表現チェック、パスワード不一致のバリデーションと同階層に3番目のif文を追加した状態です。
この中に残りのバリデーションチェックのif文を入れ込んでいきます。
12.パスワード専用のバリデーションを追加する
以下でパスワード専用のバリデーションを追加しますが、ここではif文の条件分岐を使用して2つのバリデーションを一気にかけてます。
これもパスワード専用ですね。
//5.パスワードとパスワード再入力が半角英数字でない場合
if(!preg_match("/^[a-zA-Z0-9]+$/", $pass)){
$err_msg['pass'] = MSG04;
}elseif(mb_strlen($pass) < 8){
//6.パスワードとパスワード再入力が8文字以上でない場合
$err_msg['pass'] = MSG05;
}
具体的にはパスワード・パスワード(確認用)が半角英数字のみで入力されているか、また8文字以上になっているかをチェックしました。
どちらにも引っかかる場合はかなりレアだという解釈で、if文の条件分岐で残りのバリデーションチェックをした次第です。
これでようやくバリデーションチェックが完了したので、次からはいよいよDB保存のコードを書いていきます。
【PHP実践】DB保存部分を作る
ここまででif文の外枠が3つある状態でしたが、ここでさらに外から4つ目のif文を追加します。
if(empty($err_msg)){
}
条件式自体はとっても簡単、「$err_msgに値が何もないこと」を指定しました。
こうすることで全てのバリデーションチェックをクリアしたことになります。
入力内容に不備がないと確認できたら、今追加したif文の中にDB保存のためのコードを書くわけですが。
このDB保存コードについては定型文的なものがあるんで、以下のように一気に追加しちゃいます。
if(empty($err_msg)){
// DBへの接続準備
$dsn = 'mysql:dbname=php_sample1;host=localhost;charset=utf8';
$user = 'root';
$password = 'root';
$options = array(
// SQL実行失敗時に例外をスロー
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
// デフォルトフェッチモードを連想配列形式に設定
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
// バッファードクエリを使う(一度に結果セットをすべて取得し、サーバー負荷を軽減)
// SELECTで得た結果に対してもrowCountメソッドを使えるようにする
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
);
// PDOオブジェクト生成(DBへ接続)
$dbh = new PDO($dsn, $user, $password, $options);
//SQL文(クエリー作成)
$stmt = $dbh->prepare('INSERT INTO users (email, pass, login_time) VALUES (:email, :pass, :login_time)');
//プレースホルダに値をセットし、SQL文を実行
$stmt->execute(array(':email' => $email, ':pass' => $pass, ':login_time' => date('Y-m-d H:i:s')));
//マイページへ
header('Location: sample1_mypage.php');
}
これ自体も丸暗記する必要はないんですが、今回使用するPDOについても簡単に調べてみました。
PDOとは「PHP Date Objects」の略称で、DBの種類問わず共通の書き方を使えるようにしたもの。
PHPを使用してDBにアクセスする場合にはPDOを使うことが多いらしく、今回はSQL実行時の安全対策のある「PDO::prepare」を使用しました。
1.DBに接続するための変数を用意する
DB保存時にSQLに渡す変数が必要になるんで、まずはDB接続のための変数を用意します。
$dsn = 'mysql:dbname=php_sample1;host=localhost;charset=utf8';
「$dsn」では使用するDBの名称、接続先のホスト名、指定する文字コードを書きます。
今回はphpMyAdminで作成済みのテーブルにデータ保存するため、「mysql:dbname=php_sample1」としてます。
またphpMyAdminでは接続先ホストがデフォルトでlocalhost、文字コードは今回utf-8を指定したので同じくutf8指定です。
$user = 'root';
$password = 'root';
次に「$user」と「$password」についてですが、デフォルト値ではどちらもroot指定のためそのまま使いました。
$options = array(
// SQL実行失敗時に例外をスロー
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
// デフォルトフェッチモードを連想配列形式に設定
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
// バッファードクエリを使う(一度に結果セットをすべて取得し、サーバー負荷を軽減)
// SELECTで得た結果に対してもrowCountメソッドを使えるようにする
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
);
最後に渡した「$options」は配列になってますが、ここも定型文的なやつです。
概要はコメントに記載してますが、ウェブカツでも丸暗記する必要はないと言われたので軽く流しておきます。
ここまででDB保存のための各種変数を用意できました。
2.PDOオブジェクトを作る
先ほど作成済みの変数を使い、今度はDB接続のためのインスタンスを生成します。
// PDOオブジェクト生成(DBへ接続)
$dbh = new PDO($dsn, $user, $password, $options);
ここで新たに登場した「$dbh」に各種変数を渡しました。
3.クエリを作成する
SQLを実行する際に使用する命令文のことを「クエリ」と呼びますが、ここでSQL実行のためのクエリを作成します。
//SQL文(クエリ作成)
$stmt = $dbh->prepare('INSERT INTO users (email, pass, login_time) VALUES (:email, :pass, :login_time)');
変数「$stmt(ステートメントの略)」に$dbhを渡し、この時にprepareメソッドを指定してクエリを書きます。
このprepareメソッドの具体的な役割については下記のとおり。
PDO::prepareは一部を変数(プレースホルダー)として書くことで、後から変数部分の変更を行えるようにするメソッド。
似た役割を持つqueryメソッドより汎用性が高いそうです。
また虫食い状態でDB保存のひな型を渡すため、PDOによるDB保存方法の中でも安全性が高いと言われてます。
prepareメソッド以下はSQLのクエリの書き方そのままです。
ただし「VALUES」以下で設定してる「:」付きの変数名が、いわゆるプレースホルダーにあたります。
executeメソッド実行時点でこの虫食いを埋めるんですが、それが次のクエリですね。
4.クエリを実行する
虫食い状態で作っておいたSQL文を使ってクエリ実行させるのが以下の一文です。
//プレースホルダに値をセットし、SQL文を実行
$stmt->execute(array(':email' => $email, ':pass' => $pass, ':login_time' => date('Y-m-d H:i:s')));
変数$stmtに対してexecuteメソッドを実行する際には、配列として先ほどのプレースホルダーと実際に入れたい値をセットにして渡してます。
:login_timeだけは変数として中身を用意せず、dateメソッドを使用して現在日時を指定のフォーマットで取得してから入れ込む仕様です。
5.リダイレクト先を指定する
ここまででDB保存も終わり、ユーザー登録機能自体は実現してるんですが。
一般的なWEBサイトだとこの後って必ず別ページに移動しますよね?
そのページ遷移を実現してくれるのが、最後の一文にあたるheaderメソッドです。
//マイページへ
header('Location: sample1_mypage.php');
使い方は簡単で、「Location:」の後に続けてリダイレクト先に指定したいファイル名を書くだけです。
今回は同ディレクトリ内にリダイレクト先があるんで、ファイル名だけ指定しました。
これでようやくユーザー登録機能自体は完成なんで、残すところあと一歩です。
【PHP実践】エラーメッセージ表示部分を作る
最後の仕上げとして、先ほど作ったユーザー登録時のエラーメッセージ表示部分をPHPタグでHTML内に追加します。
各種入力フォーム上部にエラーメッセージを表示させる仕様なんで、今回はform全体としてこんな感じになりました。
<form method='post'>
<span class='err_msg'><?php if(!empty($err_msg['email'])) echo $err_msg['email']; ?></span>
<input type='text' name='email' placeholder='メールアドレス'>
<span class='err_msg'><?php if(!empty($err_msg['pass'])) echo $err_msg['pass']; ?></span>
<input type='password' name='pass' placeholder='パスワード'>
<span class='err_msg'><?php if(!empty($err_msg['pass_re'])) echo $err_msg['pass_re']; ?></span>
<input type='password' name='pass_re' placeholder='パスワード(確認用)'>
<input type='submit' value='submit'>
</form>
エラーメッセージ表示部分をspanタグで囲み、中にあるPHPタグでコードを追加。
if文の条件が「$err_msgが存在すれば、$err_msgの中身を表示する」というものです。
この$err_msgにはエラーメッセージが配列として補完され、キーを指定することで対応する入力フォーム用のエラーメッセージが取り出せる、というのが基本的な仕様でした。
またspanタグのクラスにstyleタグ内でCSSが当たってるんで、中身のメッセージは赤い文字色で表示されることになります。
いちおう今回作ったPHPファイルの中身も載せておくんで、途中でこんがらがった場合の見直しにお使いください。
まとめ
ウェブカツで学んだユーザー登録機能の振り返りのためにまとめてみましたが、文字に起こすとかなーり長すぎました 笑。
最後まで読んでくれた人がいましたら、本当にお疲れ様でございました。
今回はユーザー登録機能のみまとめたんで、リダイレクト先のページについては全く触れていません。
またこれ以外にも備忘録として復習したい内容があれば、別途まとめてみるつもりです。
それでは。