CakePHPで独自の共通コントローラを継承させて使用する
CakePHPさんはControllerを作成する場合は基本的にAppControllerを継承させて作成するわけですが、AppConroller→他の共通のController→HogeControllerみたいに1つ別の共通コントローラを継承させたいってときがあって、かなーりはまったのでメモ。
ただ、激しくバッドノウハウ。
で、何にはまったかというと、AppControllerに共通のコンポーネントやヘルパーを書いて継承した場合は、HogeControllerに書いたコンポーネント、ヘルパーとかはちゃんとマージしてくれるんですが、自分で作ったコントローラを継承させた場合は親のコンポーネントやヘルパーを上書きしてくれるという素敵仕様だった。ひどくね?
というわけでControllerのソースを見に行ったら__mergeVars()で、AppControllerの内容をうまくマージさせてるっぽかったので、こいつを少し改造しました。
具体的には下記のような感じに。
まず、AppControllerを作る
<?php class AppController extends Controller{ var $margeClassName = null; function __mergeVars() { if($this->margeClassName){ $pluginName = Inflector::camelize($this->plugin); $pluginController = $pluginName . $this->margeClassName; if (is_subclass_of($this, $this->margeClassName) || is_subclass_of($this, $pluginController)) { $appVars = get_class_vars($this->margeClassName); $uses = $appVars['uses']; $merge = array('components', 'helpers'); $plugin = null; if (!empty($this->plugin)) { $plugin = $pluginName . '.'; if (!is_subclass_of($this, $pluginController)) { $pluginController = null; } } else { $pluginController = null; } if ($uses == $this->uses && !empty($this->uses)) { if (!in_array($plugin . $this->modelClass, $this->uses)) { array_unshift($this->uses, $plugin . $this->modelClass); } elseif ($this->uses[0] !== $plugin . $this->modelClass) { $this->uses = array_flip($this->uses); unset($this->uses[$plugin . $this->modelClass]); $this->uses = array_flip($this->uses); array_unshift($this->uses, $plugin . $this->modelClass); } } elseif ($this->uses !== null || $this->uses !== false) { $merge[] = 'uses'; } foreach ($merge as $var) { if (!empty($appVars[$var]) && is_array($this->{$var})) { if ($var !== 'uses') { $normal = Set::normalize($this->{$var}); $app = Set::normalize($appVars[$var]); if ($app !== $normal) { $this->{$var} = Set::merge($app, $normal); } } else { $this->{$var} = array_merge($this->{$var}, array_diff($appVars[$var], $this->{$var})); } } } } if ($pluginController && $pluginName != null) { $appVars = get_class_vars($pluginController); $uses = $appVars['uses']; $merge = array('components', 'helpers'); if ($this->uses !== null || $this->uses !== false) { $merge[] = 'uses'; } foreach ($merge as $var) { if (isset($appVars[$var]) && !empty($appVars[$var]) && is_array($this->{$var})) { if ($var !== 'uses') { $normal = Set::normalize($this->{$var}); $app = Set::normalize($appVars[$var]); if ($app !== $normal) { $this->{$var} = Set::merge($app, $normal); } } else { $this->{$var} = array_merge($this->{$var}, array_diff($appVars[$var], $this->{$var})); } } } } } parent::__mergeVars(); } }
$margeClassNameっていうプロパティを追加してやる。
そして__margeVarsをまるっとコピペして'AppController'って部分を$this->$margeClassNameに変えて、最後にスーパークラスの同じメソッドを読んでるだけ。
で、共通のControllerで$margeClassNameを設定してやる
<?php class CommonAppController extends AppController{ var $margeClassName = "CommonAppController "; var $components = array("共通のコンポーネント"); }
後はコントローラを実装するときにCommonAppControllerを普通に継承してやればよい。
<?php App::import("Controller" , "CommonApp"); class HogeController extends CommonAppController { var $components = array("ここで使うコンポーネント"); }
これでCommonAppController,AppController,HogeControllerのコンポーネントをうまくマージしてくれました。
けど、__margeVarsをまるっとコピペしてあるあたりアレ。
それと当然だけど、多段継承をさらにすると上書いてくれます。\(^o^)/
まぁ、コンポーネント、ヘルパー、モデルあたりをマージするだけだったら、コンストラクタ読んでるところでマージしてあげるだけでもいいっぽいんだけど。
でももっとうまいやり方ありそうだよなあ。ググってもそれらしいのが見つからなかった!もっとエレガントな方法教えてぷりいいいいず。