グローバル状態を使用してクラス メタデータに関する実行時情報を PHP に格納するのに苦労しています。明らかな理由により、グローバル状態は「悪い」と見なされるため、できるだけ避けたいと思います。とはいえ、どの状況でそれが本当に受け入れられ、どの状況で受け入れられないのかはまだわかりません. 具体的な例を提供するために、PHP での Enum 型の実装を見てみましょう。各具体的な Enum 型は抽象 Enum 型を拡張し、リフレクションを使用してサブクラスの定数を有効な Enum 値として読み取ります。
<?php
abstract class AbstractEnum
{
/**
* The constants currently resolved.
*
* Constants are resolved as flyweights into global state,
* therefore they have to be distinguished by the class name they
* refer to in this array.
*
* E.g.
*
* array(
* 'My\Full\Qualified\Enum\Class' => array(
* 'CONST1' => 'value1',
* 'CONST2' => 'value2'
* ),
* 'My\Other\Full\Qualified\Enum\Class' => array(
* 'CONST1' => 'value1',
* 'CONST2' => 'value2'
* )
* )
*
* @var array
*/
private static $constants = array();
/**
* The enumeration value of this instance.
*
* @var mixed
*/
private $value;
/**
* Constructor.
*
* @param mixed $value The enumeration value of this instance.
*
* @throws \UnexpectedEnumValueException if given value is not defined
* in the enumeration class.
*/
final public function __construct($value)
{
$values = static::getValues();
if (!in_array($value, $values, true)) {
throw new \UnexpectedEnumValueException($value, $values, get_called_class());
}
$this->value = $value;
}
/**
* Returns the enumeration value of this instance as string representation.
*
* @return string
*/
final public function __toString()
{
return (string) $this->value;
}
/**
* Returns the enumeration value of this instance.
*
* @return mixed
*/
final public function getValue()
{
return $this->value;
}
/**
* Returns all enumeration values defined in this class.
*
* @return array
*/
final public static function getValues()
{
$class = get_called_class();
// Resolve class constants as flyweights.
if (!isset(self::$constants[$class])) {
self::resolveConstants($class);
}
return self::$constants[$class];
}
/**
* Resolves the constants of a given full qualified class name.
*
* @param string $class The full qualified class name to resolve the constants for.
*/
private static function resolveConstants($class)
{
$reflectionClass = new \ReflectionClass($class);
self::$constants[$class] = $reflectionClass->getConstants();
}
}
class Month extends AbstractEnum
{
const JANUARY = 1;
const FEBRUARY = 2;
// ...
}
$month = new Month(Month::JANUARY);
ここで考えられる問題は、リフレクションを使用してサブクラスの定数を解決する resolveConstants() メソッドです。実行時に大量の Enum インスタンスを作成する必要がある状況を考えてみましょう。ここで各インスタンスでリフレクションを使用すると、パフォーマンスに深刻な影響を与える可能性があるため、特定の Enum 型の最初のインスタンスでのみクラス メタデータを「遅延読み込み」するのが適切な方法のようです。しかし、これを達成するにはグローバル状態を使用する必要がありますが、これも間違っているようです。これは最良の例ではないかもしれませんが、パフォーマンスの問題に関する私の懸念を示しています。オブジェクトを状態に設定するために、リフレクションとクラス メタデータ イントロスペクションを広範に使用する必要があるユース ケースは他にもあるかもしれません。この種のグローバルな状態を持つことは一般的に受け入れられるのでしょうか、それともそのような目標を達成するための代替手段はありますか? Enum のような単純な値オブジェクトの依存性注入がオーバーヘッドになるような状況で、クラス メタデータを提供する別のクラスを使用するのは好きではありません。