Symfoware

Symfowareについての考察blog

PHP spl_autoload_registerでautoloadを自前で実装

過去に作成されたこんなPHPのプログラムがあるとします。

702_01.png

・sample.php


  1. <?php
  2. require_once('classes/Hoge.php');
  3. require_once('classes/Fuga.php');
  4. // ... 以下、使いそうなクラスを事前にrequire_once
  5. // 呼び出し部分だけinclude.phpのようなファイル名で分けている場合もあり
  6. $hoge = new Hoge();
  7. $hoge->call();
  8. // 結局、Fugaは使わなかった...




・classes/Hoge.php


  1. <?php
  2. // classes/Hoge.php
  3. class Hoge {
  4.     
  5.     public function call() {
  6.         echo 'Hoge!'.PHP_EOL;
  7.     }
  8.     
  9. }




・classes/Fuga.php


  1. <?php
  2. // classes/Fuga.php
  3. class Fuga {
  4.     
  5.     public function call() {
  6.         echo 'Fuga!'.PHP_EOL;
  7.     }
  8.     
  9. }





autoloadなこの時代、なんとかならないか調べてみると
「spl_autoload_register」が解決してくれそうです。

PHP で、spl_autoload_register を使って、require_once 地獄を脱出しよう





spl_autoload_register



未知のクラスがnewされた時、spl_autoload_registerに登録しておいた関数が呼び出される。
呼び出しクラス名が引数で渡されるので、そのクラスが存在するファイルを
requireしてやればよいようです。

動作を確認するため、最初のsample.phpを変更してみます。


  1. <?php
  2. function myClassLoader($className) {
  3.     echo '$className:'.$className.PHP_EOL;    
  4. }
  5. spl_autoload_register('myClassLoader');
  6. /* 無名関数で登録してもOK
  7. spl_autoload_register(function ($className) {
  8.     echo $className.PHP_EOL;
  9. });
  10. */
  11. $hoge = new Hoge();
  12. $hoge->call();




実行してみると、「Hoge」というクラス名を引数に渡してくれていますね。
プログラムは実行エラーで終了します。


# php sample.php
$className:Hoge
PHP Fatal error: Class 'Hoge' not found in /var/dev/php/splblog/sample.php on line 15




HogeやFugaクラスの呼び出しに応じて、phpファイルをrequireするようにしてみます。


  1. <?php
  2. function myClassLoader($className) {
  3.     echo '$className:'.$className.PHP_EOL;
  4.     
  5.     // classes/[クラス名].phpファイルを読み込み
  6.     require_once('classes/'.$className.'.php');
  7. }
  8. spl_autoload_register('myClassLoader');
  9. $hoge = new Hoge();
  10. $hoge->call();




ちゃんとHogeクラスのロードと実行が行えました。


# php sample.php
$className:Hoge
Hoge!




Fugaクラスの呼び出しも試してみます。


  1. <?php
  2. function myClassLoader($className) {
  3.     echo '$className:'.$className.PHP_EOL;
  4.     
  5.     // classes/[クラス名].phpファイルを読み込み
  6.     require_once('classes/'.$className.'.php');
  7. }
  8. spl_autoload_register('myClassLoader');
  9. $hoge = new Hoge();
  10. $hoge->call();
  11. $fuga = new Fuga();
  12. $fuga->call();
  13. // 2回目のHoge呼び出し
  14. $hoge2 = new Hoge();
  15. $hoge2->call();




Fugaも呼び出せました。
また、一度呼び出しに成功したクラス名はキャッシュされ、2回めのnewの時は、
spl_autoload_registerで登録した関数の呼び出しは行われませんでした。


# php sample.php
$className:Hoge
Hoge!
$className:Fuga
Fuga!
Hoge!    # $className:Hogeのメッセージ無しに実行結果が表示される







例外クラス名への対応



例のように一律「クラス名.php」というルールでファイル名が生成されていれば良いのですが、
例えば「Piyo」クラスだけ「SpecialPiyo.php」だった場合の対応法を考えます。

・classes/SpecialPiyo.php


  1. <?php
  2. // classes/SpecialPiyo.php
  3. class Piyo {
  4.     
  5.     public function call() {
  6.         echo 'Piyo!'.PHP_EOL;
  7.     }
  8.     
  9. }





最初に考えられるのは「Piyo」の呼び出しの時だけ分岐する案。


  1. <?php
  2. function myClassLoader($className) {
  3.     echo '$className:'.$className.PHP_EOL;
  4.     
  5.     // classes/[クラス名].phpファイルを読み込み
  6.     // Piyoの時は例外
  7.     if ($className == 'Piyo') {
  8.         require_once('classes/SpecialPiyo.php');
  9.     } else {
  10.         require_once('classes/'.$className.'.php');
  11.     }
  12. }
  13. spl_autoload_register('myClassLoader');
  14. $hoge = new Hoge();
  15. $hoge->call();
  16. $piyo = new Piyo();
  17. $piyo->call();




実行結果


# php sample.php
$className:Hoge
Hoge!
$className:Piyo
Piyo!





