PHPのオートロード機構の詳しい説明
(1)オートロード機構の概要
PHPのOOモードを使ってシステムを開発する場合、通常は各クラスの実装を別のファイルに保存するのが一般的ですが、これにより、クラスの再利用が容易になり、将来のメンテナンスにも便利になります。これは、OO デザインの基本的な考え方の 1 つでもあります。 PHP5 より前では、クラスを使用する必要がある場合、include/require を使用してクラスを直接インクルードするだけで済みました。実践的な例を次に示します:
コードは次のとおりです:
/* Person.class.php */ <?php class Person { var $name, $age; function construct ($name, $age) { $this->name = $name; $this->age = $age; } } ?> /* no_autoload.php */ <?php require_once (”Person.class.php”); $person = new Person(”Altair”, 6); var_dump ($person); ?>
この例では、no-autoload.php ファイルは、require_once を使用してインクルードする Person クラスを使用する必要があり、その後、直接使用できます。 Person クラスをインスタンス化してオブジェクトを変換します。
しかし、プロジェクトの規模が拡大し続けると、この方法を使用するといくつかの隠れた問題が発生します。PHP ファイルで他の多くのクラスを使用する必要がある場合、多くの require/include ステートメントが必要になり、省略が発生する可能性があります。不要なクラスファイルを含めます。多数のファイルで他のクラスの使用が必要な場合、各ファイルに正しいクラス ファイルが含まれていることを確認するのは悪夢のような作業になります。
PHP5 は、クラスの自動読み込み (オートロード) メカニズムという、この問題の解決策を提供します。オートロード機構により、PHP プログラムは最初からすべてのクラス ファイルを組み込むのではなく、クラスが使用される場合にのみクラス ファイルを自動的に組み込むことができます。この機構は遅延ロードとも呼ばれます。
以下は、オートロードメカニズムを使用して Person クラスをロードする例です:
コードは次のとおりです:
/* autoload.php */ <?php function autoload($classname) { require_once ($classname . “class.php”); } $person = new Person(”Altair”, 6); var_dump ($person); ?>
通常、PHP5 がクラスを使用するとき、クラスがロードされていないことが判明すると、クラスがロードされます。 autoload() 関数を自動的に実行します。この関数では、使用する必要のあるクラスをロードできます。この簡単な例では、拡張子 "title="extension" >extension ".class.php" を持つクラス名を直接追加してクラス ファイル名を形成し、require_once を使用してそれをロードします。この例から、次のことができます。 autoload は少なくとも 3 つのことを行う必要があることに注意してください。1 つ目はクラス名に基づいてクラス ファイル名を決定すること、2 つ目はクラス ファイルが配置されているディスク パスを決定することです (この例では、最も単純なケースでは、クラスはそれらを呼び出す PHP プログラム ファイルと同じフォルダーにあります。3 番目のステップは、クラスをディスク ファイルからシステムにロードすることです。これは、単に include/require を使用するだけです。最初のステップ、2 番目のステップの関数は、開発中にクラス名とディスク ファイル間のマッピング方法に同意する必要があります。この方法でのみ、クラス名に従って対応するディスク ファイルを見つけることができます。インクルードするクラス ファイルが多数ある場合、現時点では、対応するルールを決定し、autoload() 関数でクラス名を実際のディスク ファイルと照合するだけで、遅延読み込み効果を実現できます。ここから、autoload() 関数の実装も確認できます。最も重要なことは、クラス名と実際のディスク ファイル間のマッピング ルールの実装です。しかし、他の多くのクラスを使用する必要がある場合に問題が発生します。システムの実装において、これらのクラス ライブラリは、異なる開発者によって開発される場合があります。このとき、自動ロードを実現したい場合は、クラス名とクラス ライブラリによって書き込まれる実際のディスク ファイルの間のマッピング ルールが異なります。クラス ライブラリ ファイルのすべてのマッピング ルールを autoload() 関数に実装する必要があるため、autoload() 関数は非常に複雑になるか、最終的には実装が不可能になる可能性があります。たとえ実装できたとしても、それは将来のメンテナンスとシステムの効率に大きな悪影響を及ぼします。これ以上の解決策を検討する前に、もっと単純で明確な解決策はないでしょうか?まず、PHP の自動ロード メカニズムがどのように実装されているかを見てみましょう
(2) PHP の自動ロード メカニズムの実装
最初のステップは、PHP ファイルの実行が 2 つの独立したプロセスに分割されていることです。 PHP ファイルを一般に OPCODE と呼ばれるバイトコード シーケンスにコンパイルします (実際には、zend_op_array と呼ばれるバイト配列にコンパイルされます)。2 番目のステップは、これらの OPCODE を仮想マシンで実行することです。したがって、PHP のすべての動作はこれらの OPCODE によって実装されます。 PHP でのオートロードの実装メカニズムを研究します。 autoload.php ファイルがオペコードにコンパイルされ、これらの OPCODE に基づいて、プロセスで PHP が何を行ったかを研究します。
/* autoload.php のコンパイルされた OPCODE リストが作成されます。著者が開発した OPDUMP ツールを使用します*/
コードは次のとおりです:
<?php // 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 ‘Person' =>RES[:0] 2 NEW :0 =>RES[$1] 3 SEND_VAL ‘Fred' 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实例化一个对象。因此autoload机制一定会在该行编译后的opcode中有所体 现。从上面的第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; // Ooops, 终于发现”autoload”了
…
fcall_cache.function_handler = EG(autoload_func); // autoload_func !
zend_call_function 是Zend Engine中最重要的函数之一,其主要功能是执行用户在PHP程序中自定义的函数或者PHP本身的库函数。zend_call_function有两个 重要的指针形参数fcall_info, fcall_cache,它们分别指向两个重要的结构,一个是zend_fcall_info, 另一个是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在实例化一个 对象时(实际上在实现接口,使用类常数或类中的静态变量,调用类中的静态方法时都会如此),首先会在系统中查找该类(或接口)是否存在,如果不存在的话就 尝试使用autoload机制来加载该类。而autoload机制的主要执行过程为:
(1) 检查执行器全局变量函数指针autoload_func是否为NULL。
(2) 如果autoload_func==NULL, 则查找系统中是否定义有autoload()函数,如果没有,则报告错误并退出。
(3) 如果定义了autoload()函数,则执行autoload()尝试加载类,并返回加载结果。
(4) 如果autoload_func不为NULL,则直接执行autoload_func指针指向的函数用来加载类。注意此时并不检查autoload()函数是否定义。
真 相终于大白,PHP提供了两种方法来实现自动装载机制,一种我们前面已经提到过,是使用用户定义的autoload()函数,这通常在PHP源程序中 来实现;另外一种就是设计一个函数,将autoload_func指针指向它,这通常使用C语言在PHP扩展中实现。如果既实现了 autoload()函数,又实现了autoload_func(将autoload_func指向某一PHP函数),那么只执行 autoload_func函数。
(3) SPL autoload机制的实现
SPL 是Standard PHP Library(标准PHP库)的缩写。它是PHP5引入的一个扩展库,其主要功能包括autoload机制的实现及包括各种Iterator接口或类。 SPL autoload机制的实现是通过将函数指针autoload_func指向自己实现的具有自动装载功能的函数来实现的。SPL有两个不同的函数 spl_autoload, spl_autoload_call,通过将autoload_func指向这两个不同的函数地址来实现不同的自动加载机制。
spl_autoload 是SPL实现的默认的自动加载函数,它的功能比较简单。它可以接收两个参数,第一个参数是$class_name,表示类名,第二个参 数$file_extensions是可选的,表示类文件的扩展名" title="扩展名">扩展名,可以在$file_extensions中指定多个扩展名" title="扩展名">扩展名,护展名之间用分号隔开即 可;如果不指定的话,它将使用默认的扩展名" title="扩展名">扩展名.inc或.php。spl_autoload首先将$class_name变为小写,然后在所有的 include path中搜索$class_name.inc或$class_name.php文件(如果不指定$file_extensions参数的话),如果找 到,就加载该类文件。你可以手动使用spl_autoload(”Person”, “.class.php”)来加载Person类。实际上,它跟require/include差不多,不同的它可以指定多个扩展名" title="扩展名">扩展名。
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 自動ロード関数に登録します。列。 。この方法では、非常に複雑な自動ロード機能を維持する必要がありません。
(4) オートロード効率の問題と対策
オートロードメカニズムを使用するとき、多くの人が最初に抱く反応は、オートロードを使用するとシステムの効率が低下するということであり、効率性のためにオートロードを使用しないことを提案する人さえいます。オートロード実装の原理を理解すると、オートロード メカニズム自体がシステム効率に影響を与える理由ではないことがわかります。これにより、不要なクラスがシステムにロードされなくなるため、システム効率が向上する可能性さえあります。
では、なぜ多くの人は自動ロードを使用するとシステム効率が低下するという印象を持っているのでしょうか?実際、オートロード メカニズムの効率に影響を与えるのは、まさにユーザー設計のオートロード機能です。クラス名を実際のディスク ファイルと効率的に一致させることができない場合 (これはファイル名だけでなく実際のディスク ファイルを指すことに注意してください)、システムは多くのファイル存在検証を実行する必要があります (各インクルード パスでの検証が必要です)。ご存知のとおり、ディスク I/O 操作の効率は非常に低いため、これが効率を低下させる原因となります。
したがって、システムを設計する際には、クラス名を実際のディスク ファイルにマッピングするための明確なメカニズムを定義する必要があります。このルールが単純かつ明確であればあるほど、自動ロード メカニズムはより効率的になります。
結論: 自動ロード メカニズムは本質的に非効率的ではありません。自動ロードの乱用と不適切に設計された自動ロード機能のみが効率の低下につながります。
以上がautoload と spl_autoload 自動ロードの比較分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。