CakePHP 5.x で新しいアプリケーションを開発して、いざ本番環境に初めてdeployしたところ、以下のようなエラーに遭遇しました。
error: [Cake\ORM\Exception\MissingTableClassException] Table class for alias `{テーブル名}` could not be found
この記事は、このエラーの原因のうち、筆者が遭遇したものについてのメモです。
長いので3行まとめ
- Macのデフォルトのような case-insensitive なボリュームで開発されたコードは、case-sensitive 環境では動作しないことがある(PHPの auto_load が)
- CakePHPの
TableRegistry::getTableLocator()->get();
が本来想定しているのは、テーブル名ではなく、Tableクラスのクラス名から「Table」を取り除いた文字列(documentから読み取れる情報) - そんな文字列はバグの原因になるから渡したくねーよ、という私のような人は
FooBarTable::class
みたいな形で渡すと幸せになれる🥰
問題のコード
ControllerやCommand(CLIコマンド)のコードで、以下のコードが「ローカル開発環境では動作するけれども、本番ではエラーになるコード」でした。
$t = TableRegistry::getTableLocator()->get('{テーブル名}');
ここでは、 {テーブル名}
は、全て英小文字で構成された文字列です。
なぜ?
TableLocatorの get()
関数は、デフォルトでは vendor/cakephp/cakephp/src/ORM/Locator/TableLocator.php
のものが呼ばれます。
このget関数は親クラスである AbstractLocator.php
のものでキャッシュ管理されます。キャッシュヒットしなかった場合は、子クラスである TableLocator
の createInstance
関数が呼ばれて、Tableのインスタンスを作るという仕組みになっています。
なので、エラーの原因を探る時に読むべきは、子クラスの createInstance
ということになります。
以下は、CakePHP 5.0.5 における createInstance
のソースコードへのリンクです。
アプリを組む側としては、ここで指定するのは「テーブル名」であって「Tableクラスの名前」ではないような感覚*1に陥りますが、このソースコードを読む限り、テーブル定義を見に行っている様子はぱっと見では見受けられず、「Tableクラス」を検索しに行っているように見えます。
途中に出てくる _getClassName
関数 の呼び出しを追いかけていくと、最終的には class_exists
関数に辿り着きます。
これを見て、ふと疑問に思ったのは、「もしかして auto_load の環境では、この関数は case sensitive な動きをしたりしなかったりするのでは?」ということでした。
その旨、検索すると以下の StackOverflow のQ&Aに到達しました。ビンゴ!です。
auto load するクラスを探索するとき、ファイルシステムが case-insensitive だったら、クラス名の検索も case-insensitive になるっぽいです。
筆者は普段はきちんと大文字始まりの文字列を、ドキュメントの慣習に従って渡していましたが、意識としてはテーブル名という意識でした。今回は偶然にも誤って小文字で渡していました。
再発防止策
CakePHPに限った話ではありませんが、クラス名とか関数名とかを文字列で渡す文化は、静的型付け言語を好む私には馴染みません(それでも使い続けますが😆)。
でも、CakePHPは親切なことに、このTableLocatorにおいては、名前空間や末尾に Table
を伴っていても上手くクラスを検索してくれますので、Tableクラスへの参照を使って TableRegistry::getTableLocator()->get(FooBarTable::class);
のような書き方をすれば、大文字小文字を誤ってfooBarTable::class
としても、完全修飾クラス名としては正しい大文字小文字設定の文字列がruntimeにおいて得られるようなので、確実に幸せになれます。typoしたらIDEとかが怒ってくれるという点も、幸せポイント増し増しですね。
バージョン情報等
*1:個人の感想です