PHPでログイン機能を実装するチュートリアル #6


1. [基本設計、ユーザーモデル](http://qiita.com/ShibuyaKosuke/items/f114ffccf441edb2b745)
2. [オートローダー](http://qiita.com/ShibuyaKosuke/items/6109810464a7293293bd)
3. [例外・ログ](http://qiita.com/ShibuyaKosuke/items/88b1305a4ff0c5f3e728)
4. [PDO シングルトン　SQLインジェクション](http://qiita.com/ShibuyaKosuke/items/094df7f399912ae7df56)
	* [汎用データベース・クラス作成](http://qiita.com/ShibuyaKosuke/items/8502492ac024362b528d)

5. [ユーザーモデルの作成](http://qiita.com/ShibuyaKosuke/items/81c556d836d70a29c5f3)
6. [クラスの継承](http://qiita.com/ShibuyaKosuke/items/490ffa6a92670cf6c644)
7. [テンプレート・クラスの実装](http://qiita.com/ShibuyaKosuke/items/803dc58a23601118942e)
8. [アカウントロック機能の実装](http://qiita.com/ShibuyaKosuke/items/e890becdb8193d3def05)
9. [メール送信機能の実装](http://qiita.com/ShibuyaKosuke/items/78c8673782ebbf51584b)
10. [アカウントロック解除機能の実装](http://qiita.com/ShibuyaKosuke/items/ee4b1896071eeca37628)
11. [CSRF対策](http://qiita.com/ShibuyaKosuke/items/8100e304e4447e44e98c)

前回作成した `UserModel.class.php` には、システム側からの要求で作成したメソッド(A)とDBの定義から要求されたメソッドとプロパティ(B)が混在しています。そこで、この(A)と(B)については分けておきたくなります。
**(B)の部分については、テーブルの定義から自動的に決定される**ので、ソースコードを書き出すプログラムを作成しておけば、テーブルの定義が変わってもそのプログラムで書き出せば良いし、 **手作業で書き換えることはかえってバグの元** になります。

また、動作確認などでバグがあったとしても、`UserModelBase.class.php` にバグがある可能性は低く、`UserModel.class.php`に書かれた処理を対象にデバッグ対象を絞り込みやすくなります。

こうしておくことで、基本的に `UserModelBase.class.php` はいじってはならず、`UserModel.class.php`に必要な処理を記述するという「基本的なルール」を設定することができます。

```php:UserModel.class.php
<?php

namespace MyApp\model;

use MyApp\dao\UserDao;

/**
 * UserModel
 */
final class UserModel extends UserModelBase
{

	/**
	 * アカウントをロックするログイン失敗回数
	 */
	const LOCK_COUNT = 3;

	/**
	 * アカウントをロックする時間（分）
	 */
	const LOCK_MINUTE = 15;

	/**
	 * メールアドレスからユーザーを検索する
	 * @param string $strEmail
	 * @return \MyApp\model\UserModel
	 */
	public function getModelByEmail($strEmail)
	{
		$dao = UserDao::getDaoFromEmail($strEmail);
		return (isset($dao[0])) ? $this->setProperty(reset($dao)) : null;
	}

	/**
	 * パスワードが一致しているかどうかを判定する
	 * @param type $password
	 * @return bool
	 */
	public function checkPassword($password)
	{
		$hash = $this->getPassword();
		return password_verify($password, $hash);
	}

	/**
	 * ログイン失敗をリセットする
	 * １以上のときに０にする
	 * @return bool
	 */
	public function loginFailureReset()
	{
		$count = $this->getLoginFailureCount();
		if (0 < $count) {
			$this->setLoginFailureCount(0)
				->setLoginFailureDatetime(null);
			return $this->save();
		}
		//変更の必要がない
		return true;
	}

	/**
	 * ログイン失敗をインクリメントする
	 * 指定回数（self::LOCK_COUNT）に満たないときのみ＋１
	 * @return bool
	 */
	public function loginFailureIncrement()
	{
		$count = $this->getLoginFailureCount();
		if (self::LOCK_COUNT > $count) {
			$now = (new \DateTime())->format('Y-m-d H:i:s');
			$this->setLoginFailureCount(1 + $count)
				->setLoginFailureDatetime($now);
			return $this->save();
		}

		//ログイン失敗が設定以上のとき
		return true;
	}

	/**
	 * アカウントがロックされているかどうかを判定する
	 * @return bool ロックされていたら true
	 */
	public function isAccountLock()
	{
		$count = $this->getLoginFailureCount();
		$datetime = $this->getLoginFailureDatetime();

		$lastFailureDatetime = new \DateTime($datetime);
		$interval = new \DateInterval(
			sprintf('PT%dM', self::LOCK_MINUTE)
		);
		$lastFailureDatetime->add($interval);

		//設定時間以内で、かつ設定回数以上の失敗を記録しているとき
		if ($lastFailureDatetime > new \DateTime() && self::LOCK_COUNT <= $count) {
			return true;
		}
		return false;
	}

}
```

```php:UserModelBase.class.php
<?php

/**
 * **** 注意 ****
 * このファイルは自動的に生成されます。
 * 手動でいじったりしないようにしてください。
 */

namespace MyApp\model;

use MyApp\dao\UserDao;

/**
 * UserModelBase
 */
class UserModelBase
{

	/**
	 * ユーザーID
	 */
	private $_userId = null;

	/**
	 * パスワード（ハッシュ）
	 */
	private $_password = null;

	/**
	 * 表示名
	 */
	private $_displayName = null;

	/**
	 * メールアドレス
	 */
	private $_email = null;

	/**
	 * トークン
	 */
	private $_token = null;

	/**
	 * ログイン失敗回数
	 */
	private $_loginFailureCount = null;

	/**
	 * ログイン失敗日時
	 */
	private $_loginFailureDatetime = null;

	/**
	 * 削除フラグ
	 */
	private $_deleteFlag = null;

	/**
	 * プロパティをセットする
	 * @param array $arrDao
	 * @return \MyApp\model\UserModel
	 */
	protected function setProperty(array $arrDao)
	{
		$this->setUserId($arrDao['userId'])
			->setDisplayName($arrDao['displayName'])
			->setEmail($arrDao['email'])
			->setPassword($arrDao['password'])
			->setToken($arrDao['token'])
			->setLoginFailureCount($arrDao['loginFailureCount'])
			->setLoginFailureDatetime($arrDao['loginFailureDatetime'])
			->setDeleteFlag($arrDao['deleteFlag']);
		return $this;
	}

	/**
	 * 更新する
	 * @return bool
	 */
	public function save()
	{
		return UserDao::save($this);
	}

	/**
	 * 新規作成する
	 * @return int
	 */
	public function create()
	{
		return UserDao::insert($this);
	}

	/**
	 * ユーザーIDを設定する
	 * @param int $userId
	 * @return \MyApp\model\UserModel
	 */
	public function setUserId($userId)
	{
		$this->_userId = $userId;
		return $this;
	}

	/**
	 * パスワード（ハッシュ）を設定する
	 * @param string $password
	 * @return \MyApp\model\UserModel
	 */
	public function setPassword($password)
	{
		$this->_password = $password;
		return $this;
	}

	/**
	 * 表示名を設定する
	 * @param string $displayName
	 * @return \MyApp\model\UserModel
	 */
	public function setDisplayName($displayName)
	{
		$this->_displayName = $displayName;
		return $this;
	}

	/**
	 * メールアドレスを設定する
	 * @param string $email
	 * @return \MyApp\model\UserModel
	 */
	public function setEmail($email)
	{
		$this->_email = $email;
		return $this;
	}

	/**
	 * トークンを設定する
	 * @param string $token
	 * @return \MyApp\model\UserModel
	 */
	public function setToken($token)
	{
		$this->_token = $token;
		return $this;
	}

	/**
	 * ログイン失敗回数を設定する
	 * @param int $loginFailureCount
	 * @return \MyApp\model\UserModel
	 */
	public function setLoginFailureCount($loginFailureCount)
	{
		$this->_loginFailureCount = $loginFailureCount;
		return $this;
	}

	/**
	 * ログイン失敗日時を設定する
	 * @param string $loginFailureDatetime
	 * @return \MyApp\model\UserModel
	 */
	public function setLoginFailureDatetime($loginFailureDatetime)
	{
		$this->_loginFailureDatetime = $loginFailureDatetime;
		return $this;
	}

	/**
	 * 削除フラグを設定する
	 * @param bool $deleteFlag
	 * @return \MyApp\model\UserModel
	 */
	public function setDeleteFlag($deleteFlag)
	{
		$this->_deleteFlag = $deleteFlag;
		return $this;
	}

	/**
	 * ユーザーIDを取得する
	 * @return int
	 */
	public function getUserId()
	{
		return $this->_userId;
	}

	/**
	 * パスワード（ハッシュ）を取得する
	 * @return string
	 */
	public function getPassword()
	{
		return $this->_password;
	}

	/**
	 * 表示名を取得する
	 * @return string
	 */
	public function getDisplayName()
	{
		return $this->_displayName;
	}

	/**
	 * メールアドレスを取得する
	 * @return string
	 */
	public function getEmail()
	{
		return $this->_email;
	}

	/**
	 * トークンを取得する
	 * @return string
	 */
	public function getToken()
	{
		return $this->_token;
	}

	/**
	 * ログイン失敗回数を取得する
	 * @return int
	 */
	public function getLoginFailureCount()
	{
		return $this->_loginFailureCount;
	}

	/**
	 * ログイン失敗日時を取得する
	 * @return string
	 */
	public function getLoginFailureDatetime()
	{
		return $this->_loginFailureDatetime;
	}

	/**
	 * 削除フラグを取得する
	 * @return bool
	 */
	public function getDeleteFlag()
	{
		return $this->_deleteFlag;
	}

}
```

次回は、当初予定していた「ログインメソッドの実装」はすでに作成済みですので、テンプレート・クラスを作成することにします。