その他の案は、2つのloaderを作るでしょうか。


  1. <?php
  2. // 通常ケース
  3. function myClassLoader($className) {
  4.     echo 'normal load:'.$className.PHP_EOL;
  5.     
  6.     // ファイルの存在チェック
  7.     $path = 'classes/'.$className.'.php';
  8.     if (!is_file($path)) {
  9.         echo 'normal load:'.$className.' error'.PHP_EOL;
  10.         return;
  11.     }
  12.     require_once($path);
  13. }
  14. // 例外ケース
  15. function mySpecialClassLoader($className) {
  16.     echo 'special load:'.$className.PHP_EOL;
  17.     
  18.     // ファイルの存在チェック
  19.     $path = 'classes/Special'.$className.'.php';
  20.     if (!is_file($path)) {
  21.         echo 'special load:'.$className.' error'.PHP_EOL;
  22.         return;
  23.     }
  24.     require_once($path);
  25. }
  26. // 2つの関数を登録
  27. spl_autoload_register('myClassLoader');
  28. spl_autoload_register('mySpecialClassLoader');
  29. $hoge = new Hoge();
  30. $hoge->call();
  31. $piyo = new Piyo();
  32. $piyo->call();




実行結果


# php sample.php
normal load:Hoge
Hoge!
normal load:Piyo
normal load:Piyo error
special load:Piyo
Piyo!







更に複雑な呼び出し



さらにこんなクラスが出てきました。

・classes/SomeOne.php


  1. <?php
  2. // classes/SomeOne.php
  3. class SomeBody {
  4.     
  5.     public function call() {
  6.         echo 'SomeBody!'.PHP_EOL;
  7.     }
  8.     
  9. }




クラス名とファイル名にある程度規則性があればよいのですが、
全く規則性がない場合は、クラス名ごとにマッピングすることになると思います。


  1. <?php
  2. function myClassLoader($className) {
  3.     
  4.     $classMap = array(
  5.         'Hoge' => 'Hoge.php',
  6.         'Fuga' => 'Fuga.php',
  7.         'Piyo' => 'SpecialPiyo.php',
  8.         'SomeBody' => 'SomeOne.php',
  9.     );
  10.     
  11.     echo 'load:'.$className.PHP_EOL;
  12.     
  13.     $fileName = $classMap[$className];
  14.     
  15.     // ファイルの存在チェック
  16.     $path = 'classes/'.$fileName;
  17.     if (!is_file($path)) {
  18.         echo 'load:'.$className.' error'.PHP_EOL;
  19.         return;
  20.     }
  21.     require_once($path);
  22. }
  23. // 関数を登録
  24. spl_autoload_register('myClassLoader');
  25. $hoge = new Hoge();
  26. $hoge->call();
  27. $piyo = new Piyo();
  28. $piyo->call();
  29. $body = new SomeBody();
  30. $body->call();





これは簡単な例なので良いですが、「クラス名」と「実体のパス」の対応表を作るのに
それなりにコストがかかる場合は、staticにしたいと思うのが人情だと思います。

spl_autoload_registerにはクラスメソッドも登録できるので、
クラスメソッド登録方式に変更してみます。


  1. <?php
  2. class MyClassLoader {
  3.     private static $_classMap = null;
  4.     
  5.     public static function initialize() {
  6.         // コストの高い対応表の作成処理
  7.         self::$_classMap = array(
  8.             'Hoge' => 'Hoge.php',
  9.             'Fuga' => 'Fuga.php',
  10.             'Piyo' => 'SpecialPiyo.php',
  11.             'SomeBody' => 'SomeOne.php',
  12.         );
  13.     }
  14.     
  15.     // spl_autoload_registerに登録する関数
  16.     public static function loadClass($className) {
  17.         echo 'load:'.$className.PHP_EOL;
  18.         
  19.         $fileName = self::$_classMap[$className];
  20.         
  21.         // ファイルの存在チェック
  22.         $path = 'classes/'.$fileName;
  23.         if (!is_file($path)) {
  24.             echo 'load:'.$className.' error'.PHP_EOL;
  25.             return;
  26.         }
  27.         require_once($path);
  28.     }
  29.     
  30. }
  31. // 事前に対応表を作成
  32. MyClassLoader::initialize();
  33. // 関数を登録 array('クラス名', 'メソッド名')が引数
  34. spl_autoload_register(array('MyClassLoader', 'loadClass'));
  35. $hoge = new Hoge();
  36. $hoge->call();
  37. $piyo = new Piyo();
  38. $piyo->call();
  39. $body = new SomeBody();
  40. $body->call();




うまく行きました。


# php sample.php
load:Hoge
Hoge!
load:Piyo
Piyo!
load:SomeBody
SomeBody!





これで昔から使用されているライブラリなどの呼び出しも楽ができそうです。


【参考URL】

PHP で、spl_autoload_register を使って、require_once 地獄を脱出しよう

spl_autoload_register

関連記事

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2016/08/02(火) 22:50:35|
  2. PHP
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集
<<PHP pharに同梱したプログラムにautoloadを適用する | ホーム | PHP pharで複数のソースファイルを1つのファイルにまとめる>>

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://symfoware.blog68.fc2.com/tb.php/1905-f65b2b25
この記事にトラックバックする(FC2ブログユーザー)