1. ホーム
  2. Web プログラミング
  3. PHP プログラミング
  4. phpのヒント

PHPのオートロード機構の事例を詳しく解説

2022-02-15 01:23:25
<ブロッククオート

魔法のような __autoload() メソッドができる前は、プログラムファイルの中で100個のオブジェクトをインスタンス化しようと思ったら、100個のクラスファイルをincludeするかrequireするか、あるいは100個のクラスをすべて同じクラスファイルに定義しなければなりませんでした-それはとても大きなものだったに違いありません。このファイルは非常に大きくなることは間違いないでしょう。しかし、__autoload()メソッドが出たことで、もうその心配はありません。クラスはオブジェクトをインスタンス化する前に自動的にファイルをロードしてくれるのです。

1. オートロードの仕組みの概要

PHPのOOパターンを使ってシステムを開発する場合、各クラスの実装を別のファイルに格納するのが通例で、これによりクラスの再利用が容易になり、将来のメンテナンスも便利になります。これは、OO設計の基本的な考え方の一つでもあります。PHP5以前は、クラスを使う必要がある場合、include/requireを使って直接インクルードすればよかったのですが、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 ファイルが Person クラスを使用する必要があり、 require_once を使用してそれをインクルードし、Person クラスを直接使用してオブジェクトをインスタンス化することができるようになっています。

しかし、プロジェクトの規模が大きくなると、この方法を使用することには暗黙の問題があります。PHPファイルが他の多くのクラスを使用する必要がある場合、多くのrequire/include文が必要になり、結果として不要なクラスファイルを見逃したり、インクルードしてしまう可能性があるのです。多くのファイルで他のクラスを使用する必要がある場合、各ファイルが正しいクラスファイルを含むことを確認するのは悪夢となります。

PHP5 では、この問題を解決するために class autoload という機構が用意されています。 autoload は、PHP プログラムがクラスファイルをすべてインクルードするのではなく、 使用するときに自動的にインクルードすることを可能にする機構で、別名 レイジーローディング .

以下は、オートロード機構を使用して Person クラスをロードする例です。

