PHPのオートロードの仕組みを詳しく解説
(1) オートロード機構の概要
PHP の OO モデルを使用してシステムを開発する場合、通常は各クラスの実装を別のファイルに保存します。これにより、クラスの再利用が容易になり、将来のメンテナンスにも便利です。これは、OO デザインの基本的な考え方の 1 つでもあります。 PHP5 より前では、クラスを使用する必要がある場合、include/require を使用してクラスを直接インクルードするだけで済みました。実際の例を次に示します:
/* person.class.php */
クラス人 {
var $name, $age;
関数 __construct ($name, $age)
{
$this->name = $name;
$this->年齢 = $年齢;
}
}
?>
/* no_autoload.php */
require_once ("person.class.php");
$person = 新しい人物("アルタイル", 6);
var_dump ($person);
?>
この例では、no-autoload.php ファイルには、require_once を使用して組み込まれた Person クラスが必要です。その後、その Person クラスを使用してオブジェクトをインスタンス化できます。
ただし、プロジェクトの規模が拡大し続けると、この方法を使用するといくつかの隠れた問題が発生します。PHP ファイルで他の多くのクラスを使用する必要がある場合、大量の require/include ステートメントが必要になり、省略や欠落が発生する可能性があります。不要なクラスファイルをインポートします。多数のファイルで他のクラスの使用が必要な場合、各ファイルに正しいクラス ファイルが含まれていることを確認するのは悪夢のような作業になります。
PHP5 は、クラスの自動ロード メカニズムというこの問題に対する解決策を提供します。オートロード メカニズムにより、PHP プログラムは最初にすべてのクラス ファイルを含めるのではなく、クラスが使用される場合にのみクラス ファイルを自動的に含めることができます。このメカニズムは遅延ロードとも呼ばれます。
以下は、自動ロードメカニズムを使用して Person クラスをロードする例です:
/* autoload.php */
関数 __autoload($classname) {
require_once ($classname . “class.php”);
}
$person = 新しい人物("アルタイル", 6);
var_dump ($person);
?>
通常、PHP5 がクラスを使用するときに、そのクラスがロードされていないことが判明すると、自動的に __autoload() 関数が実行され、使用する必要のあるクラスをロードできます。この簡単な例では、拡張子「.class.php」を付けたクラス名を直接追加してクラス ファイル名を形成し、require_once を使用してそれをロードします。この例から、autoload は少なくとも 3 つのことを行う必要があることがわかります。1 つ目はクラス名に基づいてクラス ファイル名を決定すること、2 つ目はクラス ファイルが配置されているディスク パスを決定することです。私たちの場合、それが最も単純です。クラスがそれらを呼び出す PHP プログラム ファイルと同じフォルダーにある場合、3 番目のことは、ディスク ファイルからシステムにクラスをロードすることです。 3 番目のステップは最も単純で、include/require を使用するだけです。第 1 ステップと第 2 ステップの機能を実現するには、クラス名とディスク ファイル間のマッピング方法を開発時に合意する必要があります。この方法でのみ、クラス名に基づいて対応するディスク ファイルを見つけることができます。
したがって、含めるクラス ファイルが多数ある場合は、対応するルールを決定し、__autoload() 関数でクラス名と実際のディスク ファイルを一致させるだけで、遅延読み込み効果を実現できます。ここから、 __autoload() 関数の実装で最も重要なことは、クラス名と実際のディスク ファイルの間のマッピング ルールの実装であることもわかります。
しかし、ここで問題が発生します。システムの実装で他の多くのクラス ライブラリを使用する必要がある場合、これらのクラス ライブラリは異なる開発者によって作成され、クラス名と実際のディスク ファイル間のマッピング ルールが異なる可能性があります。現時点で、クラス ライブラリ ファイルの自動ロードを実装したい場合は、すべてのマッピング ルールを __autoload() 関数に実装する必要があります。この場合、__autoload() 関数は非常に複雑になるか、実装が不可能になる可能性があります。最終的に、__autoload() 関数は、たとえ実装できたとしても非常に肥大化する可能性があり、将来のメンテナンスとシステム効率に大きな悪影響を及ぼします。この場合、もっとシンプルで明確な解決策はないのでしょうか?答えはもちろん「いいえ」です! さらなる解決策を検討する前に、まず PHP の自動ロード メカニズムがどのように実装されているかを見てみましょう。
(2) PHPのオートロード機構の実装
PHP ファイルの実行は 2 つの独立したプロセスに分かれていることがわかっています。最初のステップでは、PHP ファイルを一般に OPCODE と呼ばれるバイトコード シーケンスにコンパイルします (実際には、zend_op_array と呼ばれるバイト配列にコンパイルされます)。仮想マシンによって。 PHP のすべての動作は、これらの OPCODE によって実装されます。したがって、PHP でのオートロードの実装メカニズムを研究するために、autoload.php ファイルをオペコードにコンパイルし、これらの OPCODE を使用してプロセスで PHP が何を行うかを研究しました:
/* autoload.php コンパイルされた OPCODE リストは、作者が開発した OPDUMP ツールを使用します
* 生成された結果は、Web サイト http://www.phpinternals.com/ からダウンロードできます。
*/
1:
2: // require_once ("person.php");
3:
4: 関数 __autoload ($classname) {
0 いいえ
0 受信 1
5: if (!class_exists($classname)) {
1 SEND_VAR !0
2 DO_FCALL ‘class_exists’ [extval:1]
3 BOOL_NOT $0 =>RES[~1]
4 JMPZ
6: require_once ($classname. “.class.php”);
5 CONCAT !0, ‘.class.php’ =>RES[~2]
6 INCLUDE_OR_EVAL ~2、REQUIRE_ONCE
7: }
7 JMP —>8
8: }
8 リターンヌル
9:
10: $p = 新しい人('フレッド', 35);
1 FETCH_CLASS ‘人’ =>RES[:0]
2 新規:0 =>RES[$1]
3 SEND_VAL「フレッド」
4 SEND_VAL 35
5 DO_FCALL_BY_NAME [extval:2]
6 割り当て
11:
12: var_dump ($p);
7 SEND_VAR !0
8 DO_FCALL ‘var_dump’ [extval:1]
13:?>
autoload.php の 10 行目では、クラス Person のオブジェクトをインスタンス化する必要があります。したがって、自動ロード メカニズムは、この行のコンパイルされたオペコードに確実に反映されます。上記のコードの 10 行目で生成された OPCODE から、オブジェクト Person をインスタンス化するときに、最初に FETCH_CLASS 命令を実行する必要があることがわかります。 PHP による FETCH_CLASS 命令の処理から探索の旅を始めましょう。
ZEND_VM_HANDLER(109, ZEND_FETCH_CLASS, …) (zend_vm_def.h 1864 行目)
=> zend_fetch_class (zend_execute_API.c 1434 行目)
=>zend_lookup_class_ex (zend_execute_API.c 964 行目)
=> zend_call_function(&fcall_info, &fcall_cache) (zend_execute_API.c 1040 行目)
呼び出しの最後のステップの前に、呼び出し時の主要なパラメーターを見てみましょう:
fcall_info.function_name = &autoload_function; // おっと、ついに「__autoload」が見つかりました
…
fcall_cache.function_handler = EG(autoload_func) // autoload_func !
;
zend_call_function は、Zend Engine の最も重要な関数の 1 つであり、その主な機能は、PHP プログラム内のユーザー定義関数または PHP 独自のライブラリ関数を実行することです。 zend_call_function には 2 つの重要なポインター パラメーター fcall_info と fcall_cache があり、それぞれ 2 つの重要な構造体 (1 つは zend_fcall_info 、もう 1 つは zend_fcall_info_cache) を指します。 zend_call_function の主なワークフローは次のとおりです。 fcall_cache.function_handler ポインターが NULL の場合は、fcall_info.function_name という名前の関数を検索し、存在する場合はそれを実行します。fcall_cache.function_handler が NULL でない場合は、指定された関数を直接実行します。 fcall_cache.function_handler 関数による。
(1) エグゼキュータのグローバル変数関数ポインタ autoload_func が NULL かどうかを確認します。
(2) autoload_func==NULL の場合、__autoload() 関数がシステムに定義されているかどうかを確認します。定義されていない場合は、エラーを報告して終了します。
(3) __autoload() 関数が定義されている場合は、__autoload() を実行してクラスのロードを試み、ロード結果を返します。
(4) autoload_func が NULL でない場合は、autoload_func ポインタが指す関数を直接実行してクラスをロードします。この時点では、__autoload() 関数が定義されているかどうかはチェックされないことに注意してください。
真実がついに明らかになりました。PHP には、自動ロード メカニズムを実装するための 2 つの方法が用意されています。1 つは、前述したように、通常は PHP ソース プログラムに実装されるユーザー定義の __autoload() 関数を使用する方法です。関数を設計し、autoload_func ポインターをその関数にポイントします。これは通常、C 言語を使用して PHP 拡張機能で実装されます。 __autoload() 関数と autoload_func の両方が実装されている場合 (autoload_func が特定の PHP 関数を指す)、autoload_func 関数のみが実行されます。
(3) SPLオートロード機構の実装
SPLはStandard PHP Libraryの略称です。これは、PHP5 で導入された拡張ライブラリであり、その主な機能には、オートロード メカニズムとさまざまな Iterator インターフェイスまたはクラスの実装が含まれます。 SPL オートロード メカニズムは、関数ポインタ autoload_func をオートロード機能を備えた自己実装関数にポイントすることによって実装されます。 SPL には、spl_autoload と spl_autoload_call という 2 つの異なる関数があります。これら 2 つの異なる関数アドレスを autoload_func に指定することで、異なる自動ロード メカニズムが実装されます。
spl_autoload は、SPL によって実装されるデフォルトの自動ロード関数であり、その機能は比較的単純です。 2 つのパラメータを受け取ることができます。最初のパラメータはクラス名を表し、2 番目のパラメータ $file_extensions はオプションであり、展開を保護するために $file_extensions に複数の拡張子を指定できます。セミコロンを使用して名前を区切ります。指定しない場合は、デフォルトの拡張子 .inc または .php が使用されます。 spl_autoload は、まず $class_name を小文字に変更し、次にすべてのインクルード パスで $class_name.inc または $class_name.php ファイルを検索し ($file_extensions パラメーターが指定されていない場合)、見つかった場合はクラス ファイルをロードします。 spl_autoload("person", ".class.php") を手動で使用して、Person クラスをロードできます。実際、複数の拡張子を指定できる点を除けば、require/include に似ています。
spl_autoload を自動的に動作させる、つまり autoload_func を spl_autoload にポイントする方法は?答えは、spl_autoload_register 関数を使用することです。 PHP スクリプトで初めてパラメータを指定せずに spl_autoload_register() を呼び出すと、autoload_func を spl_autoload に指定できます。
上記の説明から、spl_autoload の機能は比較的単純であり、SPL 拡張機能で実装されており、その機能を拡張できないことがわかります。独自のより柔軟な自動読み込みメカニズムを実装したい場合はどうすればよいでしょうか?この時点で、spl_autoload_call 関数がデビューします。
まず、spl_autoload_call の実装の素晴らしい機能を見てみましょう。 SPL モジュール内には、本質的に HashTable であるグローバル変数 autoload_functions がありますが、リンク リスト内の各要素は、次の機能を持つ関数を指す関数ポインターであると単純に考えることができます。クラスの自動ロード機能。 spl_autoload_call の実装自体は非常に単純で、リンクされたリスト内の各関数を順番に実行し、各関数の実行後に必要なクラスがロードされたかどうかを判断し、ロードが成功した場合はそのままリターンします。リンクされたリストの他の機能を実行し続けます。このリンクされたリスト内のすべての関数が実行された後でクラスがロードされていない場合、spl_autoload_call はユーザーにエラーを報告せずに直接終了します。したがって、オートロード メカニズムを使用しても、クラスが自動的に正しくロードされることは保証されません。キーはオートロード関数の実装方法に依存します。
それでは、自動ロード関数リスト autoload_functions は誰が管理するのでしょうか?先ほど述べた spl_autoload_register 関数です。ユーザー定義のオートロード関数をこのリンク リストに登録し、autoload_func 関数ポインターを spl_autoload_call 関数にポイントすることができます (例外があり、具体的な状況については全員が考えることができることに注意してください)。 spl_autoload_unregister 関数を使用して、登録された関数を autoload_functions リンク リストから削除することもできます。
前のセクションで述べたように、autoload_func ポインタが null 以外の場合、__autoload() 関数は自動的に実行されません。autoload_func は spl_autoload_call を指していますが、それでも __autoload() 関数を動作させたい場合はどうすればよいでしょうか。もちろん、引き続き spl_autoload_register(__autoload) 呼び出しを使用して、autoload_functions リンク リストに登録します。
最初のセクションの最後の質問に戻りますが、解決策はあります。各クラス ライブラリの異なる命名メカニズムに従って独自のオートロード関数を実装し、spl_autoload_register を使用してそれらを SPL オートロード関数キューにそれぞれ登録します。この方法では、非常に複雑な __autoload 関数を維持する必要がありません。
(4) オートロード効率の課題と対策
自動ロード メカニズムを使用するとき、多くの人は最初に、自動ロードを使用するとシステム効率が低下するという反応をします。効率性を高めるために自動ロードを使用しないことを提案する人もいます。オートロード実装の原理を理解すると、オートロード メカニズム自体がシステム効率に影響を与える理由ではないことがわかります。これにより、不要なクラスがシステムにロードされなくなるため、システム効率が向上する可能性さえあります。
では、多くの人が自動ロードを使用するとシステム効率が低下するという印象を抱くのはなぜでしょうか?実際、オートロード メカニズムの効率に影響を与えるのは、まさにユーザー設計のオートロード機能です。クラス名を実際のディスク ファイルと効率的に一致させることができない場合 (これはファイル名だけでなく実際のディスク ファイルを指すことに注意してください)、システムは多くのファイル存在検証を行う必要があります (各インクルード パスで) に含まれるパスを検索し、ファイルが存在するかどうかを判断するにはディスク I/O 操作が必要です。ご存知のとおり、ディスク I/O 操作の効率は非常に低いため、これが自動ロードの効率を低下させる原因となります。仕組み!
したがって、システムを設計するときは、クラス名を実際のディスク ファイルにマッピングするための明確なメカニズムを定義する必要があります。このルールが単純かつ明確であればあるほど、自動ロード メカニズムはより効率的になります。
結論: オートロード メカニズムは本質的に非効率的ではありません。オートロードの乱用や不適切に設計されたオートロード機能のみが効率の低下につながります。