PHPでログイン機能作ってみた

# 作った動機
+ PHPのお勉強のため。
+ [Javaでログイン機能作ってみた](https://qiita.com/parapore/items/8043f424ad66aa0d450e)の記事がぽつぽついいねやストックされるので、PHPでも需要があるかと思い作ってみました。

# 機能
ログインログアウト。
アカウント新規作成・更新・削除。

# 環境
PHP7
MariaDB10

#コード
PHPのお作法がわからないので、Javaっぽい書き方になっています。

##Model

```php:account.php
<?php
class Account {
  private $id;
  private $name;
  private $password;

  public function idGet():int {
    return $this->id;
  }

  public function idSet(int $id) {
    $this->id = $id;
  }

  public function nameGet():string {
    return $this->name;
  }

  public function nameSet(string $name) {
    $this->name = $name;
  }

  public function passwordGet(): string {
    return $this->password;
  }

  public function passwordSet(string $password) {
    $this->password = $password;
  }
}
```

```php:sessionDestroy.php
<?php
session_start();

//ログアウト処理。
//セッション変数を空にする。
$_SESSION = [];

// セッションクッキーが存在する場合には破棄
if(isset($_COOKIE[session_name()])) {
  $cparam = session_get_cookie_params();
  setcookie(session_name(), '', time() - 3600,
            $cparam['path'], $cparam['domain'],
            $cparam['secure'], $cparam['httponly']);
}
//セッションを破棄。
session_destroy();

// ログイン画面へ
header("Location: login.html");
```

```php:encode.php
<?php
/**
 * クロスサイトスクリプティング（XSS）防止用。
 * ユーザー入力値をHTML表示する時に呼び出す。
 * $str:エンコード対象の文字列
 * #charset:エンコードタイプ
 * ENT_QUOTES | ENT_HTML5:シングル/ダブルクォートを双方エスケープし、HTML5文書として処理。原則はこれらしい。
 */ 
function e(string $str, string $charset = "UTF-8"): string {
  return htmlspecialchars($str, ENT_QUOTES | ENT_HTML5, $charset);
}
```

## DAO

```php:connectDB.php
<?php
function getDb() { 
  $dsn = 'mysql:dbname=selfphp; host=127.0.0.1; charset=utf8'; 
  $usr = 'root'; 
  $password = 'password'; 
  
  //DB接続
  $db = new PDO($dsn, $usr, $password); 

  // 例外処理を有効化
  $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  return $db;
  }
```

```php:accountSearch.php
<?php
require_once("./account.php");
require_once("./connectDB.php");

function getAccount(string $name, string $password)
{
  $account = new Account();
  try {
    //上記DB接続のgetDbメソッドでDB接続 
    $db = getDb();


    // 検索SQLプレースホルダで準備 
    $stt = $db->prepare("SELECT * FROM account WHERE name = ?");
    $stt->bindValue(1, $name);
    // SELECT命令を実行
    $stt->execute();

    //アカウントの有無判定フラグ
    $flag = 0;
    //検索結果の取得 PDO::FETCH_ASSOCは連想配列で取得
    while ($row = $stt->fetch(PDO::FETCH_ASSOC)) {
      $hash = $row['password'];
      //ハッシュ値とパスワードの照合
      if(password_verify($password, $hash)){
        $account->idSet($row['id']);
        $account->nameSet($row['name']);
        $account->passwordSet($row['password']);
        $flag = 1;
      }
    }
    // 例外が発生したら、エラー表示 
  } catch (PDOException $e) {
    print "エラーメッセージ：{$e->getMessage()}";
  } finally {
    $stt = null;
    $db = null;
  }
  if($flag === 0) {
    //アカウントが見つからなければnullを返す
    return null;
  }else{
    //アカウントが見つかればアカウント情報を返す
    return $account;
  }
}
```

```php:accountInsert.php
<?php
require_once("./connectDB.php");


function insertAccount($name, $password)
{
  try {
    //上記DB接続のgetDbメソッドでDB接続 
    $db = getDb();
    // ドランザクションを開始 
    $db->beginTransaction();
    // パスワードハッシュを生成
    $hash = password_hash($password, PASSWORD_DEFAULT);
    // 更新SQLプレースホルダで準備 
    $stt = $db->prepare('INSERT INTO account(name, password) VALUES(:name, :password)');
    $stt->bindValue(':name', $name);
    $stt->bindValue(':password', $hash);
    // insert命令を実行 
    $stt->execute();
    // 全ての処理が完了したらトランザクションをコミット 
    $db->commit();
    // 例外発生したら、エラー表示＆トランザクションをロールバック 
  } catch (PDOException $e) {
    $db->rollBack();
    print "エラーメッセージ：{$e->getMessage()}";
  } finally {
    $stt = null;
    $db = null;
  }
}
```


```php:accountUpdate.php
<?php
require_once("./connectDB.php");

function updateAccount(int $id, string $name, string $password)
{
  try {
    //上記DB接続のgetDbメソッドでDB接続 
    $db = getDb();
    // ドランザクションを開始 
    $db->beginTransaction();
    // パスワードハッシュを生成
    $hash = password_hash($password, PASSWORD_DEFAULT);
    // 更新SQLプレースホルダで準備 
    $stt = $db->prepare('UPDATE account SET name=:name, password=:password WHERE id = :id');
    $stt->bindValue(':id', $id);
    $stt->bindValue(':name', $name);
    $stt->bindValue(':password', $hash);
    $stt->execute();
    // 全ての処理が完了したらトランザクションをコミット 
    $db->commit();
    // 例外発生したら、エラー表示＆トランザクションをロールバック 
  } catch (PDOException $e) {
    $db->rollBack();
    print "エラーメッセージ：{$e->getMessage()}";
  } finally {
    $stt = null;
    $db = null;
  }
}
```

```php:accountDelete.php
<?php
require_once("./connectDB.php");

function deleteAccount(int $id)
{
  try {
    $db = getDb();
    // ドランザクションを開始 
    $db->beginTransaction();
    $stt = $db->prepare('DELETE FROM account WHERE id = :id');
    $stt->bindValue(':id', $id);
    $stt->execute();
    // 全ての処理が完了したらトランザクションをコミット 
    $db->commit();
    // 例外発生したら、エラー表示＆トランザクションをロールバック 
  } catch (PDOException $e) {
    $db->rollBack();
    print "エラーメッセージ：{$e->getMessage()}";
  } finally {
    $stt = null;
    $db = null;
  }
}
```

## Controller

```php:searchController.php
<?php
require_once("./accountSearch.php");
session_start();

$account = getAccount($_GET['name'], $_GET['password']);

if ($account === null) {
  // ログイン失敗
  header("Location: loginError.html");
} else {
  // ログイン成功
  //セッションにアカウント情報をセット
  $_SESSION["account"] = $account;
  header("Location: loginSuccess.php");
}
```

```php:registerController.php
<?php
require_once("./accountInsert.php");
require_once("./accountSearch.php");
session_start();


insertAccount($_POST['name'], $_POST['password']);
$account = getAccount($_POST['name'], $_POST['password']);
//セッションにアカウント情報をセット
$_SESSION["account"] = $account; 

if($account === null) {
  header("Location: loginError.html");
}else{
  header("Location: loginSuccess.php");
}

```

```php:updateController.php
<?php
require_once("./account.php");
require_once("./accountUpdate.php");
require_once("./accountSearch.php");
session_start();

$id = $_SESSION["account"]->idGet();
$name = $_POST["name"];
$password = $_POST["password"];
updateAccount($id, $name, $password);

$account = getAccount($name, $password);
//セッションにアカウント情報をセット
$_SESSION["account"] = $account; 

if($account === null) {
  header("Location: loginError.html");
}else{
  header("Location: loginSuccess.php");
}
```

```php:deleteController.php
<?php
require_once("./accountDelete.php");
require_once("./account.php");
session_start();

$id = $_SESSION["account"]->idGet();
deleteAccount($id);
header("Location: delete.html");
?>
```

## View

```php:loginSuccess.php
<?php
require_once("./account.php");
session_start();

// セッションID盗聴対策
// ログイン後に新しいセッションIDを生成。セッション情報は保持される。
session_regenerate_id();

print "id：".$_SESSION["account"]->idGet();
print "<br>";
print "氏名：".$_SESSION["account"]->nameGet();
print "<br>";
print "password：".$_SESSION["account"]->passwordGet();
print "<br>";
print "<form method=\"POST\" action=\"login.html\">
        <input type=\"submit\" value=\"ログイン画面へ\">
       </form>";
print "<form method=\"POST\" action=\"sessionDestroy.php\">
       <input type=\"submit\" value=\"ログアウト\">
      </form>";
print "<form method=\"POST\" action=\"update.html\">
        <input type=\"submit\" value=\"アカウント情報変更\">
       </form>";
print "<form method=\"POST\" action=\"deleteController.php\">
       <input type=\"submit\" value=\"アカウント削除\">
      </form>";
```

```html:login.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>ログイン画面</title>
</head>
<body>
  <form method="GET" action="searchController.php">
    <p>氏名　　　<input type="text" name="name"></p>
    <p>パスワード<input type="text" name="password"></p>
    <p><input type="submit" value="ログイン"></p>
  </form>
  <p><a href="register.html">新規登録はこちら</a></p>
</body>
</html>
```

```html:loginError.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>ログイン失敗</title>
</head>
<body>
  ログインできませんでした。
  <p><a href="login.html">ログイン画面へ</a></p>
  <p><a href="register.html">新規登録はこちら</a></p>
</body>
</html>
```

```html:register.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <form method="POST" action="registerController.php">
    <p>氏名　　　<input type="text" name="name"></p>
    <p>パスワード<input type="text" name="password"></p>
    <p><input type="submit" value="新規登録"></p>
  </form>
</body>
</html>
```

```html:update.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>更新</title>
</head>
<body>
  <form method="POST" action="updateController.php">
    <p>氏名　　　<input type="text" name="name"></p>
    <p>パスワード<input type="text" name="password"></p>
    <p><input type="submit" value="アカウント情報変更"></p>
  </form>
</body>
</html>
```

## DB構成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/566570/0adcd247-7d7d-deec-6828-b5e79924868c.png)