Cake の controller で __call 的な感じの処理を

最近は不本意ながら時代の流れに逆らえずに PHP の仕事なんかをしてる。
で、よりにもよって (PHP で無理やり Ruby するような感じとか言われてる) Cake だ。
まあしがない派遣労働者だから選り好みとかしてらんない(って前職のときも PHP だったか)。


まあどうでもいいのだが、弄らされている既存ソースに「ほとんど同じなのにちょっとだけ違う処理がある Controller を、まるまる二つコピペして作ってある」というダメな典型みたいなのがあったので統合しようと思った。
本来ならモデルに処理を書いて、コントローラーにはそれを使う処理を……というのが王道の解決方法っぽいのだが、ちゃんとしている時間はないので無理矢理別のコントローラーのインスタンスを生成して委譲しちゃって誤魔化す事にした。


で、ごまかしてみた結果、そもそも何一つ違わないメソッドとかがほとんどじゃん!!という衝撃の事実に。
もうこうなったらできるだけ差異以外は書かない方針にしようと思ってマジックメソッド __call を定義してみた。。。のだが動かん。
よく見たら controller のメソッドはリフレクションで呼ばれてた。。。
仕方がないのでちょっと Cake 側のコードも弄ることになるのだけど、 __call みたいな処理を追加することにした。


というわけで長い前書き終了。
サンプルコードで、ついでに無理やりコントローラー生成して委譲するところも
Cake の一部である Controller への変更部分

class Controller extends Object {
	# 前略
	public $hasMissingActionHandler = false;
	# 中略
	public function invokeAction(CakeRequest $request) {
		try {
			$method = new ReflectionMethod($this, $request->params['action']);

			if ($this->_isPrivateAction($method, $request)) {
				throw new PrivateActionException(array(
					'controller' => $this->name . "Controller",
					'action' => $request->params['action']
				));
			}
			return $method->invokeArgs($this, $request->params['pass']);

		} catch (ReflectionException $e) {
			if ($this->scaffold !== false) {
				return $this->_getScaffold($request);
			}
			
			# このあたりから追加
			if ($this->hasMissingActionHandler) {
				$actionName = $request->params['action'];
				return $this->handleMissingAction($actionName);
			}
			# このあたりまで追加
			
			throw new MissingActionException(array(
				'controller' => $this->name . "Controller",
				'action' => $request->params['action']
			));
		}
	}
	# 後略
}

それを使ってる部分

class YarukinonaiController extends AppController {
	private $ijouSakiController;
	
	public function __construct() {
		$this->hasMissingActionHandler = true;
		call_user_func_array(array('parent', '__construct'), func_get_args());
		
		App::import('Controller', 'IjouSaki');
		$dummyResponse = new CakeResponse();
		$this->ijouSakiController = new StoryController($this->request, $dummyResponse);
		$this->ijouSakiController->constructClasses();
	}
	
	public function handleMissingAction($actionName) {
		if (!method_exists($this->ijouSakiController, $actionName)) {
			throw new MissingActionException(array('controller' => $this->name . "Controller", 'action' => $actionName));
		}
		
		$this->ijouSakiController->{$actionName}();
		foreach ($this->ijouSakiController->viewVars as $key => $value) {
			$this->set($key, $value);
		}
		
		$this->render('../IjouSaki/'. Inflector::underscore($actionName));
	}
}

で、ここまでぐにゃぐにゃやった結果、「やっぱこれ別のコントローラにせずに一個に統合しよう」と思い、作業が無駄になったのでせめてこの日記でさらす事にした。同じような処理だけど URL を別にしたい場合とかで流用できるかもしれない(いや、多分そういう場合はハンドラのルーティング部分を弄ったほうがいいからないか)。