FuelPHPのORM Observerはとても便利


## ORM Observer

FuelPHPのORMパッケージに含まれるオブザーバがとても便利。

イベントベースになっていて、特定のイベント発生時に処理を行うことが出来る。
たとえば、before_insert, before_save, after_loadなどのタイミング。

標準でいくつかのオブザーバが付属していて、自前で作ることもできる。

### 付属オブザーバ

* Observer_Self
	+ 任意のイベント発生時に、モデルに含まれる任意の関数を実行する
* Observer_CreatedAt
	+ レコードが挿入されたときにcreated_atを設定
* Observer_UpdatedAt
	+ レコードが更新されたときにupdated_atを設定
* Observer_Validation
	+ レコードの挿入・更新前にカラムのデータをバリデーションする
* Observer_Typing
	+ カラムのデータをキャストする
* Observer_Slug
	+ URLのスラグを生成する

## 試してみる

### テストデータ

とりあえず説明用にMySQLに適当なテーブルを追加してテストデータを投入。


```sql
CREATE TABLE `students` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL DEFAULT '',
  `blood_type` varchar(10) DEFAULT '',
  `favorite_things` text NOT NULL,
  `created_at` datetime NOT NULL,
  `updated_at` datetime DEFAULT NULL,
  `deleted_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```

![ScreenShot 2014-05-28 20.04.54.png](https://qiita-image-store.s3.amazonaws.com/0/17664/d4852e89-f429-c4a8-4d40-8904bfff3bb2.png "ScreenShot 2014-05-28 20.04.54.png")

### Model_Studentを作成

このような感じで、カラム名や論理削除フラグ、主要な4つのオブザーバを設定。

```php:student.php
class Model_Student extends Orm\Model_Soft
{
    protected static $_primary_key = array('id');

    protected static $_soft_delete = array(
        'mysql_timestamp' => true,
    );

    protected static $_properties = array(
        'id',
        'blood_type' => array(
            'data_type' => 'var_char',
            'validation' => array(
                'match_pattern' => array('@\A(A|B|O|AB)\z@'),
            ),
        ),
        'name' => array(
            'data_type' => 'varchar',
        ),
        'favorite_things' => array(
            'data_type' => 'json',
        ),
        'created_at' => array(
            'data_type' => 'time_mysql',
        ),
        'updated_at' => array(
            'data_type' => 'time_mysql',
        ),
        'deleted_at' => array(
            'data_type' => 'time_mysql',
        )
    );

    protected static $_observers = array(
        'Orm\Observer_Typing' => array(
            'events' => array('before_save', 'after_save', 'after_load')
        ),
        'Orm\Observer_Validation' => array(
            'events' => array('before_insert', 'before_save'),
        ),
        'Orm\Observer_UpdatedAt' => array(
            'events' => array('before_save'),
            'mysql_timestamp' => true,
        ),
        'Orm\Observer_CreatedAt' => array(
            'events' => array('before_insert'),
            'mysql_timestamp' => true,
        ),
    );
}

```

### TypingでJSONもデコード

モデルをfindして取得するだけで、以下のようにキャストされる。


```php
$student = \Model_Student::find(1);

/*
  array (
    'blood_type' => 'O',
    'name' => 'Ichigo Hoshimiya',
    'favorite_things' => 
    stdClass::__set_state(array(
       'color' => 'pink',
       'genre' => 'cute',
       'brand' => 'Angely Sugar',
    )),
    'created_at' => 
    Fuel\Core\Date::__set_state(array(
       'timestamp' => 1349659654,
       'timezone' => 'Asia/Tokyo',
    )),
    'updated_at' => 
    Fuel\Core\Date::__set_state(array(
       'timestamp' => 1401273534,
       'timezone' => 'Asia/Tokyo',
    )),
    'deleted_at' => NULL,
    'id' => '1',
  )
*/
```

ここにはないけど、int型やfloat型も、もちろんキャストされる。
tinyint(1)のカラムに、data_typeでboolを設定しておくとtrue/falseになったり。とっても便利。

### Validationでバリデーション

```php
$student = \Model_Student::find(1);

try
{
    $student->blood_type = 'AC';
    $student->save();
}
catch (\Orm\ValidationFailed $e)
{
    echo $e->getMessage();
    // The field blood_type must match the pattern @\A(A|B|O|AB)\z@.
}
```

blood_typeに「AC」を入れてsaveしようとしても、match_patternで「A, B, O, AB」のどれかに制限されているのでエラーになる。

### CreatedAt, UpdatedAt

もちろんupdateしてsaveすれば、updated_atが更新される。

```PHP
$student = \Model_Student::find(1);
$student->favorite_things->lucky_charm = 'おしゃもじ';
$student->save();
```
![ScreenShot 2014-05-28 20.15.35.png](https://qiita-image-store.s3.amazonaws.com/0/17664/2febd851-d712-081d-639d-db7f604a93e3.png "ScreenShot 2014-05-28 20.15.35.png")


## Typingマジ便利

JSONの他にもserializeとかも使えるので、こういうケースだと使い勝手がすごくいい。
細かい部分でスキーマレスなデータを取り扱いたくなるとき、ラララ〜って感じで小さく書ける。