Enumerados, desde C# a PHP

Estructura nativa en algunos lenguajes de programación y aún pendiente en otros. (2020-08-18)

Enumerados, desde C# a PHP

Los enumerados es un concepto que generalmente usamos en nuestras aplicaciones, el mismo refiere a:

A un listado de valores bien conocidos referibles bajo un mismo termino, que no cambian y son de solo lectura.

Estos suelen utilizarse para referir a diferentes tipos de estados, niveles o tipos vinculados a una entidad(objetos) en la aplicación. Como valor agregado, nos permite tipar en forma fuerte los parámetros de entrada a métodos/funciones, así como su retorno.

En el mundo C Sharp

La palabra reservada para crear un enumerado en este lenguaje es enum y se separan los distintos valores que este contiene/puede tomar en base a una coma.

enum VulnerabilitySeverity 
{
    None,// 0
    Low, // 1
    Medium, // 2
    High,// 3
    Critical // 4
}

Por defecto, el 1er elemento del enumerado referencia al valor entero 0 y los sucesivos crecen de uno en uno, sin embargo es posible asignar valores enteros específicos a cada ítem, o bien, a un solo de ellos(con la consecuencia de que la numeración de los sucesivos de este se retoma a partir de este ultimo valor personalizado que colocamos).

enum HttpResponseCode
{
    ServerError = 500,
    Unauthorized = 401,
    Success = 200
}

enum SpecialMonths
{
  January = 1,
  February, // 2
  June = 6,
  July // 7
}

Ademas, podemos contar con la posibilidad de convertir a entero en forma explícitos un elemento del enumerado.

int responseCode = (int)HttpResponseCode.Success; 

En el mundo PHP

Para no perder la costumbre, es otra de las estructuras básicas que le faltan incorporar nativamente al lenguaje, incluso en su versión actual más reciente liberada(7.4), me animo a decir que no eh visto nada que lo cubra en la versión 8.0 que ya esta siendo desarrollada y con algún Alpha realease para probar ciertas prestaciones nuevas.

Si bien ya hay algunos paquetes que pueden utilizar para surcir esto instalables vía composer, dejo este link para verificarlo.

De todas formas, si bien podemos ir por cualquiera de estos que cumpla con lo básico que buscamos, muchos de ellos utilizan muchos métodos mágicos, reflection,etc para algo que no es complejo en sí mismo, que te esta forzando a agregar otra dependencia y puede hacer que su analizador estático/autocompletado en tu IDE preferido no sepa que responderte (a menos claro que haya algún plugin/add-on para el paquete en tu IDE).

En este caso prefiero mostrarles una implementación muy sencilla pero funcional, viendo que implica replicar cada uno de estos aspectos que comentamos anteriormente ya nos brinda C#, inclusive alguno más.

Empecemos por lo de que posee un listado de valores constantes:

class HttpRequestCode {
    public const ServerError = 500;
    public const Unauthorized = 401;
    public const Success = 200;
}

Aquí a simple vista nos acercamos en parte (visualmente) a un enumerado, aunque las constantes por si mismas no terminan de ser útiles y funcionan como medio para referir de una única forma al valor correspondiente a esa key. Nos esta faltando el que pueda tener cada instancia su propio estado/valor.

<?php
abstract class  GenericEnum {
    protected $value;

    public  static  function  getAllValues():  array {
        $enum =  new  \ReflectionClass(get_called_class());

        return $enum->getConstants();  
    }

    public  static  function  hasValue($valueToCheck) :bool {
        return  in_array( $valueToCheck, self::getAllValues());    
    }

    public function value() {
        return $this->value;
    }

    public function equals(self $otherEnum) :bool {
        return get_class($this) === get_class($otherEnum) && $this->value() === $otherEnum->value();
    }
}

Entonces teniendo esta clase de base, podemos hacer lo siguiente en nuestra clase de códigos de respuesta aprovechando de usar constructores semánticos para cada uno de los códigos posibles:

use \InvalidArgumentException;

class HttpRequestCode extends GenericEnum {
    public const ServerError = 500;
    public const Unauthorized = 401;
    public const Success = 200;

    public function __construct($value) {
        if(!self::hasValue($value)) {
            throw new InvalidArgumentException();
        }
        $this->value = $value;
    }

    static public function ServerError() :self { return new self(self::ServerError); }

    static public function Unauthorized() :self { return new self(self::Unauthorized); }

    static public function Success() :self { return new self(self::Success); }
}

La duda que de momento puede estar surgiendo es el porque no utilice el type hint sobre el $value en el constructor o en el return de la función value, esto se debe a que puede ser útil llegar a tener enumerados asociados a valores numéricos como en C# pero en mi experiencia el poder contar también con enumerados de valores strings también me ah sido beneficioso. De todas formas nuestro constructor nos asegura que únicamente podremos concebir objetos/enums que sean un valor válido para cada caso concreto y la función equals permite compararlos sin mayor problemas (considerando la situación de teniendo varios enums heredando de nuestro generic y que esto no permita compararlos equivocamente obteniendo falsos positivos).

Algunos ejemplos de como se utilizaría:

<?php
$success = HttpRequestCode::Success();
$error = HttpRequestCode::ServerError();

$error->equals($success); 
// false

HttpRequestCode::getAllValues();
// [500, 401, 200]

HttpRequestCode::hasValue(301);
// false

$success_code = new HttpRequestCode(200);
$success_code->equals($success);
// true

$not_valid = new HttpRequestCode(301);
// thrown InvalidExceptionArgument !