Cakephp2でHashクラスのマッチャーのたてかた(使用例もあるよ)

レファレンスを見ても最初なんのこっちゃピンとこなかった記憶があるので。

Cakephp2ではモデルからデータを読み込むと配列で返ってくる。
その配列をいい感じに整形したい時にHashクラスを使うとforeachのネストからおさらばできるかもしれない。
特にアソシエーションして引っ張ってくる時に有効です。

参考 
https://book.cakephp.org/2.0/ja/core-utility-libraries/hash.html

基本

式とマッチャーで同定する。
式がキー判定でマッチャーがバリュー判定。

{n} なんか数字のキー。0,1,2,3,...に当てはまる
{s} なんか文字のキー。'id','name','status',...に当てはまる
{*} 全部のキー
Foo 完全に同じ時。{n}.Userだと[0=>['User'=>[...], 1=>['User'=>[...]]が返る

マッチャー

[id] idのキーが存在してたら返す
[id=2] idが2だったら返す
[id!=2] idが2じゃなかったら返す
[id>2] idが2より大きかったら返す
[id>=2] idが2以上だったら返す
[id<2] idが2より小さかったら返す
[id<=2] idが2以下だったら返す
text=/.../ 正規表現...に引っかかったら返す

式はすべてのメソッドで使えるけれど、マッチャは使えるメソッドが限られているので注意

使えるもの

extract(), combine(), format(), check(), map(), reduce(), apply(), sort(), insert(), remove(), nest()

使用例

<?php
// 対象になる配列
$a = array(
    array(
        'User' => array(
            'id' => 2,
            'group_id' => 1,
            'Data' => array(
                'user' => 'mariano.iglesias',
                'name' => 'Mariano Iglesias'
            )
        )
    ),
    array(
        'User' => array(
            'id' => 14,
            'group_id' => 2,
            'Data' => array(
                'user' => 'phpnut',
                'name' => 'Larry E. Masters'
            )
        )
    ),
    array(
        'User' => array(
            'id' => 34,
            'group_id' => 2,
            'Data' => array(
                'user' => 'unijerry',
                'name' => 'David Pretter'
            )
        )
    ),
);

$result = Hash::extract($a, {n}.User);
// $aと同じ

$result = Hash::extract($a, {n}.User.id);
// [2, 14]

$result = Hash::extract($a, {n}.User[group_id=1]);
/**
 array(
            'id' => 2,
            'group_id' => 1,
            'Data' => array(
                'user' => 'mariano.iglesias',
                'name' => 'Mariano Iglesias'
            )
        )
**/

$result = Hash::extract($a, {n}.User[id>10].Data[user=phpnut].name);
// ['Larry E. Masters']

ポイント

  • マッチャは式に使う(要素の条件[マッチャ]でキー[式]の要素を絞り込む)
  • マッチャで絞り込んだ後に式を使って絞り込める。

おまけ findとconditionで書きかえてみる

<?php
// 前提条件 上の$aを再定義する
$this->User->bindModel(['hasOne'=>['Data'=>['conditions'=>['User.id = Data.user_id']]]]);

$options=>[
  fields=>['Users.id','Users.group_id','Data.user','Data.name'],
  conditions=>['Users.id'=>[2,14,34]]
];

$a = $this->User->find('all', $options);

この$aが前述の例の配列になるとすると、例にあげた$resultをモデルから直接抽出する$optionsは次のようになる。

// $result = Hash::extract($a, {n}.User);
$options=>[
  fields=>['Users.id','Users.group_id','Data.user','Data.name'],
  conditions=>['Users.id'=>[2,14,34]]
];

// $result = Hash::extract($a, {n}.User.id);
$options=>[
  fields=>['Users.id'],
  conditions=>['Users.id'=>[2,14,34]]
];

// $result = Hash::extract($a, {n}.User[group_id=1]);
$options=>[
  fields=>['Users.id','Users.group_id','Data.user','Data.name'],
  conditions=>[
    'Users.id'=>[2,14,34],
    'Users.group_id'=>1
  ]
];

// $result = Hash::extract($a, {n}.User[id>10].Data[user=phpnut].name);
$options=>[
  fields=>['Data.name'],
  conditions=>[
    'Users.id'=>[2,14,34],
    'Users.id >'=>10,
    'Data.user'=>'phpnut'
  ]
];

余談2 conditions(SQLのWHERE句)とアソシエーション

hasOneとbelongsToのアソシエーションで発行されるSQLはLEFT JOINなので(デフォルトのon句が違うだけ)、conditions(=where句)でそのまま条件を指定しても問題ない.

hasManyだとSQL上ではJOINしないためconditionsに書くとカラムが見つからずエラーとなる。
※最初のテーブルでSELECTして得たidを元にアソシエーションテーブルでSQLを回すといった手順を踏む
そのためhasManyで条件指定する場合はhasManyしつつjoinやbelongsToを同時に行ったりして絞り込む必要があるが気が向いたら記事にします。

上記の例ではhasOneなので問題無し。