Énumération en PHP

Depuis Java 1.5, nous dispositions d'objets d'énumération qui facilitent grandement la vie. Voyons comment retrouver ce comportement en PHP...

Une énumération java simple pourrait s'écrire comme suit :

public enum MyEnum
{
    FOO("foo"),
    BAR("bar"),
    BAZ("baz");
    
    private String value;
    
    MyEnum(String value)
    { 
        this.value = value;
    }
    
    public String value()
    { 
        return value;
    }
};

// Utilisation
MyEnum foo = MyEnum.FOO;
String value = foo.value();
Exemple d'une énumération en Java.

Une première implémentation, très simple, consisterait à « mimer » le comportement d'énumération avec des constantes de classes, voire même d'une interface :

class MyEnum
{
    const FOO = 'foo';
    const BAR = 'bar';
    const BAZ = 'baz';
}

// Utilisation
$foo = MyEnum::FOO
Émulation simple d'une énumération avec des constantes de classe.

Mais, évidemment, cela ne fournit en rien les fonctionnalités d'une énumération java. Alors, à la fois par simple jeu mais aussi par défi, j'ai essayé d'écrire une classe PHP qui pourrait se rapprocher de ce que l'on peut faire en Java.


namespace rnb\core;

/**
 *  Classe simulant plus ou moins les objets Enum Java
 *
 *  @class Enum
 */
class Enum
{
    /**
     *  Collection des listes de constantes des classes étendant Enum.
     *  @type Array<String, Array<String, mixed>>
     */
    protected static $list = array();
    
    /**
     *  Liste des instances des valeurs de chaques classes étendant Enum.
     *  @param Array<String, Array<String, Enum>>
     */
    protected static $inst = array();
    
    /**
     *  Valeur de la constante enum.
     *  @type mixed
     */
    private $value;
    
    /**
     *  Nom de la constante enum.
     *  @type String
     */
    private $name;
    
    /**
     *  Index de la constante enum telle que déclarée dans la classe.
     *  @type int
     */
    private $ordinal = -1;
    
    /**
     *  Constructeur.
     *
     *  La signature est différente de l'enum Java, qui prend en paramètre name 
     *  et ordinal.
     *
     *  @param {String} $name
     *  @param {mixed} $value
     */
    protected function __construct($name, $value)
    {
        $this->name = $name;
        $this->value = $value;
    }
    
    /**
     *  Retourne le nom de la constante enum.
     *
     *  @return {String}
     */
    public final function name()
    {
        return $this->name;
    }
    
    /**
     *  Retourne la valeur de la constante enum.
     *
     *  @return {mixed}
     */
    public final function value()
    {
        return $this->value;
    }
    
    /**
     *  Retourne l'index de la constante enum.
     *
     *  @return {int}
     */
    public final function ordinal()
    {
        if ($this->ordinal === -1) {
            $this->ordinal = array_search($this->name, 
                array_keys(self::$list[get_called_class()]));
        }
        return $this->ordinal;
    }
    
    /**
     *  Comparaison de 2 objets enum.
     *
     *  @param  {Enum} $e Objet à comparer à l'objet courant.
     *  @return {Boolean} Si les deux objets sont identiques.
     */
    public final function equals(Enum $e)
    {
        return (get_called_class() === get_class($e) && $this->name() === $e->name() &&
            $this->value() === $e->value()) ? true : false;
    }
    
    /**
     *  Comparaison de l'ordre de deux constantes enum de même type.
     *
     *  @param  {Enum} $e La constante enum à comparer.
     *  @return {int} -1 si la constante enum courante est inférieure, 1 si elle
     *  est supérieure ou 0 si elle est égale à l'objet comparé.
     */
    public final function compareTo(Enum $e)
    {
        if (get_called_class() !== get_class($e)) {
            return 0;
        }
        if ($this->ordinal() < $e->ordinal()) {
            return -1;
        }
        return $this->ordinal() > $e->ordinal() ? 1 : 0;
    }
    
    /**
     *  Méthode magique pour retourner une instance de l'énumérateur lors de 
     *  l'appel d'une méthode statique portant le nom d'une valeur.
     *
     *  @param  {String} $name Nom de la valeur.
     *  @param  {Array} $args Liste des paramètres (devrait être null).
     *  @return {Enum}
     *  @throw  {InvalidArgumentException} Si $name n'est pas une constante enum
     *  de l'objet.
     */
    public static final function __callStatic($name, $args)
    {
        $class = get_called_class();
        if (!isset(self::$list[$class])) {
            $reflect = new \ReflectionClass($class);
            self::$list[$class] = $reflect->getConstants();
        }
        if (!isset($name, self::$list[$class])) {
            throw new \InvalidArgumentException($name.' is not a constant of '.$class);
        }
        if (!isset(self::$inst[$class][$name])) {
            self::$inst[$class][$name] = new $class($name, self::$list[$class][$name]);
        }
        return self::$inst[$class][$name];
    }
    
    /**
     *  Méthode magique retournant le nom de la constante enum.
     *
     *  @return {String}
     */
    public function __toString()
    {
        return $this->name;
    }
    
    /**
     *  Méthode magique pour interdire le clonage
     *
     *  @throw Exception.
     */
    public final function __clone()
    {
        throw new \Exception('Cannot duplicate an Enum.');
    }
    
    /**
     *  Retourne la constante enum de type $class portant le nom $name.
     *
     *  @param  {String} $class Nom de la classe de type Enum.
     *  @param  {String} $name Nom de la constante.
     *  @return {Enum|null} La constante enum correspondante ou null.
     *  @throw  {InvalidArgumentException} si l'un des paramètre est null
     */
    public static final function valueOf($class, $name)
    {
        if (is_null($class) || is_null($name)) {
            throw new InvalidArgumentException('Parameter is null');
        }
        if (!is_subclass_of($class, 'Enum')) {
            throw new \InvalidArgumentException($class . ' does not extend Enum');
        }
        return $class::$name();
    }
}
Exemple d'une classe PHP Enum mimant le comportement des énumérations Java.

A noter qu'il existe aussi un type SPL, SplEnum, non fournit en standard, sensé permettre de créer des objets d'énumération mais dont l'utilisation n'est pas évidente :

class MyEnum extends SplEnum {
    const FOO = 'foo';
    const BAR = 'bar';
    const BAZ = 'baz';
}

// Utilisation
$foo = new MyEnum(MyEnum::FOO);
utilisation de la class SplEnum.

Java Platform Standard Ed. 6. docs.oracle.com, . Class Enum<E extends Enum<E>>

ACHOUR, Mehdi ; BETZ, Friedhelm ; DOVGAL, Antony, et al.. Manuel PHP. PHP.net, . The SplEnum class