PHP global state acceptable for runtime information like class metadata?

186 Views Asked by At

I'm struggling with using global state to store runtime information about class metadata in PHP. As global state is considered "bad" because of the obvious reasons I would like to avoid it as much as I can. Though, I still don't know in which situations it is really acceptable and in which not. To provide a concrete example, let's take an Enum type implementation in PHP, where each concrete Enum type extends an abstract Enum type, that reads the subclasses' constants as valid Enum values using reflection.

<?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);

The possible problem I see here is the resolveConstants() method which uses reflection to resolve the subclasses' constants. Consider a situation where a huge amount of Enum instances have to be created at runtime. Using reflection here on each instance could have a serious performance impact, therefore "lazy loading" the class metadata only on the first instance of a certain Enum type seems to be a good approach. BUT I have to use global state for this to accomplish, which seems wrong , too. Maybe this is not the best example, but it demonstrates my concern about performance issue. There may be other use cases where extensive use of reflection and class metadata introspection has to be made in order to set an object into state. Is it generally speaking acceptable to have this kind of gloabl state or is there any alternative to achieve such a goal? I don't like to use a separate class that provides the class metadata in such a situation as dependency injection for simple value objects like an Enum seems to be overhead.

0

There are 0 best solutions below