/* autoload.php *<?php
 function __autoload($classname)
{
  $classpath=". /". $classname.'.class.php';
  if(file_exists($classpath))
  {
    require_once($classpath);
  }
  else
  {
    echo 'class file'. $classpath.'not found!';
   }
}
 
 $person = new Person("Altair", 6);
 var_dump ($person);
 ? >

通常、PHP5がクラスを使用する際、クラスがロードされていないことがわかると、自動的に __autoload() 関数が実行され、そこで必要なクラスをロードすることができます。この単純な例では、クラス名を拡張子 ".class.php" で直接追加してクラスのファイル名を作り、 require_once で読み込んでいます。この例から、次のことがわかります。 オートロード は、少なくとも3つのことをしなければならない。 まず第一に、クラス名に基づいてクラスのファイル名を決定すること、 第二に、クラスファイルがあるディスクパスを決定すること (我々の場合、最も単純なケースとして、クラスはそれを呼び出す PHP プログラムファイルと同じフォルダにあります)、 第三に、ディスクファイルからシステムにクラスを読み込むことです。 3番目のステップは最も簡単で、include/requireを使うだけです。最初のステップの機能を実現するために、2番目のステップでは、開発時にクラス名とディスクファイルの対応付けの方法に合意する必要があり、そうして初めて、クラス名に基づいてその対応するディスクファイルを見つけることができるのです。

したがって、取り込むクラスファイルが大量にある場合は、対応するルールを決めて、__autoload()関数の中で、クラス名と実際のディスクファイルを対応させて、遅延ロードの効果を実現すればいいのです。ここから、__autoload()関数の実装で最も重要なのは、クラス名と実際のディスクファイルの対応付けのルールの実装であることもわかります。

しかし、ここで問題が発生します。システムの実装において、使用する必要がある他のクラスライブラリが多数ある場合、これらのクラスライブラリは異なる開発者によって書かれ、そのクラス名が実際のディスクファイルのマッピングルールと全く同じでない可能性があるのです。この場合、クラスライブラリファイルの自動読み込みを実装しようとすると、 __autoload() 関数ですべてのマッピングルールを実装しなければならず、 __autoload() 関数が非常に複雑になったり、実装が不可能になったりする可能性があります。結局、__autoload()関数が非常に肥大化し、実装できたとしても、将来のメンテナンスやシステム効率に大きな悪影響を与える可能性があります。この場合、もっとシンプルで明快な解決策はないのでしょうか?もちろん、答えはそうです。NO!」です。さらなる解決策を検討する前に、PHPのオートロード機構がどのように実装されているかを見てみましょう。

2. PHPのオートロード機構の実装

ご存知のように、PHPファイルの実行は2つの別々の処理に分かれています。最初のステップは、PHPファイルをコンパイルして一般に言うところの OPCODE PHPのすべての動作は、このOPCODEによって実装されています。そこで、PHPにおけるautoloadの実装メカニズムを研究するために、autoload.phpファイルをopcodeにコンパイルし、このopcodeに基づいてPHPがどのような処理を行うかを研究します。

 /* autoload.php compiled OPCODE list, using the OPDUMP tool developed by the author
     * The generated result can be downloaded from the website 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クラスのオブジェクトをインスタンス化する必要があります。そのため、その行がコンパイルされた後のオペコードにオートロード機構を反映させる必要があります。上記のコードの10行目で生成されたOPCODEから、Personというオブジェクトをインスタンス化する際にFETCH_CLASSディレクティブが最初に実行されることが分かります。それでは、PHP の FETCH_CLASS ディレクティブの扱いから、発見の旅を始めましょう。

PHPのソースコード(PHPバージョン5.3alpha2を使用)を見てみると、次のような一連の呼び出しがあることがわかります。

ZEND_VM_HANDLER(109, ZEND_FETCH_CLASS, ...) (zend_vm_def.h line 1864)
 => zend_fetch_class (zend_execute_API.c line 1434)
  => zend_lookup_class_ex (zend_execute_API.c line 964)
   => zend_call_function(&fcall_info, &fcall_cache) (zend_execute_API.c line 1040)

呼び出しの最終ステップの前に、呼び出しのための主要なパラメータである

 /* Set the value of the autoload_function variable to "__autoload" * fcall_info.function_name = &autoload_function; // Ooops, finally found "__autoload"
 ...
 fcall_cache.function_handler = EG(autoload_func); // autoload_func !

zend_call_function zend_call_function は Zend Engine で最も重要な関数のひとつで、 その主な機能は PHP プログラム内のユーザ定義関数や PHP ライブラリ関数を実行することです。 zend_call_function には重要なポインタパラメータ fcall_info, fcall_cache があり、これらは 2 つの重要な構造体を指しています。fcall_cache.function_handler ポインタが NULL の場合、 fcall_info.function _name という名前の関数を探して、もし存在すればそれを実行します。

これで、PHP がオブジェクトをインスタンス化するとき (実際には、インターフェースを実装するとき、クラス定数や静的変数をクラスで使うとき、クラスの静的メソッドを呼び出すとき) は、まずそのクラス (やインターフェース) がシステムに存在するかどうかを調べ、存在しなければ オートロード機構を使ってクラスをロードしようとすることがよくわかりました。そして、オートロード機構の主な実行内容は以下の通りです。

  1. アクチュエータグローバル変数関数ポインタautoload_funcがNULLであるかどうかをチェックします。
  2. autoload_func==NULL の場合、システムで定義されている __autoload() 関数があるかどうかを調べ、ない場合はエラーを報告して終了します。
  3. autoload()関数が定義されている場合、__autoload()を実行してクラスの読み込みを試み、その結果を返します。
  4. autoload_func が NULL でない場合、 autoload_func ポインタが指す関数が直接実行され、クラスがロードされます。この時点では __autoload() 関数の定義がチェックされないことに注意しましょう。

ようやく真実が見えてきました。PHPはオートロードの仕組みを実装するために2つの方法を提供しています。1つは、先ほど述べたように、ユーザー定義の__autoload()関数を使用することで、通常PHPソースプログラム内に実装されています。そしてもう1つは、autoload_funcポインタを指す関数を設計し、通常C言語を使ってPHP拡張モジュールで実装されていることです。autoload()関数とautoload_func(autoload_funcを特定のPHP関数に指す)の両方が実装されている場合、autoload_func関数だけが実行されます。

3. SPLオートロード機構の実装

SPLとは、Standard PHP Libraryの略です。SPL のオートロード機構は、関数ポインタ autoload_func を実装したオートロード関数に向けることで実装します。 autoload, spl_autoload_call. 異なるオートロード機構は、これら2つの異なる関数のアドレスを autoload_func に指定することで実装されます。

spl_autoload は SPL が実装するデフォルトのオートロード関数で、比較的シンプルなものです。最初の引数はクラス名の $class_name で、2 番目の引数はクラスファイルの拡張子の $file_extensions で、セミコロンで区切って複数の拡張子を指定できます。spl_autoload はまず $class_name を小文字に変更し、次にすべてのインクルードパスで $class_name.inc あるいは $class_name.php を探し ($file_extensions パラメータが指定されていない場合)、見つかったらそのクラスファイルをロードします。spl_autoload("Person", ".class.php") を使うと、Person クラスを手動で読み込むことができます。実際、これは複数の拡張子を指定できることを除けば require/include に似ています。

答えは、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 関数に向けます (ただし、これには 1 つの例外があり、それがどれかはあなたの考えるにお任せします)。また、spl_autoload_unregister 関数を使って autoload_functions チェーンから既に登録されている関数を削除することもできます。

前節で述べたように、autoload_func ポインタが空でないときは、 __autoload() 関数は自動実行されません。autoload_func がすでに spl_autoload_call を指している今、それでも __autoload() 関数を動作させたい場合はどうしたらよいでしょうか。もちろん、autoload_functions チェーンに登録するために spl_autoload_register(__autoload) 呼び出しをまだ使用します。

各クラスライブラリの異なる命名規則に従って各オートロード関数を実装し、 spl_autoload_register を使ってそれらを別々に SPL オートロード関数キューに登録するのです。この方法では、非常に複雑な __autoload 関数を維持する必要がありません。

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

オートロード機構を使う場合、多くの人が最初に感じるのは、オートロードを使うとシステム効率が落ちるということであり、効率のためにオートロードを使わないという提案さえある。オートロード機構は、不要なクラスをシステムにロードしないため、それ自体がシステム効率を低下させる原因にはならず、むしろ効率を向上させる可能性もあります。

では、なぜオートロードを使うとシステム効率が落ちるという印象を持つ人が多いのでしょうか。実は、オートロードの仕組み自体の効率に影響を与えるのは、まさにユーザーが設計したオートロードの機能なのです。クラス名と実際のディスクファイル(ファイル名だけでなく、実際のディスクファイルという意味です)を効率的に対応させないと、システムがファイルが存在するかどうかの判断(インクルードパスに含まれる各パスを探す必要があります)を多く行うことになり、ファイルが存在するかどうかを判断するには非常に効率が悪いことで知られているディスク入出力操作を行う必要があります。 は非常に低いので、これがオートロード機構の効率を下げる元凶なのです!。

そこで、システム設計時にクラス名と実際のディスクファイルの対応付けの仕組みを明確に定義しておく必要があります。このルールがシンプルで明確であればあるほど、オートロード機構はより効率的なものになります。

<ブロッククオート

結論 オートロードの誤用やオートロード関数の設計が悪いだけで、オートロードの効率は低下します。

PHPのオートロード機構については、この記事がすべてです。PHPのオートロード機構についてもっと詳しく知りたい方は、BinaryDevelopの過去の記事を検索するか、以下の関連記事を引き続きご覧ください。