2024.04.12
STAFF BLOG
スタッフブログ
TECHNICAL
テクログ
## 不思議なこと
### 前提
FuelPHP ではディレクトリ構造とファイル名に規則があります。
ざっくり言うと`fuel/app/classes/controller/example/something.php`のようなパスがある場合、
something.php で定義する class は `Controller_Example_Something` になります。
このように classes から数えていき、ディレクトリを区切るときはアンダースコアで区切ります。
### 不思議な部分
通常 PHP で別ファイルに定義されている class を読み込むときは `require something.php` や `include something.php` のようにファイル自体を読み込む必要があります。
ですが、FuelPHP を使っている場合は基本的に `require` などでファイルは読み込みません。自然と使えます。
これが不思議です。
## class 名でアクセスできる理由
調べると `spl_autoload_register` による機能でした。
## FuelPHP での実装を覗く
FuelPHPのアプリの実行時の序盤で呼ばれるファイル `fuel\app\bootstrap.php` では、Coreファイルの読み込み後に以下のメソッドを実行しています。
// Register the autoloader
Autoloader::register();
このメソッドの定義を見ると以下のメソッドがあります。
public static function register()
{
spl_autoload_register('Autoloader::load', true, true);
}
`spl_autoload_register` 関数を利用することで未定義の class を読み込むときに実行される関数を呼ぶことが出来ます。
公式ドキュメントには以下のようにあります。
spl_autoload_register — 指定した関数を __autoload() の実装として登録する
https://www.php.net/manual/ja/function.spl-autoload-register.php
公式ドキュメントだけを読むと私には結果的にどうなるか分かりません。`__autoload()` という名前の関数があり、その実装として`spl_autoload_register`を使って実装できると認識しています。たらい回しになっている気分です。
とりあえず未定義の class を読み込む場合に実行してくれる認識でいます。
## `Autoloader::load` の中身を覗く
spl_autoload_register('Autoloader::load', true, true);
上記の関数によって `Autoloader::load` が呼ばれます。`Autoloader::load`の中身を覗くと`Controller_XXX_YYY_ZZZ`のような class にアクセスするための処理がありました。
かいつまむと以下のようになっています。
public static function load($class)
{
// ~~ 省略 ~~
if ( ! $loaded)
{
$path = APPPATH.'classes'.DS.static::class_to_path($class);
if (is_file($path))
{
static::init_class($class, $path);
$loaded = true;
}
}
※ なぜか $class には呼び出しもとのクラス名が入りますが不思議です。この不思議はこの記事では解明できていません。
static::class_to_path($class)
このメソッドによってアンダースコアをスラッシュに置換しています。
最終的に該当 class までのパスを取得しています。
さらに覗いていけばより詳細な読み込み方法が分かると思いますが、ここまででも`Controller_XXX_YYY_ZZZ`のように class 名でのアクセスが可能だと分かりました。
## ちょっと検証する
`fuel/app/classes/controller/example/something.php` にこういう class を用意する
<?php
class Controller_Example_Something
{
public static function hello()
{
Debug::dump('Hello');
}
}
### 正常系を確認する
別コントローラでこのように呼び出します。
Controller_Example_Something::hello();
die;
### 正常系の結果
以下のように画面に表示されました。
APPPATH/classes/controller/example/something.php @ line: 6
Variable #1:
(String): "Hello" (5 characters)
### 異常系の検証1
`Autoloader::load` を書き換えて読み込まれる時に something.php => replace.php に変換します。
public static function load($class)
{
if ( ! $loaded)
{
$path = APPPATH.'classes'.DS.static::class_to_path($class);
/**
* vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
* something.php => 'replace.php に変換
*/
if ($class === 'Controller_Example_Something') {
$path = str_replace('something.php', 'replace.php', $path);
}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
if (is_file($path))
{
static::init_class($class, $path);
$loaded = true;
}
}
### 異常系の結果1
以下のように画面に表示されました。エラーです。
Error!
Error [ Error ]: Class 'Controller_Example_Something' not found
`fuel/app/classes/controller/example/something.php` ではなく、実際には `fuel/app/classes/controller/example/replace.php` を参照しにいきますが存在しないのでエラーになります。
### 異常系の検証2
ここで存在していなかった replace.php を実際に controller/example/replace.php に用意するとエラーにならないはずです。
↓↓このファイルを定義します。
<?php
class Controller_Example_Something
{
public static function hello()
{
Debug::dump('Hello FROM replace');
}
}
### 異常系の結果2
以下のように画面に表示されました。予想通りでした。
APPPATH/classes/controller/example/replace.php @ line: 6
Variable #1:
(String): "Hello FROM replace" (18 characters)
## おわり
ということで不思議は解明しました。別の不思議が1つ生まれましたが今回は見逃します。
OSS の貢献方法はプルリクエストを作成する以外にも「このOSS使ってますよ!」っていう声も貢献の一つになると聞きました。
ということでFuelPHP使ってます!業務上の課題はいくつもありますが、FuelPHP自体に課題があると思ったことはないです。多分。