柿の種が降ってきてあわててミドリゾウリムシを持って逃げる防空頭巾姿の白い生き物がクラスの定義ファイルを読み込む手段に spl_autoload_register() をお勧めする7つの理由。

| | コメント(6) | トラックバック(0)

xxnyann.png2007年も残すところ一ヶ月を切り、PHP 4 の開発・メンテナンスと、築城400年祭と、商標使用期限の終了が迫っています。既に PHP 5 への移行は済んだと思われますが、この機会にクラスを定義したファイルの読み込み方法について見直してみましょう。

クラスを定義したファイルを1ファイル1クラスで保存している場合、PHP 4 から使用できる手段として、コードの先頭で require_once() を使用して読み込んでいました。使用するクラスの数だけ列挙が必要なため、オブジェクト同士の協調を図るコードを書く場合など、非常に多くの手間が掛かります。
例えば…

<?php
/**
 * クリスマス・イヴ未明からクリスマス終日にかけてひこにゃんが一緒に居てくれます。
 */

// require_once() からクラス定義ファイルを相対パスで読めるようにして。
set_include_path(get_include_path() . PATH_SEPARATOR . '/path/to/my/');

require_once 'My/DateTime.php';
require_once 'My/TimeSpan.php';
require_once 'My/Schedule.php';
require_once 'My/HikonyannPool.php';
require_once 'My/NakanohitoNadoInaiException.php';
require_once 'My/PoolOverflowException.php';
require_once 'My/TrademarkExpiredException.php';


// 私「2007-12-24T00:00:00Z から」
$when = new My_DateTime(2007, 12, 23, 15, 0, 0);
// 私「二日間ほど」
$howlong = new My_TimeSpan(2, 0, 0, 0);
// 私「借りたいの!」
$schedule = new My_Schedule($when, $howlong);

// 私「ダメ?」
try
{
    $hikonyann = HikonyannPool::borrow($schedule); 
    echo $hikonyann->letEnjoy();
    
    /****
            /i     iヽ
  ワーワー     ((/□\))
           ソ_∠ニ二ス  キャーキャー
      ヽ○ノ ∠シ,, ・ェ・ )ゝ
        |○ノ  (   ) ヾ○ノ
        へ|  ○ ∪∪    |
         /> /|ヽ      lヽ
           ∧ 
     ****/
}
// 市「ダメ(中の人などいない的に)」
catch (My_NakanohitoNadoInaiException $e) 
{
    echo 'ひこにゃんもクリスマスを楽しんでいます。';
}
// 市「ダメ(着ぐるみ数が限界的に)」
catch (My_PoolOverflowException $e)
{
    echo 'ひこにゃんは忙しいです。';
}
// 市「ダメ(登録商標的に)」
catch (My_TrademarkExpiredException $e)
{
    echo '尻尾をつけたのが不味かったか!?';
}

?>

この膨大な require_once にうんざりした PHP 5 は、解決手段としてオブジェクトのオートローディングを用意しました。未定義のクラスが使用された時点で、__autoload() がユーザー定義されていれば引数にクラス名を与えて呼び出され、クラスを定義する機会が与えられます。
例えば…

<?php

function __autoload($className)
{
    $replaces = array(
            '_' => DIRECTORY_SEPARATOR,
            '.' => '',
        );
    $classPath = str_replace(array_keys($replaces), array_values($replaces), $className); 
    $fileName = '/path/to/classes/' . $classPath . '.php';
    
    if (is_file($fileName))
    {
        require_once $fileName;
    }
}


// この行が実行された時点で初めて __autoload('Hikonyann') が呼び出されます。
// 結果的に require_once '/path/to/classes/My/Hikonyann.php' が実行されます。
$hikonyann = new My_Hikonyann();

?>

これを利用して、最初のサンプルをオートローディングを使ったものへ置き換えます。ここではローダーを再利用できるように、別のファイルに分けて記述してみます。

/path/to/autoload.php

<?php

/**
 * 未定義のクラスを解決します。
 *
 * @param string $className 定義されていないクラスの名前。
 */
function __autoload($className)
{
    $replaces = array(
            '_' => DIRECTORY_SEPARATOR,
            '.' => '',
        );
    $classPath = str_replace(array_keys($replaces), array_values($replaces), $className); 
    $fileName = '/path/to/my/' . $classPath . '.php';
    
    if (is_file($fileName))
    {
        require_once $fileName;
    }
}

?>

次に、set_include_path() やら require_once() が並んでいた部分と入れ替えてしまいます。

xmas.php

<?php
/**
 * クリスマス・イヴ未明からクリスマス終日にかけてひこにゃんが一緒に居てくれます。
 */

