cakephp + UNION + paginator + sort

訳あって cakephp で UNION を使うことになったんだが。。。

cakephp customな pagenateで $orderが emptyになってしまう件


どうもcakephpにはUNIONはないようで、->query()をおこなうことに。

(SELECT --- FROM --- WHERE ---)
UNION
(SELECT --- FROM --- WHERE ---)

な感じで。
UNIONを使う場合、どうやらpublic $useTable = false;にするらしいので、わかりやすく別モデルを用意。

まあ、これで済むものならいいんだけど、paginateしたい。
となると、自分でごりっと実装するか、paginatorのコンポーネントを利用するかになるんだけど、できれば標準のpaginatorでなんとかしたい。

paginatorの準備

まあ、このpaginatorについては使える記事がたくさん出てくるから、労はなかった。
良い記事書いてる人tks。

英語
http://blog.andolasoft.com/2014/08/how-to-do-custom-pagination-in-cakephp.html
日本語
http://d.hatena.ne.jp/sutara_lumpur/20120825/1345885278
日本語
http://u2k772.blog95.fc2.com/blog-entry-332.html


ORDER そして SORT

nで、詰まったのが、ORDERまわりだった。。。

単純に
$query = array (
  'order' =>  'hoga DESC' ,
  'limit' => 50,
  'extra' => array (
    'type' => (ここにクエリ突っ込むやり方が単純にわかりやすかった。)
  )
);
$this->Paginator->settings = $query;

これなら、とりあえずorderは効くんだけど、viewで$this->Paginator->sort ()しても上手いこといかないんだな。


単純にORDERするだけなら、parameな感じで指定するより生クエリーに書いてしまえばいいんだけど、$this->Paginator->sort ()を使いたいんだな。

普通にpaginateを使う場合には特に意識することもなかったんだけど、今回はそもそも、'order'=> array( 'hoga' => 'asc' )みたいな感じで渡しても、overrideしたfunction paginateには空のarray()がわたってくるんだな。
 'order' =>  'hoga DESC' なら大丈夫ってなんでだよ。。。

PaginatorComponentを見たらわかった

だらだら書いてごめんね。ちょっと愚痴りたかった気分だ。

cake/lib/Cake/Controller/Component/PaginatorComponent.phpの400行目あたりの箇所。

public function validateSort(Model $object, array $options, array $whitelist = array()) {
~~略~~

if ($correctAlias && $object->hasField($field)) { $order[$object->alias . '.' . $field] = $value; } elseif ($correctAlias && $object->hasField($key, true)) { $order[$field] = $value; } elseif (isset($object->{$alias}) && $object->{$alias}->hasField($field, true)) { $order[$alias . '.' . $field] = $value; }

~~略~~
}

hasField($field)ではじかれるね。

ググれかすしたらここがヒット。猫が可愛い。。。
http://blog.livedoor.jp/hal_can/archives/52346896.html

なわけで、とりあえず突っ込んだら、


function hasField($name, $checkVirtual = false) {
return true;
}



動いてなーーーーーい。

なぜだ?


UNIONを使うからややこしくなってる(疲)

public $useTable = false;
にしてるから当然 aliasはnullだね。

という訳で、書き直し。



UNIONを使うモデルを切り出して用意。

App::uses ( 'AppModel', 'Model' );
App::uses ( 'Fuge', 'Model' ); //UNIONに使うモデル


class Hoga extends AppModel {


  public $useTable = false;
  //alias名はなんでもいい。無くてもいいけどNULLは気持ち悪いので念のため。
  public $alias = 'KABARANAINAMAE';
  public $virtualFields = array (
//AS でsortするなら virtualFields を設定
);


  //hasFieldをoverride
  function hasField($name, $checkVirtual = false) {
    /*
    *  実際に使用するモデルのfieldを取得して
    *  columnがあるかチェックする
    */

    $Fuge = new Fuge(); //使用するモデル
    $hasFields = array_keys ( $Fuge->getColumnTypes () ); //field取得
    if (in_array ( $name, $hasFields )) {
      return true;
    }
    if ($checkVirtual && !empty($this->virtualFields)) {
      if (array_key_exists ($name, $this->virtualFields )) {
        return true;
      }
    }
    //_schemaとかは別に必要ないから省く
    return false;
  }


  //paginateをoverride
  function paginate($conditions, $fields, $order, $limit, $page = 1, $recursive = null, $extra = array()) {

  /*
  *  $orderのarray()には 『 'KABARANAINAMAE.sort_key' => 'DESC'』
  *  みたいな感じで来る
  */

    $orderStr = '';
    if (! empty ( $order )) {
      foreach ( $order as $k => $ord ) {
        //'KABARANAINAMAE.'を削除しないとUNIONをしている関係でエラーになる
        $k = str_replace ( $this->alias . '.', '', $k );
        $orderStr [] = $k . ' ' . $ord;
      }
      $orderStr = 'ORDER BY ' . implode ( ', ', $orderStr );
    }
    $sql = $extra ['extra'] ['type'];
    $sql .= $orderStr;
    $sql .= ' LIMIT ' . $limit;
    if ($page > 1) {
      $sql .= ' OFFSET ' . ($limit * ($page - 1));
    }
    return $this->query ( $sql );
  }


  //paginateCountをoverride
  function paginateCount($conditions = null, $recursive = 0, $extra = array()) {
    return count ( $this->query ( preg_replace ( '/LIMIT \d+ OFFSET \d+$/u', '', $extra ['extra'] ['type'] ) ) );
  }
}



先人の試行錯誤をもとに、小細工加えて無事動きました。
感謝感謝。


それにしても・・・コードが不細工だな。。。

コメント

このブログの人気の投稿

eclipse 改行後のインデントを無効にする

Jquery datetimepicker 日本語にならない原因

サービスエラー:Spreadsheets(service error:Spreadsheets)の原因