Enums no PHP 8.1

Na versão 8.1 do PHP foi introduzido os enums, um recurso já presente em outras linguagens e bastante aguardado no PHP. Os enums nos permite criar uma estrutura parecida com uma classe, mas que armazena somente um conjunto de valores pré-estabelecidos. Antes dele era comum utilizarmos classes com constantes publicas como se fossem enums, mas esta abordagem traz alguns efeitos que podem não ser interessante.

Vamos a um exemplo que talvez você já tenha feito algo parecido, temos algum atributo ou parâmetro de um método ou função que precisa ser limitado a algum conjunto de valores, então criamos uma classe com as constantes destes valores que iremos aceitar e os utilizamos nos locais desejados.

declare(strict_types=1);

class Turno{
    public const MANHA = 'MANHA'; 
    public const TARDE = 'TARDE'; 
    public const NOITE = 'NOITE'; 
}

//--------
// executa.php
function cadastrarDisciplina(string $nome, string $turno){
    // faz alguma coisa
    echo $nome . ' - ' . $turno . PHP_EOL;
}

cadastrarDisciplina('PHP Básico', Turno::NOITE);

Ao tipar o parâmetro de uma função devemos tipá-lo como string que é o valor contido nas constantes da classe. Desta forma o método não está validando que o valor passado recebe realmente um dos três valores do conjunto de valores válidos, mas somente um string, o que pode ser facilmente burlado.

cadastrarDisciplina('PHP Básico', 'MADRUGADA');

A chamada de função acima é válida e não irá gerar um erro no PHP, mas certamente irá gerar um erro de lógica em seu programa.

Outro problema é que cada um dos valores constantes é um string e pode ser manipulado como uma, podendo ser por exemplo concatenado.

$turno = Turno::MANHA . ' cedo!';
var_dump($turno);

//saída: string(11) "MANHA cedo!"

Esse tipo de problema pode ocasionar bugs difíceis de encontrar. Então utilizando enums isto fica um pouco melhor.

Usando Enums

enum Turno{

    case MANHA;
    case TARDE;
    case NOITE;

}

Declaramos um enum da mesma forma de uma classe, mas utilizando a palavra chave enum, e cada valor do conjunto de valores válidos declaramos utilizando a declaração case.

Vamos ao exemplo anterior, mas agora com uma enumeração.

function cadastrarDisciplina(string $nome, Turno $turno){
    // faz alguma coisa
    echo $nome . ' - ' . $turno->name . PHP_EOL;
}

cadastrarDisciplina('PHP Básico', Turno::NOITE);

Se tentarmos agora passar um valor que não for do tipo enum Turno ele irá gerar um erro.

cadastrarDisciplina('PHP Básico', 'MADRUGADA');

// Erro gerado
//
// Fatal error: Uncaught TypeError: cadastrarDisciplina(): Argument #2 ($turno) must be of type Turno, string given, called in /application/index.php on line 12 and defined in /application/index.php:7
// Stack trace:
// #0 /application/index.php(12): cadastrarDisciplina('PHP B\xC3\xA1sico', 'MADRUGADA')
// #1 {main}
//   thrown in /application/index.php on line 7

Como vemos foi gerado um erro informando que o segundo argumento deve ser um Turno e foi passada uma string.

Agora ao tentar concatenar um dos valores do enum irá gerar um erro.

$turno = Turno::MANHA . ' cedo!';

// Erro gerado
//
//Fatal error: Uncaught Error: Object of class Turno could not be converted to string in /application/index.php:14

Isso ocorre porque cada um dos valores do enum é “objeto” do tipo.

$a = Turno::NOITE;
$b = Turno::NOITE;

echo $a === $b; // true

echo $a instanceof Turno; // true

Caso necessitarmos obter nome do valor do enum como string podemos utilizar a propriedade name.

var_dump( Turno::TARDE->name );

// saída
// string(5) "TARDE"

Por padrão uma enumeração não tem valores escalares, que podem ser inseridos em um banco de dados por exemplo. Mas podemos facilmente defini-los na criação da enum simplesmente definindo o tipo de dado junto a declaração do nome do enum (Turno:string) e atribuindo o valor escalar correspondente na declaração case. Lembrando que estes valores devem ser únicos.

enum Turno:string{

    case MANHA = 'Manhã';
    case TARDE = 'Tarde';
    case NOITE = 'Noite';

}

Para acessar o valor escalar de um enum basta utilizar o atributo value

echo Turno::TARDE->name . PHP_EOL; // TARDE
echo Turno::TARDE->value . PHP_EOL; // Tarde

Para obter uma lista de todos os valores de uma enumeração podemos chamar o método cases, que irá retornar um array contento todos os valores da enumeração.

var_dump( Turno::cases());

// Saída

// array(3) {
//   [0]=>
//   enum(Turno::MANHA)
//   [1]=>
//   enum(Turno::TARDE)
//   [2]=>
//   enum(Turno::NOITE)
// }

Caso seja necessário transformar um valor escalar em um enum, podemos utilizar o método from passando como argumento o valor escalara e ele irá retornar o valor enum.

$turno = Turno::from('Manhã');

var_dump($turno);

// Saída
// enum(Turno::MANHA)

Podemos também definir métodos dentro de um enumeração, para isso criamos um método como se fosse em uma classe normal acessando o valor de instancia da enumeração através do $this se necessário.

enum Turno:string{

    case MANHA = 'Manhã';
    case TARDE = 'Tarde';
    case NOITE = 'Noite';


    public function saudacao(): string
    {
        return match($this) {
            Turno::MANHA => 'Bom dia!',
            Turno::TARDE => 'Boa Tarde!',
            Turno::NOITE => 'Boa Noite!',
        };
    }
}

Então podemos chamar o método normalmente.

$turno = Turno::TARDE;

echo $turno->saudacao();

Bom era isso, enumerações são um boa forma de manter seu código com os dados mais integros e legíveis. Outros exemplos e mais detalhes podem ser visto na própria documentação do PHP.

T++.