Enumeradores en PHP Parte I: Empecemos por lo básico > Blog | Josep Salvà - Desarrollo Web

La palabra de Wifft

En PHP 8.1, debutó una de las características más interesantes (y esperadas) que se han añadido al lenguaje en los últimos tiempos: los enumeradores. Yo personalmente los llevo usando desde hace que se implementaron y hoy en dia me cuesta evitar no usarlos para enumerar todo lo enumerable 🤣

Pongamos un ejemplo sencillo. Imaginemos que estamos desarrollando un cliente HTTP que interactúa con cierta API REST. Definimos un método genérico para realizar peticiones mediante cURL.

private function makeRequest(string $endpoint, string $method) : ?object
{
    try {
        $ch = curl_init($this->baseUrl . '/' . $endpoint);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt(
            $ch,
            CURLOPT_HTTPHEADER,
            [
                'Content-Type: application/json',
                'Authorization: Bearer ' . $this->token
            ]
        );

        $result = curl_exec($ch);

        curl_close($ch);
        unset($ch);

        $query = json_decode($result);

        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new Exception(json_last_error_msg());
        }

        return $query;
    } catch (Throwable $e) {
        Log::error($e->getMessage());

        return null;
    }
}

Como podemos observar a dicho método makeRequest() le proporcionamos un parámetro de tipo string llamado $method. El parámetro consiste en una cadena de texto cuyo valor es el método HTTP que pretendemos utilizar a la hora de realizar dicha petición (GET, POST, PUT, PATCH...), que será posteriormente recogido por la función curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method).

Pero... ¿Qué tiene que ver todo esto con los enumeradores? Pues muy sencillo. Antes de PHP 8.1, lo más probable es que proporcionáramos el string directamente o definiéramos una clase final llena de constantes que contuvieran cada valor posible a utilizar:

final class HttpMethod
{
    public const GET = 'GET';
    public const POST = 'POST';
    public const PUT = 'PUT';
    public const PATCH = 'PATCH';
    public const DELETE = 'DELETE';
}

Y la invocación del método fuera algo así como:

$this->makeRequest("createUser", HttpMethod::POST);

Hasta en PHP 8.1. Como ya he comentado antes, PHP 8.1 incorpora el concepto de enumeradores (enums). Básicamente, los enum son una capa de abstracción por encima de las clases. Veamos como se definiría el ejemplo anterior si de un enum se tratara.

enum HttpMethod
{
    case GET;
    case POST;
    case PUT;
    case PATCH;
    case DELETE;
}

Simple, ¿verdad? Incluso algunos dirían que es una mezcla entre una declaración de clase y de switch.

Pero... ¿cómo se implementaría sobre el ejemplo práctico que hemos visto antes? Pues de la siguiente manera:

private function makeRequest(string $endpoint, HttpMethod $method) : ?object
{
    try {
        $ch = curl_init($this->baseUrl . '/' . $endpoint);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method->name);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        ...
    } catch (Throwable $e) {
        Log::error($e->getMessage());

        return null;
    }
}

Y dicho método pasaría a ser invocado con los parámetros:

$this->makeRequest("createUser", HttpMethod::POST);

Vemos que la función receptora del parámetro curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method->name) accede a una propiedad llamada name pero... ¿De dónde sale?

Si realizamos un dump sobre el parámetro HttpMethod::POST nos devuelve:

 App\Enums\HttpMethod {#3523
     +name: "POST",
 }

Es decir, como si fuera la instancia de un objeto con una única propiedad accesible llamada name, ¿verdad? Pues ahí reside el truco. Al mencionar que un enum es una capa de abstracción por encima de los objetos, me refería precisamente a que en realidad generan un array de objetos reales, uno por cada elemento enumerado. Podemos obtener dicho array fácilmente accediendo al método estático ::cases() implementado de manera implícita en todos los enumeradores.

>>> HttpMethod::cases();
[
    App\Enums\HttpMethod {#3525  
        +name: "GET",
    },
    App\Enums\HttpMethod {#3523  
        +name: "POST",
    },
    App\Enums\HttpMethod {#3526  
        +name: "PUT",
    },
    App\Enums\HttpMethod {#3521  
        +name: "PATCH",
    },
    App\Enums\HttpMethod {#3518  
        +name: "DELETE",
    },
]

En entradas posteriores, veremos maneras mucho más avanzadas de aprovechar eso, pero todo a su tiempo. Hasta aquí esta primera parte. El próximo día veremos como asignar valores más específicos que los generados automáticamente por el nombre dado.