ホームページ  >  記事  >  php教程  >  PHPのオートロード機構の実装分析

PHPのオートロード機構の実装分析

WBOY
WBOYオリジナル
2016-06-13 11:57:591026ブラウズ

1. オートロードメカニズムの概要

PHP の OO モードを使用してシステムを開発する場合、通常は各クラスの実装を別のファイルに保存するのが一般的です。これは非常に簡単です。クラスの再利用が可能になり、将来のメンテナンスに便利です。これは、OO デザインの基本的な考え方の 1 つでもあります。 PHP5 より前では、クラスを使用する必要がある場合、include/require を使用してクラスを直接インクルードするだけで済みました。
以下は実際的な例です:

コードをコピーします コードは次のとおりです:


/ * パーソン.クラス .php */
クラス パーソン {
var $name, $age
function __construct ($name, $age)
{
$this-> ;name = $name;
$this->age = $age;
}
/* no_autoload.php */
require_once ("person.class.php");
$person ("Altair", 6);
var_dump ($person); 🎜>
この例では、no-autoload.php ファイルには、require_once を使用して組み込まれた Person クラスが必要です。その後、その Person クラスを直接使用してオブジェクトをインスタンス化できます。
しかし、プロジェクトの規模が拡大し続けると、この方法を使用するといくつかの隠れた問題が発生します。PHP ファイルが他の多くのクラスを使用する必要がある場合、多くの require/include ステートメントが必要になり、これにより問題が発生する可能性があります。クラスファイルが存在しないか、不要なクラスファイルが含まれています。多数のファイルで他のクラスの使用が必要な場合、各ファイルに正しいクラス ファイルが含まれていることを確認するのは悪夢のような作業になります。
PHP5 は、クラスの自動ロード メカニズムというこの問題の解決策を提供します。オートロード機構により、PHP プログラムは最初からすべてのクラス ファイルを組み込むのではなく、クラスが使用される場合にのみクラス ファイルを自動的に組み込むことができます。この機構は遅延ロードとも呼ばれます。


次に、自動ロード メカニズムを使用して Person クラスをロードする例を示します。




コードをコピーします

コードは次のとおりです。

/* autoload.php */ function __autoload($classname) {

require_once ($classname . "class.php");

}
$person("Altair", 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 つの独立したプロセスに分割することです。一般に OPCODE と呼ばれる一連のバイトコード (実際には zend_op_array と呼ばれるバイト配列にコンパイルされます) 2 番目のステップは、仮想マシンによってこれらの OPCODE を実行することです。 PHP のすべての動作は、これらの OPCODE によって実装されます。したがって、PHP でのオートロードの実装メカニズムを調べるために、autoload.php ファイルをオペコードにコンパイルし、これらの OPCODE を使用してプロセスで PHP が何を行うかを調べます:

コード をコピーします。 コードは次のとおりです。


/* autoload.php コンパイルされた OPCODE リストは、作者が開発した OPDUMP ツール
* を使用して生成できます。 Web サイト http://www.phpinternals.com/ にアクセスし、ソフトウェアをダウンロードします。
*/
// require_once ("person.php");

function __autoload ($classname) {
0 NOP
0 RECV 1
if (!class_exists($classname)) {
1 SEND_VAR !0
2 DO_FCALL 'class_exists' [extval:1]
3 BOOL_NOT $0 =>RES[~1]
4 JMPZ ~1, ->8
require_once ($classname. ".class.php")
5 CONCAT !0, '.class.php' =>RES[~2]
6 INCLUDE_OR_EVAL ~2, REQUIRE_ONCE
}
7 JMP ->8
}
8 RETURN null

$p = new Person('Fred', 35); >1 FETCH_CLASS '人' =>RES[:0]
2 NEW :0 =>RES[$1]
3 SEND_VAL 'フレッド'
4 SEND_VAL 35
5 DO_FCALL_BY_NAME [extval: 2]
6 ASSIGN !0, $1

var_dump ($p);
7 SEND_VAR !0
8 DO_FCALL 'var_dump' [extval:1]
?>


autoload.php の 10 行目で、クラス Person のオブジェクトをインスタンス化する必要があります。したがって、自動ロード メカニズムは、この行のコンパイルされたオペコードに確実に反映されます。上記のコードの 10 行目で生成された OPCODE から、オブジェクト Person をインスタンス化するときに、最初に FETCH_CLASS 命令を実行する必要があることがわかります。 PHP による FETCH_CLASS 命令の処理から探索の旅を始めましょう。

PHP のソース コードを確認すると (PHP 5.3alpha2 バージョンを使用しています)、次の呼び出しシーケンスを見つけることができます:

コードをコピー コードは次のとおりです:

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 行目)

呼び出しの最後のステップの前に、以下を見てみましょう。呼び出しの主要なパラメータ:

コードをコピー コードは次のとおりです:


/* autoload_function 変数の値を "__autoload" に設定します */
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 関数による。

これで、PHP がオブジェクトをインスタンス化するとき (これは実際にインターフェイスを実装し、クラス内でクラス定数または静的変数を使用し、クラス内で静的メソッドを呼び出す場合に当てはまります)、最初に次のようになります。システムにクラス (またはインターフェイス) が存在するかどうかを確認し、存在しない場合は、自動ロード メカニズムを使用してクラスをロードしてみます。オートロード メカニズムの主な実行プロセスは次のとおりです。

(1) エグゼキューターのグローバル変数関数ポインター autoload_func が NULL かどうかを確認します。
(2) autoload_func==NULL の場合、__autoload() 関数がシステムに定義されているかどうかを確認し、定義されていない場合は、エラーを報告して終了します。
(3) __autoload() 関数が定義されている場合は、__autoload() を実行してクラスのロードを試み、ロード結果を返します。
(4) autoload_func が NULL でない場合は、autoload_func ポインタが指す関数を直接実行してクラスをロードします。この時点では、__autoload() 関数が定義されているかどうかはチェックされないことに注意してください。
真実は、最終的に明らかになります。PHP には、自動ロード メカニズムを実装するための 2 つの方法が用意されています。1 つは、前述したように、通常は PHP ソース プログラムに実装されるユーザー定義の __autoload() 関数を使用する方法です。もう 1 つは関数を設計し、autoload_func ポインタをその関数に指すようにする方法です。これは通常、C 言語を使用して PHP 拡張機能で実装されます。 __autoload() 関数と autoload_func の両方が実装されている場合 (autoload_func が特定の PHP 関数を指す)、autoload_func 関数のみが実行されます。

3. SPL オートロード機構の実装
SPL は Standard PHP Library の略称です。これは、PHP5 で導入された拡張ライブラリであり、その主な機能には、オートロード メカニズムとさまざまな Iterator インターフェイスまたはクラスの実装が含まれます。 SPL オートロード メカニズムは、関数ポインタ autoload_func をオートロード関数を持つ自己実装関数にポイントすることによって実装されます。 SPL には 2 つの異なる関数 spl_autoload と spl_autoload_call があり、これら 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 を指します。仕事、何をすべきですか?もちろん、引き続き spl_autoload_register(__autoload) 呼び出しを使用して、autoload_functions リンク リストに登録します。

最初のセクションの最後の質問に戻りますが、解決策があります。各クラス ライブラリの異なる命名メカニズムに従って独自のオートロード関数を実装し、spl_autoload_register を使用してそれらを SPL オートロード関数に登録します。それぞれキューに入れるだけです。この方法では、非常に複雑な __autoload 関数を維持する必要がありません。

4. オートロード効率の問題と対策

オートロード メカニズムを使用するとき、多くの人は最初に、オートロードを使用するとシステム効率が低下するのではないかと考える人もいます。効率を高めるため、自動ロードは使用しないでください。オートロード実装の原理を理解すると、オートロード メカニズム自体がシステム効率に影響を与える理由ではないことがわかります。これにより、不要なクラスがシステムにロードされなくなるため、システム効率が向上する可能性さえあります。

では、自動ロードを使用するとシステム効率が低下するという印象を多くの人が抱くのはなぜでしょうか?実際、オートロード メカニズムの効率に影響を与えるのは、まさにユーザー設計のオートロード機能です。クラス名を実際のディスク ファイルと効率的に一致させることができない場合 (これはファイル名だけでなく実際のディスク ファイルを指すことに注意してください)、システムは多くのファイル存在検証を行う必要があります (各インクルード パスで) に含まれるパスを検索し、ファイルが存在するかどうかを判断するにはディスク I/O 操作が必要です。ご存知のとおり、ディスク I/O 操作の効率は非常に低いため、これが自動ロードの効率を低下させる原因となります。

したがって、システムを設計するときは、クラス名を実際のディスク ファイルにマッピングするための明確なメカニズムを定義する必要があります。このルールが単純かつ明確であればあるほど、自動ロード メカニズムはより効率的になります。自動ロード メカニズムは本質的に非効率的ではありません。効率の低下につながるのは、自動ロード機能の乱用と不適切な設計のみです。
声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。