cakePHP3でWebアプリ「TimeLetter」作成手順

# ①データベース作成
MySQL使用

usersテーブル

~~~
mysql> create table users(user_id int primary key auto_increment, user_name text not null, mail_address text not null, password text not null);
~~~


letterテーブル

~~~
mysql> create table letters(letter_id integer primary key auto_increment, user_id integer, posted_date datetime, send_date datetime, source_address text not null, destination_address text not null, title text, content text);
~~~

# ②アプリの雛形作成

作業ディレクトリにて

~~~
composer create-project --prefer-dist cakephp/app TimeLetter
~~~

# ③データベース登録

config/app.phpにて

~~~
'username' => '****',
'password' => '************',
'database' => 'time_letter',
~~~

### 接続できたか確認
サーバ起動

~~~
bin\cake server
~~~

http://localhost:8765/(http://localhost:8765/)
![スクリーンショット (603).png](https://qiita-image-store.s3.amazonaws.com/0/251551/d825dc66-dd8a-433c-d572-bc2466229253.png)

接続できている。


# ③bakeコマンド
作業ディレクトリにてMVC雛形を作成

~~~
bin\cake bake all users
bin\cake bake all letters
~~~

必要なディレクトリ(usersとlettersのController,Model,Templateなど)が作成されたのを確認

# ログイン機能実装
usersアプリケーションを修正してログイン機能を実装する。

### AppController.phpを変更

~~~
<?php

namespace App\Controller;

use Cake\Controller\Controller;
use Cake\Event\Event;

class AppController extends Controller
{
    // loginしてない状態で入れるページを設定
    public function beforeFilter(Event $event)
    { $this->Auth->allow(['login', 'add']);
    }

    public function initialize()
    {
        parent::initialize();
        $this->loadComponent('Paginator');
        $this->loadComponent('RequestHandler', [
            'enableBeforeRedirect' => false,
        ]);
        $this->loadComponent('Flash');
        $this->loadComponent('Auth', [
            'loginRedirect' => [
                'controller' => 'Users',
                'action' => 'index'
            ],
            'logoutRedirect' => [
                'controller' => 'Users',
                'action' => 'login'
            ],
            'authenticate' => [
                'Form' => [
                    'userModel' => 'Users',
                    'fields' => [
                        'username' => 'user_name',
                        'password' => 'password'
                    ]
                ]
            ],
        ]);
    }
}

~~~

authenticateで認証する項目を設定する。ここではusernameとpassword。fieldsのキーはauth固定で値はカラム名と等しくしなければならない。user_nameでカラムを作ったので変更。

### UsesController.phpを変更

~~~
<?php
namespace App\Controller;

use App\Controller\AppController;
use Cake\Event\Event;

class UsersController extends AppController
{

    public $paginate = [
        'limit' => 5,
        'order' => [
            'Users.user_id' => 'asc'
        ]
    ];

    public function initialize()
    {
        parent::initialize();
        $this->loadComponent('Paginator');
    }

    public function beforeFilter(Event $event)
    {
        parent::beforeFilter($event);
        $this->Auth->allow(['login', 'add']);
    }

    public function index()
    {
        $this->set('users', $this->Users->find('all'));
        $this->set('users', $this->paginate());
    }

    public function view($id = null)
    {
        $user = $this->Users->get($id, [
            'contain' => []
        ]);

        $this->set('user', $user);
        $this->set('_serialize', ['user']);
    }

    public function add()
    {
        $user = $this->Users->newEntity();
        if ($this->request->is('post')) {
            $user = $this->Users->patchEntity($user, $this->request->getData());
            if ($this->Users->save($user)) {
                $this->Flash->success(__('The user has been saved.'));

                return $this->redirect(['action' => 'login']);
            }
            $this->Flash->error(__('The user could not be saved. Please, try again.'));
        }
        $this->set(compact('user'));
        $this->set('_serialize', ['user']);
    }


    public function login()
    {
        if ($this->request->is('post')) {
            $user = $this->Auth->identify();
            if ($user) {
                $this->Auth->setUser($user);
                return $this->redirect($this->Auth->redirectUrl());
            }
            $this->Flash->error(__('Invalid username or password, try again'));
        }
    }
    public function logout()
    {
        return $this->redirect($this->Auth->logout());
    }

}

~~~

ここでもbeforeFilterを指定する。
またdelete,editはログイン機能には必要ないので削除し、login,logoutを作成

### Entityを変更


~~~
<?php
namespace App\Model\Entity;

use Cake\ORM\Entity;
use Cake\Auth\DefaultPasswordHasher;

class User extends Entity
{
    protected $_accessible = [
        'user_name' => true,
        'mail_address' => true,
        'password' => true,
        'user_id' => false
    ];
    protected $_hidden = [
        'password'
    ];

    protected function _setPassword($password)
    {
        if (strlen($password) > 0) {
            return (new DefaultPasswordHasher)->hash($password);
        }
    }
}
~~~

### UsersTableを変更

~~~
<?php
namespace App\Model\Table;

use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;

class UsersTable extends Table
{
    public function initialize(array $config)
    {
        parent::initialize($config);

        $this->setTable('users');
        $this->setDisplayField('user_id');
        $this->setPrimaryKey('user_id');
    }
    public function validationDefault(Validator $validator)
    {
        return $validator
            ->notEmpty('user_name', 'A username is required')
            ->notEmpty('mail_address', 'A mail address is required')
            ->notEmpty('password', 'A password is required');
    }
    public function buildRules(RulesChecker $rules)
    {
        $rules->add($rules->isUnique(['mail_address']));

        return $rules;
    }
}
~~~

keyやfieldはカラム名にすること。

### Templete

add.ctp

~~~
<nav class="large-3 medium-4 columns" id="actions-sidebar">
    <ul class="side-nav">
        <li class="heading"><?= __('Actions') ?></li>
        <li><?= $this->Html->link(__('List Users'), ['action' => 'index']) ?></li>
    </ul>
</nav>
<div class="users form large-9 medium-8 columns content">
    <?= $this->Form->create($user) ?>
    <fieldset>
        <legend><?= __('Add User') ?></legend>
        <?= $this->Form->control('user_name') ?>
        <?= $this->Form->control('mail_address') ?>
        <?= $this->Form->control('password') ?>
   </fieldset>
   <?= $this->Html->link(__('Login'), ['action' => 'login']) ?>
   <?= $this->Form->button(__('Submit')) ?>
   <?= $this->Form->end() ?>
</div>
~~~

index.ctp

~~~
<nav class="large-3 medium-4 columns" id="actions-sidebar">
    <ul class="side-nav">
        <li class="heading"><?= __('Actions') ?></li>
        <li><?= $this->Html->link(__('Logout'), ['action' => 'logout']) ?></li>
    </ul>
</nav>
<div class="users index large-9 medium-8 columns content">
    <h3><?= __('Users') ?></h3>
    <table cellpadding="0" cellspacing="0">
        <thead>
            <tr>
                <th scope="col"><?= $this->Paginator->sort('user_id') ?></th>
                <th scope="col"><?= $this->Paginator->sort('username') ?></th>
                <th scope="col"><?= $this->Paginator->sort('password') ?></th>
                <th scope="col" class="actions"><?= __('Actions') ?></th>
            </tr>
        </thead>
        <tbody>
            <?php foreach ($users as $user): ?>
            <tr>
                <td><?= $this->Number->format($user->user_id) ?></td>
                <td><?= h($user->user_name) ?></td>
                <td><?= h($user->password) ?></td>
                <td class="actions">
                    <?= $this->Html->link(__('View'), ['action' => 'view', $user->user_id]) ?>
                </td>
            </tr>
            <?php endforeach; ?>
        </tbody>
    </table>
    <div class="paginator">
        <ul class="pagination">
            <?= $this->Paginator->first('<< ' . __('first')) ?>
            <?= $this->Paginator->prev('< ' . __('previous')) ?>
            <?= $this->Paginator->numbers() ?>
            <?= $this->Paginator->next(__('next') . ' >') ?>
            <?= $this->Paginator->last(__('last') . ' >>') ?>
        </ul>
        <p><?= $this->Paginator->counter(['format' => __('Page {{page}} of {{pages}}, showing {{current}} record(s) out of {{count}} total')]) ?></p>
    </div>
</div>
~~~

login.ctp

~~~
<div class="users form large-9 medium-8 columns content">
    <?= $this->Flash->render() ?>
    <?= $this->Form->create() ?>
    <fieldset>
        <legend><?= __('Please enter your username and password') ?></legend>
        <?= $this->Form->control('user_name') ?>
        <?= $this->Form->control('password') ?>
    </fieldset>
    <?= $this->Form->button(__('Submit')) ?>
    <?= $this->Form->end() ?>
</div>
~~~

view.ctp

~~~
<nav class="large-3 medium-4 columns" id="actions-sidebar">
    <ul class="side-nav">
        <li class="heading"><?= __('Actions') ?></li>
        <li><?= $this->Html->link(__('List Users'), ['action' => 'index']) ?> </li>
        <li><?= $this->Html->link(__('Logout'), ['action' => 'logout']) ?></li>
    </ul>
</nav>
<div class="users view large-9 medium-8 columns content">
    <h3><?= h($user->id) ?></h3>
    <table class="vertical-table">
        <tr>
            <th scope="row"><?= __('Username') ?></th>
            <td><?= h($user->user_name) ?></td>
        </tr>
        <tr>
            <th scope="row"><?= __('Id') ?></th>
            <td><?= $this->Number->format($user->id) ?></td>
        </tr>

    </table>
</div>
~~~

### サーバを起動しログインできるか確認する