
class Hoge { public static $profile; public function __construct() { if (self::$profile === null) { self::$profile = (object)array('construction' => 0); } self::$profile->construction++; } } // まだ参照しちゃ、らめ! // echo Hoge::$profile->construction; // Notice: Trying to get property of non-object // これはよい。 new Hoge(); echo Hoge::$profile->construction; // 1
一度もインスタンスを作成していない場合、Hoge::$profile は NULL のままです。クラス定義の後に続けて Hoge::$profile = (object)array('construction' => 0); としてやれば回避できますが、フローがクラス定義の外へ追い出されてしまいます。そこで、Java の静的初期化子や C# の静的コンストラクタを模倣する手段を考えました。PHP は存在しないクラスが要求された場合にオートローディングが働くよう仕組まれています。これを利用して、オートローダが呼ばれたタイミングで初期化用の静的メソッドをコールすればいいのではと思いました。
実装例を上げます。静的初期化子をサポートするクラスのマーカー的な interface と、それに対応したクラスローダを定義します。
interface IStaticInitializable { static function initializeMember(); } class ClassLoader { protected $baseClassPath; public function __construct($baseClassPath) { $this->baseClassPath = $baseClassPath . DIRECTORY_SEPARATOR; } public function loadClass($name) { $patterns = array(DIRECTORY_SEPARATOR, ’.’, '_', '::'); $replacements = array('', '', DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR); $filePath = $this->baseClassPath . str_replace($patterns, $replacements, $name) . '.php'; if (file_exists($filePath)) { require_once $filePath; } if (class_exists($name, false)) { $class = new ReflectionClass($name); if ($class->implementsInterface('IStaticInitializable')) { eval("$name::initializeMember();"); } } } }
ここから IStaticInitializable の実装例を上げます。静的変数(クラスのそれではなく、スコープ修飾子。紛らわしい )で二度初期化しないよう定義しています。ファイル名はみんな大好き Hikonyann.php です。
class Hikonyann implements IStaticInitializable { public static $stacks; public static $eatings; public static $initializedBy; public static $initializedAt; public static function initializeMember() { static $initialized = false; if (!$initialized) { self::$stacks = mt_rand(0, 100); self::$eatings = mt_rand(0, 100); ob_start(); debug_print_backtrace(); $backtrace = ob_get_clean(); self::$initializedBy = $backtrace; self::$initializedAt = time(); $initialized = true; } } }
クラスの利用側。ファイルはクラスと同じディレクトリにあると仮定します。
<?php $loader = new ClassLoader(dirname(__FILE__)); spl_autoload_register(array($loader, 'loadClass')); printf( 'ひこにゃんは %2$d 回通路に挟まりました。%1$s' . 'ひこにゃんは %3$d 個のだんごを食べました。%1$s' . '状態が観測されたのは %4$s です。%1$s' . '観測者は次の通りです。%1$s%5$s', PHP_EOL, Hikonyann::$stacks, Hikonyann::$eatings, date(DATE_RFC3339, Hikonyann::$initializedAt), Hikonyann::$initializedBy );
そして出力例。
ひこにゃんは 85 回通路に挟まりました。 ひこにゃんは 27 個のだんごを食べました。 状態が観測されたのは 2008-05-01T12:32:39+09:00 です。 観測者は次の通りです。 #0 Hikonyann::initializeMember() called at [#:\###########\sample.php(32) : eval()'d code:1] #1 eval() called at [#:\###########\sample.php:32] #2 ClassLoader->loadClass(Hikonyann) #3 spl_autoload_call(Hikonyann) called at [#:\###########\sample.php:47]
これで初期化を意識せず済むようになりました。