FuelPHPのORMを攻略しよう

# FuelPHPのORMを使ってみよう

最近FuelのORM初めて使ってみたので、自分の備忘録も兼ねて。
FuelのORMについて記述されているサイトや投稿はたくさんありますが、
リレーションの細かい設定など、実際にこれ読んで使ってみようとするにはまだ充実してないなあ〜という印象なので、その辺を補える記事にします。
~~公式doc隅々まで見て解読して使えばいーんだよというツッコミはご法度！！~~

## ORMを使うには
ORMはFuelPHPのパッケージ内に標準で含まれています。
configファイル内にパッケージ名を指定することでORMが使用できます。

```app/config/config.php

     'always_load'  => array(

        /**
         * These packages are loaded on Fuel's startup.
         * You can specify them in the following manner:
         *
         * array('auth'); // This will assume the packages are in PKGPATH
         *
         * // Use this format to specify the path to the package explicitly
         * array(
         *     array('auth'    => PKGPATH.'auth/')
         * );
         */
        'packages'  => array(
            'orm',
        ),
```
always_load 内に記述しましょう。

## ORMの設定

* 今回はサンプルに以下のER図内のテーブルを使用します。
図の作成は MySQLWorkbench(ORACLE MySQL公式のGUIツール) にて行いました。
![er図.png](https://qiita-image-store.s3.amazonaws.com/0/49828/56fc3bb7-9c26-7363-d75b-d845368af0f9.png)

※あくまでORMを使う上でのサンプルなので設計上の細かいところは気にしないでください。

### Modelクラスの作成

それでは上記のER図を参考に、ORMを使用してデータの取得をやってみます。
まずそれぞれ使用するモデルとORMを関連付けるために、
<pre>
app/class/model/モデル名.php
</pre>
このようにModelクラスを作成します。
作成後、Modelクラス内でOrm\Modelを拡張するかextendsしてやる必要があります。
以下のようにModelクラスで

```app/classes/model/shop.php
use Orm\Model
class Model_Shop extends Model
{
```
名前空間で直接指定してやるか

```app/classes/model/shop.php
class Model_Shop extends \Orm\Model
{
```
¥Orm¥Modelを拡張する(extendsする)記述を書きます。
Modelクラスはデータを取得したいテーブルに関連する、そのモデルの数分クラスを作成してやります。
上記のER図の例だと、use_food_sub のテーブルからデータを取得したい場合、それに関連するモデル

* model/shop.php

```app/classes/model/shop.php
class Model_Shop extends \Orm\Model
{
```

* model/usefood.php

```app/classes/model/usefood.php
class Model_Usefood extends \Orm\Model
{
```

* model/usefoodsub.php

```app/classes/model/usefoodsub.php
class Model_Usefoodsub extends \Orm\Model
{
```

* model/product.php

```app/classes/model/product.php
class Model_Product extends \Orm\Model
{
```

を作成してやります。

### ORMの設定

まず、モデル内のORMの設定として

* テーブル名
* テーブルのプライマリキー
* テープル内で使用するフィールド名

を指定してやります。
shopモデルを例に下記のように書きます。

```app/classes/model/shop.php
<?php
/**
 * ORM 店舗モデル
 */
class Model_Shop extends \Orm\Model
{
    /**
     * テーブル名
     * @var string
     */
    protected static $_table_name = 'shop';

    /**
     * テーブルのプライマリキー
     * @var array
     */
    protected static $_primary_key = array('id');

    /**
     * プロパティ
     * @var array
     */
    protected static $_properties = array(
        'id' => array(
            'data_type'  => 'int',
            'label'      => 'id',
            'validation' => array(
                'required',
                'valid_string' => array(array('numeric')),
            ),
            'form'      => array('type' => false),
        ),
        // 店舗エリアid
        'area_id' => array(
            'data_type'  => 'int',
            'label'      => 'area_id',
            'validation' => array(
                'required',
                'valid_string' => array(array('numeric')),
            ),
            'form'      => array('type' => false),
        ),
        // 作成日時
        'created_at'          => array(
            'data_type' => 'datetime',
            'label'     => 'created_at',
            'form'      => array('type' => false),
        ),
        // 更新日時
        'updated_at'          => array(
            'data_type' => 'datetime',
            'label'     => 'updated_at',
            'form'      => array('type' => false),
        ),
    );
```
validation, form については、フォームを自動生成する際に使用するオプションになります。（今回は説明を割愛）

### リレーションの設定

これから行うリレーションの設定とは、
**モデル同士の関連付けを行うことで、指定されたテーブルに関連するテーブルのデータを取得できるようになる**というものです。
先の工程で、use_food_sub テーブルからデータを取得するために、まず各モデルの作成、モデル内でのORMの設定を終えました。
そしてここからはそれぞれのモデル同士を関連付けさせるための作業になります。

今回やりたいこととして use_food_sub テーブルからデータ取得するのに、shop テーブルからデータを参照していきます。
use_food テーブルの shop_id には、shop テーブルのプライマリキーである id が入ります。
つまり

* shop.id = use_food.shop_id

という関係性になります。
このshopテーブルとuse_foodテーブルの関係性は「1対多」というものになります。
このテーブルのモデル間の関連性について、FuelPHPではこれらの関連性の定義付けをする関数が用意されています。

```php
$_has_many //1対多
$_belongs_to　// has many または has one の反対側の定義
$_has_one // 1対1
$_many_many // 多対多
```
この中の `$_has_many` と `$_belongs_to` を使ってモデルの関連付けを行います。
それぞれのモデルはこんな感じになります。

```app/classes/model/shop.php
<?php
/**
 * ORM 店舗モデル
 */
class Model_Shop extends \Orm\Model
{
    /**
     * テーブル名
     * @var string
     */
    protected static $_table_name = 'shop';

    /**
     * テーブルのプライマリキー
     * @var array
     */
    protected static $_primary_key = array('id');

    /**
     * プロパティ
     * @var array
     */
    protected static $_properties = array(
        'id' => array(
            ...上にあるので省略...
        ),
        // 店舗エリアid
        'area_id' => array(
            ...省略...
        ),
        // 作成日時
        'created_at'          => array(
            ...省略...
        ),
        // 更新日時
        'updated_at'          => array(
            ...省略...
        ),
    );

   /**
     * Has Many
     * @var array
     */
    protected static $_has_many = array(
        // リレーション指定する際の関係性を示す名前を付ける
        'use_food' => array(
            // 紐づけるモデル  
            'model_to'       => 'Model_Usefood',
            // このモデル（店舗）のキー
            // 反対側のモデルとの結合条件となる項目を示す
            'key_from'       => 'id',
            // 関連するモデルでのキー
            // 反対側のモデルで結合条件となる項目を示す
            'key_to'         => 'shop_id',
            // 関係するテーブルが保存されるときに同時にアップデートするか
            'cascade_save'   => false,
            // 親テーブルの関連レコードが削除されるときに同時に削除するか
            'cascade_delete' => false,
        ),
    );
```

```app/classes/model/usefood.php
<?php
/**
 * ORM 取り扱い食材モデル
 */
class Model_Usefood extends \Orm\Model
{
    /**
     * テーブル名
     * @var string
     */
    protected static $_table_name = 'use_food';

    /**
     * テーブルのプライマリキー
     * @var array
     */
    protected static $_primary_key = array('id');

    /**
     * プロパティ
     * @var array
     */
    protected static $_properties = array(
        'id' => array(
            ...省略...
        ),
        // 店舗id
        'shop_id' => array(
            ...
        ),
        // 食材id
        'product_id' => array(
            ...
        ),
        // 食材名
        'name' => array(
            ...
        ),
        // 作成日時
        'created_at'          => array(
            ...
        ),
        // 更新日時
        'updated_at'          => array(
            ...
        ),
    );

    /**
     * Belongs To.
     *
     * @var array
     */
    protected static $_belongs_to = array(
        // リレーションの関係性を示す名前を指定
        'use_food' => array(
            // 紐付けられるモデル  :店舗
            'model_to' => 'Model_Shop',
            // このモデルのキー  :店舗id
            'key_from' => 'shop_id',
            // 関連するモデルでのキー  :店舗モデルのid
            'key_to' => 'id',
            // 関係するテーブルが保存されるときに同時にアップデートするか
            'cascade_save' => false,
            // 親テーブルの関連レコードが削除されるときに同時に削除するか
            'cascade_delete' => false,
        ),
    );

```
これで店舗モデルと取り扱い食材モデルのリレーション設定ができました。

* 店舗モデル shop 側からは取り扱い食材モデル use_food を紐付けしますよという指定を `$_has_many` で行い
* 取り扱い食材モデル use_food 側からは店舗モデル shop が紐付けられてますよという指定を `$_belongs_to` で行っているわけです。

### データを取得

* Controllerなり何なりからモデルを呼んで実行してみます

```app/classes/comtroller/shop.php
class Controller_Shop extends Controller
{
  /**
   * コントローラ
   * @return [type] [description]
   */
  public function action_index()
  {
    $param = Input::get();
    // データ取得
    $shop_data = Model_Shop::find('first',  array(
      'related' => array(
        // リレーションの設定名を指定
        'use_food'
      ),
      'where' => array(
        array('id', $param['id'])
      )
     )
    );
  }
}
```
relatedの配列ではリレーションの設定を、 whereの配列ではSQLでのwhere句の指定にあたる絞り込みの条件を設定します。
これで実行してみるとObject型のデータが取得できます！
基本的なリレーションの設定からのデータ取得は以上の方法でできます。

## 更にリレーション設定をしてデータ取得してみよう

>今回やりたいこととして use_food_sub テーブルからデータ取得するのに、

ここまででひとまずリレーション設定＆データ取得はできましたが
use_food_subから最終的にデータを取ってきたいので、もうちょいがっつりリレーションを設定してみます。
解説を入れながら設定例を書きます。

店舗モデルは上で設定した通りなので省きます

**use_food**

```app/classes/model/usefood.php
<?php
/**
 * ORM 取り扱い食材モデル
 */
class Model_Usefood extends \Orm\Model
{
    /**
     * テーブル名
     * @var string
     */
    protected static $_table_name = 'use_food';

    /**
     * テーブルのプライマリキー
     * @var array
     */
    protected static $_primary_key = array('id');

    /**
     * プロパティ
     * @var array
     */
    protected static $_properties = array(
        'id' => array(
            ...省略...
        ),
        // 店舗id
        'shop_id' => array(
            ...
        ),
        // 食材id
        'product_id' => array(
            ...
        ),
        // 食材名
        'name' => array(
            ...
        ),
        // 作成日時
        'created_at'          => array(
            ...
        ),
        // 更新日時
        'updated_at'          => array(
            ...
        ),
    );

    /**
     * Belongs To.
     *
     * @var array
     */
    protected static $_belongs_to = array(
        // リレーションの関係性を示す名前を指定
        'use_food' => array(
            // 紐付けられるモデル  :店舗
            'model_to' => 'Model_Shop',
            // このモデルのキー  :店舗id
            'key_from' => 'shop_id',
            // 関連するモデルでのキー  :店舗モデルのid
            'key_to' => 'id',
            // 関係するテーブルが保存されるときに同時にアップデートするか
            'cascade_save' => false,
            // 親テーブルの関連レコードが削除されるときに同時に削除するか
            'cascade_delete' => false,
        ),
    );

    /**
     * Has Many.
     *
     * @var array
     */
    protected static $_has_many = array(
        'use_food_sub' => array(
            // 紐づけるモデル  :取り扱い食材サブ
            'model_to' => 'Model_Usefoodsub',
            // このモデルのキー  :取り扱い食材モデルのid
            'key_from' => 'id',
            // 関連するモデルでのキー　:取り扱い食材サブモデルの食材コンテンツid
            'key_to' => 'food_content_id',
            'cascade_save' => false,
            'cascade_delete' => false,
        )
    );

    /**
     * Has One.
     * @var array
     */
    protected static $_has_one = array(
        // 商材テーブルからデータ取得するためのリレーションを設定
        'use_food_product' => array(
            // このモデルのキー :商材id
            'key_from' => 'product_id',
            // 関連するモデル  :商材
            'model_to' => 'Model_Product',
            // 関連するモデルでのキー :id
            'key_to' => 'id',
            'cascade_save' => false,
            'cascade_delete' => false,
        )
    );

```

 `$_has_one` を使って「1対1」の関係を取り扱い食材モデル⇔商材モデル間で設定します。
取り扱い食材モデルでは

 * 店舗モデル shop から紐付けられていること
 * 取り扱い食材モデル use_food との紐付け
 * 商材モデル product との紐付け
 
を記述します。

**use_food_sub**

```app/classes/model/usefoodsub.php
<?php
/**
 * ORM 取り扱い食材サブモデル
 */
class Model_Usefoodsub extends \Orm\Model
{
    /**
     * テーブル名
     * @var string
     */
    protected static $_table_name = 'use_food_sub';

    /**
     * テーブルのプライマリキー
     * @var array
     */
    protected static $_primary_key = array('id');

    /**
     * プロパティ
     * @var array
     */
    protected static $_properties = array(
        'id' => array(
            ...
        ),
        // 食材コンテンツid
        'food_content_id' => array(
            ...
        ),
        // 商材id
        'product_id' => array(
            ...
        ),
        // 作成日時
        'created_at'          => array(
            ...
        ),
        // 更新日時
        'updated_at'          => array(
            ...
        ),
    );

    /**
     * Belongs To.
     *
     * @var array
     */
    protected static $_belongs_to = array(
        'use_food_sub' => array(
            // 紐付けられるモデル  :取り扱い食材モデル
            'model_to' => 'Model_Usefood',
            // このモデルのキー  :取り扱い食材サブモデルの食材コンテンツid
            'key_from' => 'food_content_id',
            // 関連するモデルでのキー  :id
            'key_to' => 'id',
            'cascade_save' => false,
            'cascade_delete' => false,
        ),
    );

    /**
     * Has One.
     *
     * @var array
     */
    protected static $_has_one = array(
        // 商材テーブルからデータ取得するためのリレーションを設定
        'use_food_sub_product' => array(
            // このモデルのキー :商材id
            'key_from' => 'product_id',
            // 関連するモデル  :商材
            'model_to' => 'Model_Product',
            // 関連するモデルでのキー :id
            'key_to' => 'id',
            'cascade_save' => false,
            'cascade_delete' => false,
        )
    );

```

取り扱い食材サブモデルでは、取り扱い食材モデルと同じく`$_has_one` を使って「1対1」の関係を設定します。

 * 取り扱い食材モデル use_food から紐付けられていること
 * 商材モデル product との紐付け
 
を記述します。


**product**

```app/classes/model/product.php
<?php
/**
 * ORM 商材モデル
 */
class Model_Product extends \Orm\Model
{
    /**
     * テーブル名
     * @var string
     */
    protected static $_table_name = 'product';

    /**
     * テーブルのプライマリキー
     * @var array
     */
    protected static $_primary_key = array('id');

    /**
     * プロパティ
     * @var array
     */
    protected static $_properties = array(
        'id' => array(
            ...
        ),
        // 商材識別id
        'code' => array(
            ...
        ),
        // 作成日時
        'created_at'          => array(
            ...
        ),
        // 更新日時
        'updated_at'          => array(
            ...
        ),
    );

    /**
     * Belongs To.
     *
     * @var array
     */
    protected static $_belongs_to = array(
        // 取り扱い食材モデルからのリレーションの設定名　
        'use_food_product' => array(
            // 紐付けられるモデル  :取り扱い食材モデル
            'model_to' => 'Model_Usefood',
            // このモデルのキー  :商材id
            'key_from' => 'id',
            // 関連するモデルでのキー  :取り扱い食材モデルの商材id
            'key_to' => 'product_id',
            'cascade_save' => false,
            'cascade_delete' => false,
        ),
        // 取り扱い食材サブモデルからのリレーションの設定名　
        'use_food_sub_product' => array(
            // 紐付けられるモデル  :取り扱い食材サブモデル
            'model_to' => 'Model_Usefoodsub',
            // このモデルのキー  :商材id
            'key_from' => 'id',
            // 関連するモデルでのキー  :取り扱い食材サブモデルの商材id
            'key_to' => 'product_id',
            'cascade_save' => false,
            'cascade_delete' => false,
        ),
    );

```

商材モデルでは取り扱い食材モデル・取り扱い食材サブモデルからの`$_belongs_to`の関係のみを記述します。
これでuse_food_subテーブルからデータ取得する準備は完了です。

はてさて呼ぶ側はどうなったかというと

```app/classes/comtroller/shop.php
        // リレーション、where条件設定
        $orm_settings = array(
            'related' => array(
                // 店舗モデルからのリレーション  to取り扱い食材
                'use_food' => array(
                    'related' => array(
                        // 取り扱い食材モデルからのリレーション
                        // to取り扱い食材サブ
                        'use_food_sub' => array(
                            'related' => array(
                                // 取り扱い食材サブからのリレーション  to商材
                               'use_food_sub_product',
                           ),
                           // order by も指定可
                            'order_by' => array(),
                        ),
                        // 取り扱い食材モデルからのリレーション　to商材
                        'use_food_product',
                     ),
                     // order by も指定可
                      'order_by' => array(),
                  ),
              ),
             // where句
             'where' => array(
                 array('id', $param['id']),
             ),
         );

         // データ取得
         $shop_data = Model_Shop::find('first', $orm_settings);
            
```

改行入れると非常にネストが深いですがこんな感じになります。
order_byやwhereを指定したい場合、指定するカラムに対応するモデルの
リレーション設定と同じ階層で指定できます。
リレーションしたいモデルからは更にこのモデルが別のリレーション設定で紐付いているので、それをひとつひとつ追って指定していき、データを取ってくるという流れですね。
見にくいので設定部分を一旦変数に受け、後でfindで指定しています。

以上で今回のデータ取得の目的は達成しました。
今回のテーブル設計例を即席で作った為、わかりづらい部分もありますが
どんな構造であれリレーションを使用することで難なくデータ取得できるのは良いですね。
正直SQLをガッチリ書きたい派ですが。。

`$_many_many`も使いそこねたのでどこかで。
FuelPHPerの助けになれば幸いです。