ラベル cakephp の投稿を表示しています。 すべての投稿を表示
ラベル cakephp の投稿を表示しています。 すべての投稿を表示

2014年10月4日土曜日

CakePHP で独自の親クラスを作成する場合の注意点

CakePHP で、全てのコントローラに共通な内容であれば AppController に記述すれば良いけれど、
全てのコントローラに共通する内容で無かったり、ある特定のドメインや機能で共通化を図りたい場合、どう解決するか?
まず思いつくのが AppController と各コントローラの間に独自親コントローラ (以下、DomainController) を作成する方法。
CakePHP でコントローラを作成する場合、AppController を継承して作成するが、
AppController を継承した DomainController を継承してコントローラを作成するようにする。

AppController
全てのコントローラに共通するプロパティやメソッドを定義。

DomainController
ある特定のドメインだけに共通するプロパティやメソッドを定義。

各コントローラ(以下、MyController)
自身に必要なロジックだけを記述。

継承関係が適していればそうしたいけれど、この方法だと DomainController で定義した components, uses といったプロパティが自動マージされず、 DomainController を継承している MyController で参照出来ない。
AppController を直接継承している MyController では、これらプロパティは CakePHP が自動でマージしてくれるて参照出来るのに。。。
DomainController でも共通化ロジックを書くために components, uses といったプロパティを定義する必要があるので、自動マージしてくれないとせっかく共通化したのに MyController で同じ記述しないといけない(全てじゃないにしろ)。。。

何故か?
原因を探ろう。

自動マージされず参照出来ない理由は AppController の親クラス Controller の $_mergeParent にあります。
Controller クラスをソースコード読めば、AppController を直接継承している MyController では自動マージが行われるのかが逆に分かります。
ちなみに Controller::$_mergeParent の値を AppController から DomainController に変えると、今度は AppController で定義している uses などが自動マージされなくなって参照出来なくなる(当たり前)。

ちょっと前置きが長くなったけれど、これを解決する方法は調べると複数あったけれど、
自分は下記のようなアプローチでこの問題を解決するようにした。
class DomainController extends AppController {

    /**
     * コンストラクタ
     * 
     * @see Controller::__construct()
     */
    public function __construct($request = null, $response = null) {

        // uses, components をマージ

        $this->uses = am($this->uses, array(
            'User',
            'Label',
         ));

         $this->components = am($this->components, array(
            'Me.AuthTest',
         ));

    }

    // 以下、共通ロジックなど ...

}
class MyController extends DomainController {

    public $uses = array('Employee');

    public $components = array('Search.Prg');

    public function index() {

        debug($this->uses); // User, Label, Employee + AppController::uses
        debug($this->components); // AuthTest, Search + AppController::components

    }

}


[参考]
CakePHPで独自に定義した親クラスでのComponentとHelperのマージ
http://qiita.com/uda0922/items/e9501c46f5b4cd91106a
$componentsと$helpersと$uses — CakePHP Cookbook 2.x ドキュメント http://book.cakephp.org/2.0/ja/controllers.html#components-helpers-uses

2014年5月10日土曜日

CakePHP で複数のデータベースのテーブルを Join する

先日、複数のサブシステムを統合した社内システムを開発してリリースした。
裏話というか Tips になるけど、
開発に着手しはじめ、技術仕様について考えていた時、もっとも考え込んだのはデータベース。
複数のサブシステムのあるシステムなので、サブシステム毎にデータベースを分けて管理したいと思っていた。

共通化出来るエンティティ群と4つのサブシステム毎にあるエンティティ群をリンクする必要がある事が分かり、
リンクが必要な際はデータベースが異なるテーブル同士を JOIN して。。。
しかし CakePHP で複数データベースを取り扱った事が無く、
また CakePHP のモデル同士を繋ぐアソシエーションの設定や Model->find のオプションで、異なるデータベースのテーブル同士の JOIN が可能なのかも知らなかった。
Model->query で出来るのは分かるけど、非常に便利なアソシエーションや Model->find で出来ないと CakePHP の恩恵が半減するので調べてみたところ、Model->find でも異なるデータベースの JOIN が出来るのが分かったのが大きかった。

実際に実装に組み込んだものだが、例として共通テーブルの Shop と、サブシステムAの Report テーブルを JOIN してみる。
※実際はもっとリファクタリングして共通化したりしているけれど
class Shop extends AppModel {

    public $useDbConfig = 'commons';

}

class Report extends AppModel {

    public $useDbConfig = 'sub-a-systems';

    public $belongsTo = array(
        'Shop' => array('type' => 'INNER'),
    );

}

class ReportsController extends AppController {

    public $uses = array('Report');

    public function index() {

        $model = $this->{$this->modelClass};

        $reports = $model->find('all', array(
            'fields' => array(
                'Report.*',
                'Shop.name AS shop_name',
            ),
            'joins' => array(
                array(
                    'type'       => 'INNER',
                    'tables'     => 'commons.shops',
                    'alias'      => 'Shop',
                    'conditions' => 'Report.shop_id = Shop.id'
                ),
            ),
            'recursive' => -1,
        );

    }

}
そう、ただ joins オプションの tables プロパティの値にテーブル名を入れれば出来るようでしたwww
もし同じような事を検討している人がいたならば、参考になれば幸いです。

[参考]
モデルの属性 — CakePHP Cookbook 2.x ドキュメント http://book.cakephp.org/2.0/ja/models/model-attributes.html