🍰

CakePHPの自作ControllerクラスのソースにはXdebugのブレークポイントが効かない?

2020/09/20に公開

NetBeans 7.4 や Xdebug 2.2.3(共に 2013 年 10 月 27 日最新版)の組み合わせで CakePHP のデバッグを進めている際、IDE でブレークポイントを設定しても全く効かないという現象が起こりました。

すぐに検索して出てくる解決法は「プロジェクトファイルの実行構成を"app/webroot/index.php"に設定しろ」というものですが、どうも今回はそういう類の問題でも無さそうです。

現象

  • 自作 Controller クラスでは、ブレークポイントを設定してもことごとく無視されてしまう。しかし処理動作は正常である。
  • 自作 Model クラスでは、問題なく期待のブレークを起こす。

解決法

  • 無し?(おそらく CakePHP2.4.2 現在の仕様)
  • Controller クラスにステップインでの検証が必要になるほど複雑なロジックを書かない。
  • どんどん Model に割り振る。
  • あるにはあるけど…。(13/10/28 追記)

濃い話

解決法無しというのは個人的に解析してみた上での所感なのですが、ちょっと仕様っぽいのでどうしようもないなと思っています。

なぜ無しと分かったかというとコアな処理の話になるのですが、CakePHP は Dispatcher や CakeEventManager というクラスが複雑に絡み合っているようで、特に CakeEventManager のdispatch()メソッドがとても重要な仕事をしています。

public function dispatch($event) {
		/* 省略 */
		foreach ($this->listeners($event->name()) as $listener) {
			if ($event->isStopped()) {
				break;
			}
			if ($listener['passParams'] === true) {
				$result = call_user_func_array($listener['callable'], $event->data);
			} else {
				$result = call_user_func($listener['callable'], $event);
			}
			if ($result === false) {
				$event->stopPropagation();
			}
			if ($result !== null) {
				$event->result = $result;
			}
			continue;
		}
	}

これはlib/Cake/Event/CakeEventManager.phpの 240 行目付近の引用ですが、CakePHP では$this->listenersにズラーっと準備されたイベント(メソッド)を foreach で回しながらcall_user_func($listener['callable'], $event)で順に実行しています(らしいです)。このときメソッドによってはソースファイルに飛ぶので call_user_func()にステップインさえすれば 以降はブレークが効くのですが、自作 Controller クラスのメソッドだけは何故かどうしても飛べないのです。

ここでさらに var_dump などを並べて処理順を詳しく見たところ、どうも Controller クラスの自前メソッドは Closure として実行されているのです。CakePHP 側が Closure オブジェクトに変換したのか、そもそもどういう処理でこうなっているのかまでは解析出来なかったのですが、結果として自前メソッドはソースファイルに飛ばずいきなり Closure として実行されています。

仮説ではありますが、この仕様が原因でブレークポイントが一切効かないという現象が起きるのではないでしょうか。コア部がこのように動いている以上考えられる対策は、Controller では 1 行デバッグで追う必要があるほど複雑なことをしない、引数渡しとレンダリング指示に留めておく、この辺りかと思います。

この処理の事情に詳しい方がおられましたら是非教えてください。
以上です。

追記 頑張ればできる、かも

(13/10/28 追記)CakePHP のテストケースクラスにテストを書いて、そのテスト内にブレークポイントを置いてからステップアウトして辿って行くとコントローラー内にステップが進むことがあります。

ただ、自作モデルの自作メソッドから登らないと入れないので、コントローラー自作メソッドに既存の API しか使っていない場合は入れません。再現性も確実とは言い難く、ステップが進むか進まないかには説明のつかない不安定さを感じます。

狙ってコントローラー内にブレークポイントを置いても無視される件は上記の通りなので、あまり積極的に使えないのですが、可能性がゼロというわけでは無さそうでした。

Discussion