CakePHP3+CakeDC/usersでModelをカスタマイズする

CakePHP3で手軽に認証機能を実装できるので、[CakeDC/users](https://github.com/CakeDC/users)プラグインをよく利用します。
ただ、実運用で使う場合には色々とカスタマイズしたい要件が出てきます。

+ usersテーブルに独自のカラムを追加したい
+ usernameやpasswordのバリデーションを要件に合わせたい
+ usersテーブルの子テーブルとのアソシエーションを設定したい

等々

その場合、usersテーブルに対して独自にカスタマイズしたTableクラスやEntityクラスを使いたくなりますが、拡張方法の基本は公式ドキュメントにあるものの、実際の使い方がいまいちわかりにくかったのでメモ代わりに残します。

[公式ドキュメント：　Extending the Plugin](https://github.com/CakeDC/users/blob/master/Docs/Documentation/Extending-the-Plugin.md)

試した環境は以下の通りです。

~~~
PHP : 5.6.23
CakePHP : v3.3.3
CakeDC/users : 3.2.3
~~~

## 独自のModelを作成

独自のTableクラス、Entityクラスを作成します。
ここは公式ドキュメントのままです。

CakeDC/usersのUserクラスを継承して独自のMyUserクラスを作成します。

src/Model/Entity/MyUsers.php を作成

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

use CakeDC\Users\Model\Entity\User;

class MyUser extends User
{
}
~~~

CakeDC/UsersのUsersTableクラスを継承して独自のMyUsersTableクラスを作成します。

src/Model/Table/MyUsersTable.php を作成

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

use CakeDC\Users\Model\Table\UsersTable;

class MyUsersTable extends UsersTable
{

    public function initialize(array $config)
    {
        parent::initialize($config);
    }
}
~~~

上記で作成したTableクラスをCakeDC/usersプラグインで利用するための設定を行います。

事前にconfig/bootstrap.phpに以下を設定。

~~~
Configure::write('Users.config', ['users']);
Plugin::load('Users', ['routes' => true, 'bootstrap' => true]);
~~~

config/users.php を作成し、以下を設定（今回利用する以外の設定を全て含んだテンプレートは./vendor/cakedc/users/config/users.phpにあるのでコピってきて利用してもいいです）

~~~
<?php
use Cake\Core\Configure;

$config = [
    'Users' => [
        // Table used to manage users
        'table' => 'MyUsers'
    ]
];
return $config;
~~~

以上で、独自にカスタマイズしたモデルを利用することができます。

ためしにコンソールを立ち上げてレコードを取得してみます。
MyUsersテーブルでfindするとMyUserエンティティクラスを取得できます。

~~~
$ ./bin/cake console

use Cake\ORM\TableRegistry;
$my_users_table = TableRegistry::get('MyUsers');
$my_users_table->get(1);
=> App\Model\Entity\MyUser {#000
     +"id": "xxx",
     +"username": "xxx",

==（略）==

   }
~~~

あとは必要に応じてバリデーションなりアソシエーションなりを設定していけばいいです。

## Modelのカスタマイズ

たとえばカラム'username'の入力を英数字と'-','_'に制限したいなら以下のような感じでしょうか。

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

use CakeDC\Users\Model\Table\UsersTable;
use Cake\Validation\Validator;

class MyUsersTable extends UsersTable
{

==（略）==

    public function validationDefault(Validator $validator)
    {
        $validator_new = parent::validationDefault($validator);
        
        $validator_new->add('username', 'custom', [
            'rule' => function ($value, $context) {
                return preg_match('/^[a-zA-Z][a-zA-Z0-9_\-]+$/', $value) === 1;
            },
            'message' => "英数字と記号（'-'または'_'）のみ利用できます。1文字目に数字・記号は利用できません。"
        ]);
        
        return $validator_new;
    }
}
~~~

usersテーブル配下にprofilesテーブルを作成してアソシエーションを設定するなら以下のような感じにできます。

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

use CakeDC\Users\Model\Table\UsersTable;

class MyUsersTable extends UsersTable
{

    public function initialize(array $config)
    {
        parent::initialize($config);
        $this->hasOne('Profiles', [
            'foreignKey' => 'user_id'
        ]);
    }
}
~~~

profilesテーブル側では以下のように設定します。

~~~
==(略)==

class ProfilesTable extends Table
{

    public function initialize(array $config)
    {
        parent::initialize($config);

==(略)==

        $this->belongsTo('MyUsers', [
            'foreignKey' => 'user_id',
            'joinType' => 'INNER',
            'propertyName' => 'user'
        ]);
    }
}
~~~

profilesテーブル側でfindする際にusersテーブルを含める場合は以下の通りです。
containする際にUsersではなくMyUsersを指定する必要があります。

~~~
$profiles_table = TableRegistry::get('Profiles);
$profiles_table->find()->contain(['MyUsers']);
~~~

以上