require_once '/path/to/autoload.php';


// 私「2007-12-24T00:00:00Z から」
$when = new My_DateTime(2007, 12, 23, 15, 0, 0);
// 私「二日間ほど」
$howlong = new My_TimeSpan(2, 0, 0, 0);
// 私「借りたいの!」
$schedule = new My_Schedule($when, $howlong);

// ...
// ...
// ...
?>

一度ローダーを作ってしまえば、もう二度とクラスの定義ファイルを手作業で解決する必要がなくなります。 後から使用するクラスを追加して require_once() を忘れてしまい、 <samp>Fatal error: Class 'My_Hikonyann' not found</samp> と怒られる事もなくなります。 もしも怒られるとしたら、本当にクラスを作ってない時だけです。

PHP 5.1.2 からは SPL 関数で spl_autoload_register() がサポートされ、複数のローダーが使用できるようになりました。 spl_autoload_register() の引数は callback 型で、ローダー関数を登録するようになっています。 クラスに対するファイルの結び付けルールが異なるライブラリを使用する場合にも、それぞれのローダーを登録して解決できます。 
例えば…

<?php

function my_autoload($className)
{
    // ここにワタシのクラスを解決するコード。
}

class Your
{
    public static function autoload($className)
    {
        // ここにアナタのクラスを解決するコード。    
    }
}

spl_autoload_register('my_autoload');
spl_autoload_register(array('Your', 'autoload'));

?>

spl_autoload_register() と create_function() を組み合わせて、 クラスの利用者にとって不必要なローダー関数を隠してしまう事も可能です。 ローダー関数が衝突する可能性もなくなります。

<?php

spl_autoload_register(
        create_function(
                '$className',
                'require_once \'path/to/my/\' . $className . \'.php\';'
            )
    );

?>

PHP 5.3 からは名前空間のサポートが始まります。 ローダーの名前空間への対応も簡単です。クラス名にダブルコロン "::" が加わるだけですから。

<?php

/**
 * 未定義のクラスを解決します。
 *
 * @param string $className 定義されていないクラスの名前。
 */
function my_autoload($className)
{
    $replaces = array(
            '_' => DIRECTORY_SEPARATOR,
            '::' => DIRECTORY_SEPARATOR, // 名前空間のための追加箇所
            '.' => '',
        );
    $classPath = str_replace(array_keys($replaces), array_values($replaces), $className); 
    $fileName = '/path/to/my/' . $classPath . '.php';
    
    if (is_file($fileName))
    {
        require_once $fileName;
    }
}

spl_autoload_register('my_autoload');

?>

最後に、spl_autoload_register() を利用することで得られる(得られそうな)利点を上げてみます。

  • クラス定義ファイルの解決が自動化。→手作業によるポカミスが減ります。
  • include_path を汚したり、パスを定義するための定数・変数が不要になります。
  • クラス定義ファイルの遅延評価による、パフォーマンスの改善があるかもです。
  • 一つの基底クラスから extends する場面が続くと require_once() による確認が減るので、結果的にパフォーマンスの改善があるかもです。
  • クラス定義ファイルの配布形式を後から変えられます。→例えば Phar 化
  • クラス数多すぎて I/O 大変だから1個のファイルにまとめろ(乱暴な!)。→もしも require_once() を使っていたら書き換えとチェックで…ぞぞ

トラックバック(0)

このブログ記事を参照しているブログ一覧: 柿の種が降ってきてあわててミドリゾウリムシを持って逃げる防空頭巾姿の白い生き物がクラスの定義ファイルを読み込む手段に spl_autoload_register() をお勧めする7つの理由。

このブログ記事に対するトラックバックURL: http://zenith.sakura.ne.jp/mt/mt-tb.cgi/4

コメント(6)

R :

おー、便利ー……とか読みながら思ったけど、
そう言えば借りてるサーバはまだPHP4だった……

zenith :

うんあるある、さくらもまだ4.4.7のまま
互換性って大事よねー

Many thanks for your good publish. I'll take the notes you've written.

Hello there, I should say it is a clever posting. I'll certainly be seeking in on this web site yet again soon.

Cheers for this content, guys, continue to keep up the good work.

コメントする


画像の中に見える文字を入力してください。

このブログ記事について

このページは、zenithが2007年12月 5日 01:37に書いたブログ記事です。

ひとつ前のブログ記事は「今作ってる物」です。

次のブログ記事は「読み取り専用の配列を用いて柿の種が降ってきてあわててミドリゾウリムシを持って逃げる防空頭巾姿の白い生き物のプロフィールを保障する。」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

Powered by Movable Type 4.12