PHPでログイン機能を実装してみる【3】
新規登録の処理を実装する
Usersモデルを作る
共通処理を Model.php に記述し、それ以外のを Model フォルダの中に入れて、User クラスなどを作っていく。
/lib/Model.php
<?php
namespace MyApp;
class Model {
protected $db;
public function __construct() {
try {
$this->db = new \PDO(DSN, DB_USERNAME, DB_PASSWORD);
} catch (\PDOException $e) {
echo $e->getMessage();
exit;
}
}
}
libディレクトリ内にModelディレクトリを作成し、その中にUser.phpを作成
User モデルは \MyApp\Model\User() なので、Model フォルダを作ってあげる。
/lib/Model/User.php
<?php
namespace MyApp\Model;
class User extends \MyApp\Model {
public function create($avlues) {
}
}
渡ってきたデータでレコードを挿入してあげる。
/lib/Model/User.php
class User extends \MyApp\Model {
public function create($values) {
~
$stmt = $this->db->prepare("insert into users (email, password, created, modified) values (:email, :password, now(), now())");
$res = $stmt->execute([
':email' => $values['email'],
':password' => password_hash($values['password'], PASSWORD_DEFAULT)
]);
if ($res === false) {
throw new \MyApp\Exception\DuplicateEmail();
} //追加
~
}
ユーザーモデルのインスタンスを作る
/lib/Controller/Signup.php
if ($this->hasError()) {
return;
} else {
~
//create user
try {
$userModel = new \MyApp\Model\User();
$userModel->create([
'email' => $_POST['email'],
'password' => $_POST['password']
]);
} catch (\MyApp\Exception\DuplicateEmail $e) {
$this->setErrors('email', $e->getMessage());
return;
}
//redirect to login
header('Location: ' . SITE_URL . '/login.php');
exit; //追加
~
}
Exceptionディレクトリの中にDuplicateEmail.phpを作成する
※InvalidEmailを流用
/lib/Exception/DuplicateEmail.php
<?php
namespace MyApp\Exception;
class DuplicateEmail extends \Exception {
protected $message = 'Duplicate Email!';
}
Signup画面から適当なメールアドレスとパスワードを入力して確認。
重複した場合はDuplicate Email!が出ることも確認。
データベースにログインし、usersテーブルを確認。
mysql> use login_php
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> select * from users;
+----+------------------------+--------------------------------------------------------------+---------------------+---------------------+
| id | email | password | created | modified |
+----+------------------------+--------------------------------------------------------------+---------------------+---------------------+
| 1 | xxxxx@xxx.com | $2y$10$SBZ7BhVWF.FajIxUmWOwEOLxOZY0zPPSji2LzLTYqE6j.O0OfGLjW | 2019-05-13 05:29:26 | 2019-05-13 05:29:26 |
+----+------------------------+--------------------------------------------------------------+---------------------+---------------------+
1 row in set (0.00 sec)
ログイン処理を実装する
Signup.phpをコピーしてLogin.phpを作成
lib/Controller/Login.php
<?php
namespace MyApp\Controller;
class Login extends \MyApp\Controller {
public function run() {
if ($this->isLoggedIn()) {
header('Location: ' . SITE_URL);
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$this->postProcess();
}
}
protected function postProcess() {
try {
$this->_validate();
} catch (\MyApp\Exception\EmptyPost $e) {
$this->setErrors('login', $e->getMessage());
}
$this->setValues('email', $_POST['email']);
if ($this->hasError()) {
return;
} else {
try {
$userModel = new \MyApp\Model\User();
$userModel->login([
'email' => $_POST['email'],
'password' => $_POST['password']
]);
} catch (\MyApp\Exception\UnmatchEmailOrPassword $e) {
$this->setErrors('login', $e->getMessage());
return;
}
// login処理
// redirect to home
header('Location: ' . SITE_URL);
exit;
}
}
private function _validate() {
if (!isset($_POST['token']) || $_POST['token'] !== $_SESSION['token']) {
echo "Invalid Token!";
exit;
}
if (!isset($_POST['email']) || !isset($_POST['password'])) {
echo "Invalid Form!";
exit;
}
if ($_POST['email'] === '' || $_POST['password'] === '') {
throw new \MyApp\Exception\EmptyPost();
}
}
}
例外のクラスを作成
lib/Exception/EmptyPost
<?php
namespace MyApp\Exception;
class EmptyPost extends \Exception {
protected $message = 'Please enter email/password!';
}
lib/Exception/UnmatchEmailOrPassword
<?php
namespace MyApp\Exception;
class UnmatchEmailOrPassword extends \Exception {
protected $message = 'Email/Password do not match!';
}
処理をViewに反映させる
tokenを仕込む必要があるので、signup.phpを参照して記述
public_html/login.php
~
<div id="container">
<form action="" method="post" id="login"> <!-- 追記 -->
<p>
<input type="text" name="email" placeholder="email" value="<?= isset($app->getValues()->email) ? h($app->getValues()->email) : ''; ?>"> <!-- 追記 -->
</p>
<p>
<input type="password" name="password" placeholder="password">
</p>
<p class="err"><?= h($app->getErrors('login')); ?></p>
<div class="btn" onclick="document.getElementById('login').submit();">Log In</div> <!-- 追加 -->
<p class="fs12"><a href="/signup.php">Sign Up</a></p>
<input type="hidden" name="token" value="<?= h($_SESSION['token']); ?>"> <!-- 追加 -->
</form>
</div>
~
ログイン時のViewを作ったのでログイン処理を行っていきます。
同時にセッションハイジャック対策も施します。
/lib/Controller/Login.php
~
else {
try {
$userModel = new \MyApp\Model\User();
$user = $userModel->login([ //変更
'email' => $_POST['email'],
'password' => $_POST['password']
]);
} catch (\MyApp\Exception\UnmatchEmailOrPassword $e) {
$this->setErrors('login', $e->getMessage());
return;
}
// login処理
session_regenerate_id(true); //セッションハイジャック対策
$_SESSION['me'] = $user; //追記
~
ログインメソッドを実装して動作確認をしていく
lib/Model/User.php
class User extends \MyApp\Model {
~
public function login($values) {
$stmt = $this->db->prepare("select * from users where email = :email");
$stmt->execute([
':email' => $values['email']
]);
$stmt->setFetchMode(\PDO::FETCH_CLASS, 'stdClass');
$user = $stmt->fetch();
if (empty($user)) {
throw new \MyApp\Exception\UnmatchEmailOrPassword();
}
if (!password_verify($values['password'], $user->password)) {
throw new \MyApp\Exception\UnmatchEmailOrPassword();
}
return $user;
}
~
/public_html/index.php
<?php
// ユーザーの一覧
require_once(__DIR__ . '/../config/config.php');
var_dump($_SESSION['me']);
// $app = new MyApp\Controller\Index();
//
// $app->run();
動作を確認
下記のように新規登録した情報を入力した時に、正しい情報が取れていればOKです。
ユーザー一覧の表示(Index)を作っていく
※login.phpの一部コピー、ダミーで一覧作成
/public.html/index.php
<?php
// ユーザーの一覧
require_once(__DIR__ . '/../config/config.php');
// var_dump($_SESSION['me']);
// $app = new MyApp\Controller\Index();
//
// $app->run();
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Home</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="container">
<form action="logout.php" method="post" id="logout">
ogtabnvfi@gmail.com <input type="submit" value="Log Out">
<input type="hidden" name="token" value="<?= h($_SESSION['token']); ?>">
</form>
<h1>Users <span class="fs12">(3)</span></h1>
<ul>
<li>fadslfj@gmail.com</li>
<li>ghsajav@gmail.com</li>
<li>ljarhg@gmail.com</li>
</ul>
</div>
</body>
</html>
スタイルを付けていく
/public_html/styles.css
以下追加
~
form#logout {
text-align: right;
font-size: 12px;
margin-bottom: 15px;
}
.fs12 {
font-size: 12px;
font-weight: normal;
}
h1 {
font-size: 16px;
border-bottom: 1px solid #ccc;
padding-bottom: 5px;
}
ul > li {
padding: 3px 0;
~
ログアウト処理を実装していく
ログアウト処理をControllerを作ってもよいが、それほど複雑な処理ではないのでlogout.phpを作成して直接記述
/public_html/logout.php
<?php
// ログアウト
require_once(__DIR__ . '/../config/config.php');
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!isset($_POST['token']) || $_POST['token'] !== $_SESSION['token']) {
echo "Invalid Token!";
exit;
}
$_SESION = [];
if (isset($_COOKIE[session_name()])) {
setcookie(session_name(), '', time() - 86400, '/');
}
session_destroy();
}
header('Location: ' . SITE_URL);
以下のコメントアウトを外して有効にする。
/public_html/index.php
~
$app = new MyApp\Controller\Index();
$app->run();
~
ユーザー一覧画面(Index)のViewを完成させていく
ログインしているユーザー情報と、登録されているユーザー情報を取得するよう記述していく。
ログインしているユーザー情報はよく使うと想定されるので、コントローラーに me() というメソッドを作り、$app->me() で取得できるようにする。
また、ユーザーの一覧は $app->getValues()->users で取得できるようにする。
/public.html/index.php
<body>
<div id="container">
<form action="logout.php" method="post" id="logout">
<?= h($app->me()->email); ?> <input type="submit" value="Log Out">
<input type="hidden" name="token" value="<?= h($_SESSION['token']); ?>">
</form>
<h1>Users <span class="fs12">(<?= count($app->getValues()->users);
?>)</span></h1>
<ul><?php foreach($app->getValues()->users as $user)? : >
<li><?= h($user->email); ?></li>
<?php endforeach; ?>
</ul>
</div>
</body>
me()メソッドの実装
/lib/Controller.php
~
public function me() {
return $this->isLoggedIn() ? $_SESSION['me'] : null;
}
~
ユーザー情報の取得
/lib/Controller/Index.php
~
// get users info
$userModel = new \MyApp\Model\User();
$this->setValues('users', $userModel->findAll());
}
~
userモデルに、findall()メソッドを実装する
~
public function findAll() {
$stmt = $this->db->query("select * from users order by id");
$stmt->setFetchMode(\PDO::FETCH_CLASS, 'stdClass');
return $stmt->fetchAll();
}
~
動作確認
ユーザー一覧が更新されるよう反映されているはずです。
以上です。
機能追加に備えてバージョン管理の準備
前提条件
gitはインストールしてあること。
GitHubのアカウントは作成済みであること。
/login
$ git init
※必要であれば.gitignoreを作成し、バージョン管理しないファイルやディレクトリを記載(例:*.log)
プロジェクトディレクトリ内にあるファイルやディレクトリを全てコミット
$ git add .
$ git commit -m "Initial Commit"
作成したGitHubリポジトリのURLをコピー&ペーストして、リモートブランチとして設定
$ git remote add origin https://github.com/xxxxxxxxxx
ローカルのファイルをアップロード
git push origin master
Username for 'https://github.com': xxxxxx
Password for 'https://planetsman@github.com':
Counting objects: 31, done.
Compressing objects: 100% (29/29), done.
Writing objects: 100% (30/30), 6.96 KiB | 0 bytes/s, done.
Total 30 (delta 3), reused 0 (delta 0)
remote: Resolving deltas: 100% (3/3), done.
To https://github.com/xxxxx/login.git
359beab..4836908 master -> master