CakePHP3で"ログイン状態の保持"を実装する

cakephp3で **"ログイン状態の保持"** の機能を実装してみました。

※Quick Start Guide http://book.cakephp.org/3.0/en/quickstart.html に追加する感じで

## 方針 ##

* cookieとデータベースにログインキーを保持
* 両者が一致したらログインとする

## テーブル ##
auto_loginテーブルを作ります

```sql
CREATE TABLE `users` (
    `id` INT(11) AUTO_INCREMENT PRIMARY KEY,
    `email` VARCHAR(255) NOT NULL,
    `password` VARCHAR(255) NOT NULL,
    `created` DATETIME,
    `modified` DATETIME
);

CREATE TABLE `auto_login` (
    `user_id` INT(11) UNSIGNED NOT NULL,
    `auto_login_key` VARCHAR(255) NOT NULL,
    PRIMARY KEY (`user_id`)
);
```

## view ##
チェックボックスを追加します

```php:login.ctp
<h1>Login</h1>
<?= $this->Form->create() ?>
<?= $this->Form->input('email') ?>
<?= $this->Form->input('password') ?>
<?= $this->Form->input('autologin', ['type' => 'checkbox']) ?>
<?= $this->Form->button('Login') ?>
<?= $this->Form->end() ?>
```

## Controller ##
cookieを使うので、Cookieコンポーネントをloadします

```php:AppController.php
$this->loadComponent('Cookie');
```

UserController.phpのlogin()とlogout()を修正します

```php:UserController.php
    public function login()
    {
        if ($this->request->is('post')) {
            $user = $this->Auth->identify();
            if ($user) {
                if ($this->request->data('autologin') === '1') {
                    $this->__setupAutoLogin($user);
                }
                $this->Auth->setUser($user);
                $this->Flash->success(__('Log In Success.'));
                return $this->redirect($this->Auth->redirectUrl());
            }
            $this->Flash->error('Your username or password is incorrect.');
        } else {
            if ($autoLoginKey = $this->Cookie->read('AUTO_LOGIN')) {
                $this->loadModel('AutoLogin');
                $query = $this->AutoLogin->findByAutoLoginKey($autoLoginKey);
                if ($query->count() > 0) {
                    $userId = $query->first()->user_id;
                    $user = $this->Users->get($userId)->toArray();
                    if ($user) {
                        // 一度ログインキーを消してから再作成する
                        $this->__destroyAutoLogin($user);
                        $this->__setupAutoLogin($user);
                        $this->Auth->setUser($user);
                        $this->Flash->success(__('Auto Log In Success.'));
                        return $this->redirect($this->Auth->redirectUrl());
                    }
                }
            }
        }
    }

    public function logout()
    {
        $this->__destroyAutoLogin($this->Auth->user());
        $this->Flash->success('You are now logged out.');
        return $this->redirect($this->Auth->logout());
    }
```

ログインキーを保存するメソッドとログインキーを削除するメソッドを実装します

```php:UserController.php
    private function __setupAutoLogin($user)
    {
        $this->loadModel('AutoLogin');
        $autoLoginKey = sha1(uniqid() . mt_rand(1, 999999999) . '_auto_login');
        $entity = $this->AutoLogin->newEntity([
            'user_id' => $user['id'],
            'auto_login_key' => $autoLoginKey
        ]);
        $this->AutoLogin->save($entity);

        $this->Cookie->config([
            'expires' => '+7 days',
            'path' => '/'
        ]);
        $this->Cookie->write('AUTO_LOGIN', $autoLoginKey);
    }

    private function __destroyAutoLogin($user)
    {
        $this->loadModel('AutoLogin');
        try {
            $entity = $this->AutoLogin->get($user['id']);
            if ($entity) {
                $this->AutoLogin->delete($entity);
                $this->Cookie->delete('AUTO_LOGIN');
            }
        } catch (RecordNotFoundException $e) {
            $this->Cookie->delete('AUTO_LOGIN');
        }
    }
```