経緯
リファクタのためのクラス分割用にトップディレクトリにsrcなどのディレクトリを作成して、App::build()で登録、App::use()で読み込み使えるようにしたかった。
結論として
無駄にトップディレクトリにこだわっていたけれども、
素直にappディレクトリ以下を使った方が良い。
やったこと
とりあえずまずはトップディレクトリにsrc/Hoge/Fuga.php
を作成。
src/ Hoge/ Hoge.php
適当にテストを書いてHoge.php
に記述したclass Hoge
を読み込んでみる。
<?php class HogeTest { public function testHoge () { App::build( [ 'Sample' => [ROOT . DS . 'src' . DS], ], App::REGISTER ); App::uses('Hoge','Sample'); // Class Not Found... $hoge = new Hoge(); } }
Sample/Hoge/Hoge.phpまでは読み込んでくれない。
https://book.cakephp.org/2/ja/core-utility-libraries/app.html#vendor
サブディレクトリがある場合はサブディレクトリも登録してあげる必要があるらしい。
<?php class HogeTest { public function testHoge () { App::build( 'Sample' => [ ROOT . DS . 'src' . DS, ROOT . DS . 'src' . DS . 'Hoge' . DS, ], App::REGISTER ); App::uses('Hoge','Sample'); // 読み込めたぞ! $hoge = new Hoge(); } }
実験は成功だ!!!!!
しかし、リファクタをするにあたりディレクトリが大量にできていくであろう中、
その度にパスを追加する必要があるのか…?
ということに気づいた。
そしてできたコードが以下。
<?php // ディレクトリを取得する再帰関数 function _load_src($root, &$paths) { $res = glob($root . '/*', GLOB_ONLYDIR | GLOB_MARK); if (empty($res)) { return; } $paths = array_merge($paths, $res); foreach ($res as $f) { _load_src($f, $paths); } } $_src_paths = []; _load_src(ROOT . DS . 'src', $_src_paths); App::build( [ 'Sample' => $_src_paths, ], App::REGISTER );
これでいいじゃん(いいじゃん)
と思ってbootstrap.php
にこいつをぶち込んでやるぜと意気揚々としていたところ、
そういえばクラス名が競合した場合はどうなるのん?
とふと思ったので試した。
src/ Hoge.php Hoge/ Hoge.php
<?php class Hoge { public function getHoge() { return 'Hoge'; } }
<?php class Hoge { public function getHoge() { return 'Fuga'; } }
これを出力する。
<?php /** テストfunction内 **/ // パッケージを追加 ※略 App::uses('Hoge','Sample'); $hoge = new Hoge() var_dump($hoge->getHoge());
string(4) "hoge"
実験は失敗だ。
考えてみれば当たり前だけれども先に登録されているパスのクラスが使用される。
そのため上記の方法ではsrc以下でクラス名の重複ができない。
つまり
結論に戻って、素直にappディレクトリ以下につくれば良かった。
CakePHP2のクラスロードの仕組みは
<?php App::use('Hoge','Sample'); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /** classMapに ['Hoge' => 'Sample'] として登録される。 **/ public static function uses($className, $location) { self::$_classMap[$className] = $location; } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /** ロード処理 **/ public static function load($className) { if (!isset(self::$_classMap[$className])) { return false; } $parts = explode('.', self::$_classMap[$className], 2); list($plugin, $package) = count($parts) > 1 ? $parts : array(null, current($parts)); if ($file = self::_mapped($className, $plugin)) { return include $file; } $paths = self::path($package, $plugin); if (empty($plugin)) { // ここに入ってきてCakeの設定した接頭辞がつけられる(Cake規約) $appLibs = empty(self::$_packages['Lib']) ? APPLIBS : current(self::$_packages['Lib']); $paths[] = $appLibs . $package . DS; $paths[] = APP . $package . DS; $paths[] = CAKE . $package . DS; } else { $pluginPath = self::pluginPath($plugin); $paths[] = $pluginPath . 'Lib' . DS . $package . DS; $paths[] = $pluginPath . $package . DS; } $normalizedClassName = str_replace('\\', DS, $className); foreach ($paths as $path) { $file = $path . $normalizedClassName . '.php'; if (file_exists($file)) { self::_map($file, $className, $plugin); return include $file; } } return false; } /** $pathsの中身 array(3) { [0]=> string(29) "/ROOTHOGE/app/Lib/Sample/" [1]=> string(25) "/ROOTHOGE/app/Sample/" [2]=> string(30) "/ROOTHOGE/html/lib/Cake/Sample/" } **/
よって、appディレクトリ以下に作成すれば下記のような場合
- app - src - Hoge.php -Hoge - Hoge.php
<?php App::uses('Hoge','src'); // または App::uses('Hoge','src/Hoge');
でクラスを使い分けられるようになるのであった。
- 完 -
補足
一応App.phpのloadに
$paths[] = ROOT . DS . $package . DS;
と記述してあげればできるっちゃできるのだけど流石にコアライブラリいじくりたくなくてやめた。
補足 +
これ単純に先にパスを登録しておくか動的に追加するかでしかないなとか
そもそもnamespaceきってできるじゃんとか色々と気づいた。
元々のソースにnamespace入れようとして死んだ経験からnamespace自体無理じゃん〜
って思ってたけど独立してるソースなら普通に読み込めるんだった。