初心phperがいきなりフレームワークを自作したときにオートローダがどういう仕組みで動いているかわからなかったので、設計してみてわかったことメモです。今更ありふれたネタかもしれませんがお許しください。
最初は名前空間なんぞいらんやんけ!とか思ってましたが、徐々に良さに気づいてきたので順に書いてみます。
#はじめに
composer使いましょう!
特殊な案件でないかぎり既成フレームワークを使いましょう!
ということで、実用ではなく理解のための記事になりますのでご容赦を。
名前空間を用いているのでPHP5.3以降でのおはなしですが、オートロードそのものは名前空間とは全くの別モノです。
名前空間という仕組みが導入されたことによって、オートローダ設計が格段にやりやすく、わかりやすくなりました。
#基本はrequire_once(require)だった
オブジェクト指向で開発をすると1クラス1ファイルの原則で書いていきます。
スクリプトAでクラスBとCとDを使いたければ、スクリプトAの先頭に
require_once B
require_once C
require_once D
を書く必要がありました。
ただしこれだと、利用するクラスと同じだけのrequire_once行を追加しなければいけません。
さて、このスクリプトAで、うしろのほうの行でクラスEが$e = new E();
されたとします。
Eはrequire_onceされていません。このままだとクラスEは存在しないというエラーが出てしまいます…
#オートロード関数という保険があった
クラスのオートローディングのためのspl_autoload_register
という関数があります。
(他にもオートロードはいくつか関数がありますがここではこれだけ使用します)
この関数を、どんなスクリプトが始まるよりも先にたった1度だけ実行しておきましょう。
引数にはコールバック関数をいれます。
spl_autoload_register('myLoader');
function myLoader($class) {
require_once $class . '.php';
// new E() される前に自動で ./E.phpがrequire_onceされる
}
require_once B
...
..
このようにしておくと
クラスの読み込みができなかったとき、そのクラスを$class
に代入し、関数myLoader($class)
を実行してくれます。
spl_autoload_register
は、 クラス読み込み失敗時に実行する関数を登録するための関数 です。
これでrequireが漏れることはありません!
…しかし、大半のクラスはスクリプトAと同じディレクトリにあるものの、クラスEはシステム上とても重要な機能を扱うものであったため別のディレクトリに置いてありました。
そもそもクラスファイルなんて、いろいろな場所に散り散りになっていたりすることがあるので、この書き方ではどうしようもありません。
#クラス名とクラスファイルパスの関係に決まりをつくったら
「このクラスは、このパスにある何々というphpファイル」というのがしっかりわかっている、もしくは決まっていさえすればいいのです。
例えばこのように
spl_autoload_register('myLoader');
function myLoader($class) {
$classMap = array(
'Hoge' => '/var/www/html/classes/hogeClass.php',
'Foo' => '/var/www/html/classes/bar/foo.php',
'E' => '/var/www/html/classes/core/util/E.php',
...
..
);
require_once $classMap[$class];
}
あらかじめマップを作っておけば全然問題ナシですね!もはやrequireとか必要なさそうです!
でもこれ、クラスを新しく作成したときにmyLoader
に追加するのを忘れそうで悪寒が止まりません。
汎用性がゼロです。どうすればよいでしょうか。
#クラス名にも階層のあるパスがついてたらいいのにな
システムのルートを$root = '/var/www/html'
とします。
ここにindex.php
と、/classes
があります。
上記のmappedLoader.php
の例だと、クラスEはルートから見て/classes/core/util
というパスにあるE.php
というファイルだということになっています。面倒ですね。
いっそ/classes/core/util/E
とかいうクラス名 だったら.php
つけてrequire_onceするだけでいいのに…とか思っちゃいます。
…
さすがにスラッシュをつけるとホンモノのパスみたいでなんだかマズいので、せめて逆に傾けてやりましょう。
\classes\core\util
という仮想的な空間にあるE
ということにしちゃえば、バックスラッシュをスラッシュに置き換えて末尾に拡張子をつけるだけで実際のファイルパスに変換できます。
このクラスに対する仮想的なパスを名前空間といいます。
#各ファイルの先頭にクラス専用のパスをつけてあげよう
クラスファイルはパスが決まっていますが、名前空間は自動的には決まりません。
必ずファイルの先頭に「このファイルにあるクラスはこの空間(仮想パス)に存在してるよ」ということを書いてあげなければいけません。
namespace classes\core\util
class E
{
// 処理..
}
こうすると、E.php
が読み込まれたあとでクラスEを使うためには
$e = new \classes\core\util\E();
とすることで可能となります。なんだか長くなっちゃいましたが、use
文を使うことで解決できます。(ここでは説明しません)
名前空間が自動的に決まらないということは、つまり実際のファイルパスとは関係なくどんな名前空間をつけてあげてもいいのですが、それではメリットがありません。
次のようにルールを決めてみましょう。
- 名前空間は、システムのルートディレクトリ以降のクラスファイルパスの
/
を\
に置き換えたものとする。 - クラスファイル名は、
(クラス名).php
とする。
このルールをオートローダに適用させてみるとどうなるでしょうか。
#名前空間を利用したオートローダ
システムルート直下にindex.phpとautoLoader.phpを置いておきます。
<?php
class autoLoader
{
private $system_root;
public function __construct($root)
{
// ルートディレクトリの設定
$this->system_root = $root;
}
public function register()
{
spl_autoload_register(array($this, 'myLoader'));
}
public function myLoader($class)
{
// 最初のスラッシュを含めるかどうかは適宜調整(ここでは取り除く)
$classNamespace = ltrim($class, '\\');
// バックスラッシュを右に倒してルートと拡張子を結合
$classFileName = $this->system_root . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $classNamespace) . '.php';
// クラスファイルが存在して読み込めるか確認
if(is_readable($classFileName)){
require_once $classFileName;
return true;
} else {
return false;
}
}
}
<?php
// ルートディレクトリ
$root = __DIR__;
// オートローダ読み込み
// これだけはrequireしなければいけない
require_once $root . 'autoLoader.php';
$autoloader = new autoLoader($root);
$autoloader->register();
$e = new \classes\core\util\E();
$e->setVar("baz");
...
..
このようにすると、
$e = new \classes\core\util\E();
がコールされた段階であらかじめ登録されていたmyLoader
が動作し、ルールに従って($root/)classes/core/util/E.php
をrequire_onceしてくれるというわけです。
各ファイルに名前空間をきちんと設定し、new
時に名前空間付きのクラス名を記述しておけばオートローダ本体を触る必要はありません。
もっとルールを含ませれば名前空間に縛られない多様なオートロードもできそうですね。
以上、オートローダと名前空間についてざっくり解説でした。