経緯
リファクタのためのクラス分割用にトップディレクトリにsrcなどのディレクトリを作成して、App::build()で登録、App::use()で読み込み使えるようにしたかった。
結論として
無駄にトップディレクトリにこだわっていたけれども、
素直にappディレクトリ以下を使った方が良い。
やったこと
とりあえずまずはトップディレクトリにsrc/Hoge/Fuga.php
を作成。
適当にテストを書いてHoge.php
に記述したclass Hoge
を読み込んでみる。
<?php
class HogeTest {
public function testHoge ()
{
App::build(
[
'Sample' => [ROOT . DS . 'src' . DS],
],
App::REGISTER
);
App::uses('Hoge','Sample');
$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
App::uses('Hoge','Sample');
$hoge = new Hoge()
var_dump($hoge->getHoge());
実験は失敗だ。
考えてみれば当たり前だけれども先に登録されているパスのクラスが使用される。
そのため上記の方法ではsrc以下でクラス名の重複ができない。
つまり
結論に戻って、素直にappディレクトリ以下につくれば良かった。
CakePHP2のクラスロードの仕組みは
<?php
App::use('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)) {
$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;
}
よって、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自体無理じゃん〜
って思ってたけど独立してるソースなら普通に読み込めるんだった。