ホームページ >バックエンド開発 >PHPチュートリアル >PHPのオートロードメカニズムの詳細な分析

PHPのオートロードメカニズムの詳細な分析

不言
不言オリジナル
2018-09-07 14:21:277857ブラウズ

__autoload は自動ロードを実現しますが、マルチクラスライブラリの導入により __autoload のメンテナンスが煩雑になるため、 spl_aotoload を導入しました 自動ロード関数リストの手動登録と削除を実装します spl を見てみましょう具体的な内容です。

PHP マジック関数 __autoload() メソッドが登場する前は、プログラム ファイル内で 100 個のオブジェクトをインスタンス化したい場合は、include を使用するか、100 個のクラス ファイルを include する必要があるか、または 100 個のクラスが同じクラス ファイル - このファイルは非常に大きくなると思います。ただし、__autoload() メソッドを使用すると、オブジェクトをインスタンス化する前に、このクラスが指定されたファイルを自動的にロードしますので、今後は心配する必要はありません。

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)
{
  $classpath="./".$classname.&#39;.class.php&#39;;
  if(file_exists($classpath))
  {
    require_once($classpath);
  }
  else
  {
    echo &#39;class file&#39;.$classpath.&#39;not found!&#39;;
   }
}
 
 $person = new Person(”Altair”, 6);
 var_dump ($person);
 ?>

通常、PHP5 がクラスを使用するとき、クラスがロードされていないことが判明すると、そのクラスが自動的に実行されます。 __autoload() 関数 この関数では、使用する必要があるクラスをロードできます。この簡単な例では、拡張子「.class.php」を付けたクラス名を直接追加してクラス ファイル名を形成し、require_once を使用してそれをロードします。この例から、autoload は少なくとも 3 つのことを実行する必要があることがわかります。最初のことは、クラス名に基づいてクラス ファイル名を決定することであり、2 つ目はディスクを決定することです。クラス ファイルの場所 パス (この場合は最も単純なケースで、クラスはそれらを呼び出す PHP プログラム ファイルと同じフォルダーにあります)、そして 3 番目のことは、ディスク ファイルからシステムにクラスをロードすることです。 3 番目のステップは最も単純で、include/require を使用するだけです。第一段階と第二段階の機能を実現するには、クラス名とディスクファイルのマッピング方法を開発時に合意する必要があり、これによって初めてクラス名から対応するディスクファイルを見つけることができます。

したがって、インクルードするクラス ファイルが多数ある場合は、対応するルールを決定し、__autoload() 関数でクラス名と実際のディスク ファイルを一致させるだけで済みます。遅延読み込み効果。ここから、 __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工具
     * 生成的结果,可以到网站 http://www.phpinternals.com/ 下载该软件。
     */
    1: <?php
    2:  // require_once (”Person.php”);
    3:  
    4:  function __autoload ($classname) {
            0  NOP                
            0  RECV                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                ~1, ->8
    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  RETURN              null
    9:  
   10:  $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
   11:  
   12:  var_dump ($p);
            7  SEND_VAR            !0
            8  DO_FCALL            ‘var_dump’ [extval:1]
   13: ?>

在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是可选的,表示类文件的扩展名,可以在$file_extensions中指定多个扩展名,护展名之间用分号隔开即可;如果不指定的话,它将使用默认的扩展名.inc或.php。spl_autoload首先将$class_name变为小写,然后在所有的include path中搜索$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 モジュール内には、グローバル変数 autoload_functions があります。これは本質的に HashTable ですが、単純にリンク リストと考えることができます。リンク リストの各要素は、次の機能を持つ関数を指す関数ポインターです。クラスを自動的にロードする関数。 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 操作の効率は非常に低いため、これが効率を低下させる原因となります。

したがって、システムを設計するときは、クラス名を実際のディスク ファイルにマッピングするための明確なメカニズムを定義する必要があります。このルールが単純かつ明確であればあるほど、自動ロード メカニズムはより効率的になります。

結論: オートロード メカニズムは本質的に非効率的ではありません。効率の低下につながるのは、オートロードの乱用や不適切に設計されたオートロード機能のみです。

関連する推奨事項:

#PHP オートロード実行メカニズムのサンプル分析、オートロードサンプル分析

#PHP オートロード メカニズム (遅延読み込み)# ##############################

以上がPHPのオートロードメカニズムの詳細な分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。