2

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.

  • you can populate the cache as a protected static property of the subclass... this keeps things out of global space but will allow your caching mechanism – Orangepill Jul 26 '13 at 22:25
  • The problem here is that I do not want the subclass to be able to modify the valid constants, therefore the property is private. – user2624343 Jul 26 '13 at 22:29
  • actually it looks like you where trying that ... to fix your implementation define `private static $constants = array();` on the subclass and change your references from `self::$constants` to `static::$constants` then each subclass can have it's own cache – Orangepill Jul 26 '13 at 22:30
  • Defining `private static $constants` on the subclass does not have any effect when accessing the subclasses' `$constants` from the abstract class. This results in a PHP error and making it protected in the abstract class is not an option. Besides that, I do not want to rely on the subclass defining the `$constants` again. I would need a contract for that, which is not possible. – user2624343 Jul 26 '13 at 22:41

0 Answers0