ユーザ認証パッケージ Sentry2 の拡張

# はじめに。
アドベントカレンダー初参加のかわおと申します。
よろしくお願いします。

今回は、Sentry2のLaravel4での使用例を紹介したいと思います。  

![brand-docs.png](https://qiita-image-store.s3.amazonaws.com/0/52207/7337d993-df93-45bb-8596-da6459582f0c.png)


# ユーザ管理について
Laravel4にて、ユーザ認証パッケージ Sentry2の eloquentによる拡張を行いました。

eloquentモデルで扱い、モデルを拡張をすることで、追加用件の要望にこたえられ、強度の高いユーザ認証システムを構築することができます。  
その一例として、priorityの追加を行う手順を追って記します。  

Sentryの開発チームは現在後継の[Sentinel](https://cartalyst.com/manual/sentinel/1.0) というフリーミアムライセンスのライブラリを更新されておりますが、
今回紹介いたします Sentryの最終更新は１年前ほどということで、しばらくは現役でSentry2を扱うには問題ないと考えております。  

基本的にはマニュアルに書かれたとおりに進めていけば、セットアップ、構築は可能です。Composerから導入してください。  
https://cartalyst.com/manual/sentry/2.1#laravel-4  

# Sentryが標準機能でできること  
* ユーザは複数グループに所属可能。多対多の関係性で紐づけられる。  
* ログイン失敗時には規定回数でパスワードロックがかかる(Throttleに格納されます)  
* 少々コードは必要ですが、リマインダーメールへの対応()。  
* 権限設定を個人とグループに持たせることができ、権限の全所持する重ねがけが可能。  
など。盤石です。


# Sentryのライブラリの拡張で行えること  
* migration時に、カスタムでテーブル構成を定義できます。  
* 当然、eloquentにてユーザのカラムの拡張を行い、ユーザ情報のカスタムを行うことはできます。  
DBでリレーションなど追加の要件がなければ、modelのカスタムを行わずとも扱えます。  
* グループ間の関係は、users_groups テーブルなので、マイグレーション側のカスタムで対応されます。
グループごとのユーザの表示に優先順位をつけたい場合、
どうしてもグループとユーザ間の関係に優先順位をつけないとならないため、。


# 追加記述するべき箇所について  
sentryのライブラリはそのままに、configで見る場所を変える、migrationの設定の
[Laravel 4 + Sentry 2でデフォルトのテーブル名を変更する](http://d.hatena.ne.jp/ngmy/20131219/1387469158)  の記事を参考にさせて頂いております。  

## migrationのコピー、変更  
migration を行うファイルは、Cartalyst/sentry/src/migrations 以下にあります。  
pivot tableですが、こちらにpriorityの行を追加しました。  

```lang:xxxxxxx_create_users_groups.php
...
class CreateUsersGroups extends Migration {

	/**
	 * Run the migrations.
	 *
	 * @return void
	 */
	public function up()
	{
		Schema::create('users_groups', function(Blueprint $table)
		{
			//
			$table->integer('user_id')->unsigned();
			$table->integer('group_id')->unsigned();
			$table->integer('priority')->unsigned(); // ここに追加
...
```

## modelのコピー
Sentryのフォルダーにあるeloquentより、モデルを継承しましょう。  
sentry/src/Cartalyst/Sentry/Users/Eloquent のように各テーブル名の奥にEloquentと言うフォルダの中にあります。  
Group,Users,users_groupsをプロジェクトのmodelsディレクトリで継承する形で記述してください。  
宣言だけでもひとまず今は問題はないです。  

```lang:User.php
class User extends Cartalyst\Sentry\Users\Eloquent\User {
...
```

```lang:Group.php
class Group extends Cartalyst\Sentry\Groups\Eloquent\Group {
...
```

## modelの変更
グループの呼び出しに際して、```->withPivot('priority')->orderBy('priority', 'desc')``` を加えることで優先順位のソートをかけることが出来るようになります。  

```lang:User.php
...
...
	public function addGroupWithPivot(GroupInterface $group, array $pivot = array() )
	{
		if ( $this->inGroup($group))
		{
			$this->groups()->detach($group);
		}
		// pivotで関連する値をあるなら入れる
		$this->groups()->attach($group, $pivot);
		$this->invalidateUserGroupsCache();
		$this->invalidateMergedPermissionsCache();
		return true;
	}

	/**
	 * Returns the relationship between users and groups.
	 *
	 * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
	 */
	public function groups()
	{
		// return $this->belongsToMany(static::$groupModel, static::$userGroupsPivot);
		return $this->belongsToMany(static::$groupModel, static::$userGroupsPivot)->withPivot('priority')->orderBy('priority', 'desc');
	}
...
```
訂正：2015/1/17 下記のようにsyncで変更を行うと、グループを複数組み合わせた場合に不具合が出ますので削除->再度作成の呼び出し順に変更しました。  

```
// すでにグループに登録されている場合も同様にsyncで代入する
$this->groups()->sync(array($group->id => $pivot));
```

## コンフィグの設定  
Sentryへの反映にはコンフィグ設定が必要です。  

```lang:app\config\packages\cartalyst\sentry  
...
		//'model' => 'Cartalyst\Sentry\Groups\Eloquent\Group',
		'model' => 'Group', // カスタムモデルで読み書きさせる
...
		//'model' => 'Cartalyst\Sentry\Users\Eloquent\User',
		'model' => 'User', // カスタムモデルで読み書きさせる
...
```

ソート順の設定については、

```
if ( $user->addGroupWithPivot($staffGroup, array('priority' => $staffstatus['priority'])) ) {
```
のように modelに定義した addGroupWithPivot で行えます。  

# おわりに
駆け足となってしまいましたが、Sentryというモジュールは一度慣れてしまえば、基本的な機能は任せられ、業務案件にも適したレベルのカスタムまで行えるため、恩恵を受けることが出来ます。  
是非ご検討ください！  

明日は@yashikawa さんです。