É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.
Titre
Java Platform Standard Ed. 6
Editeur
docs.oracle.com
Date
chapter
Class Enum<E extends Enum<E>>
Titre
Manuel PHP
Auteurs
Mehdi ACHOUR
Auteurs
Friedhelm BETZ
Auteurs
Antony DOVGAL
Auteurs
Nuno LOPES
Auteurs
Hannes MAGNUSSON
Auteurs
Georg RICHTER
Auteurs
Damien SEGUY
Auteurs
Jakub VRANA
Editeur
PHP.net
Date
Chapitre
The SplEnum class