É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
chapter
Class Enum<E extends Enum<E>>
Éditeur
docs.oracle.com
Date
Titre
Manuel PHP
Chapitre
The SplEnum class
Auteurs
Achour MEHDI
Auteurs
Betz FRIEDHELM
Auteurs
Dovgal ANTONY
Auteurs
Lopes NUNO
Auteurs
Magnusson HANNES
Auteurs
Richter GEORG
Auteurs
Seguy DAMIEN
Auteurs
Vrana JAKUB
Editeur
PHP.net
Date