← Retour aux articles
PHP

Les énumérations en PHP : bien plus qu'une liste de constantes

Par Mendel · 01/10/2026 à 00:31

Les énumérations en PHP : bien plus qu'une liste de constantes

Avant PHP 8.1, représenter un ensemble fini de valeurs (un statut, un rôle, une devise) passait presque toujours par des constantes de classe. Ça fonctionnait, mais rien n'empêchait de passer n'importe quelle chaîne là où un statut était attendu. Les énumérations, arrivées avec PHP 8.1, règlent ce problème, et elles offrent bien plus qu'une simple liste de valeurs.

Le problème des constantes

Voici l'approche « à l'ancienne » :

class ArticleStatus
{
    public const DRAFT = 'draft';
    public const PUBLISHED = 'published';
}

function publish(string $status): void
{
    // ...
}

publish(ArticleStatus::PUBLISHED); // OK
publish('publihsed');              // Aucune erreur !

Le type du paramètre est string : PHP accepte n'importe quelle chaîne, y compris une faute de frappe. L'erreur ne sera découverte que bien plus tard, souvent en production.

Une première énumération

Avec une énumération, le statut devient un type à part entière :

enum ArticleStatus
{
    case Draft;
    case Scheduled;
    case Published;
}

function publish(ArticleStatus $status): void
{
    // ...
}

publish(ArticleStatus::Published); // OK
publish('published');              // TypeError

Impossible désormais de passer autre chose qu'un des trois cas prévus. Votre éditeur propose les valeurs possibles en autocomplétion, et un outil d'analyse statique comme PHPStan détecte les erreurs avant même l'exécution.

Chaque cas est un objet unique : on les compare simplement avec ===.

$status = ArticleStatus::Draft;

var_dump($status === ArticleStatus::Draft); // true
var_dump($status instanceof ArticleStatus); // true

Les énumérations « backed »

Une énumération comme celle-ci est dite pure : ses cas n'ont pas de valeur associée. Or, pour enregistrer un statut en base de données ou l'envoyer dans une API, il faut une valeur scalaire. On utilise alors une énumération backed, où chaque cas est associé à une chaîne ou à un entier :

enum ArticleStatus: string
{
    case Draft = 'draft';
    case Scheduled = 'scheduled';
    case Published = 'published';
}

echo ArticleStatus::Published->value; // published

Et pour faire le chemin inverse, deux méthodes sont fournies :

ArticleStatus::from('draft');      // ArticleStatus::Draft
ArticleStatus::from('archived');   // ValueError

ArticleStatus::tryFrom('draft');    // ArticleStatus::Draft
ArticleStatus::tryFrom('archived'); // null
  • from() lève une exception si la valeur est inconnue : idéal quand la valeur est censée être valide, par exemple lue depuis votre propre base ;
  • tryFrom() renvoie null : parfait pour une valeur qui vient de l'extérieur, comme un paramètre d'URL, qu'on veut valider sans exception.

Les énumérations backed ont un autre avantage : json_encode() les convertit automatiquement en leur valeur.

echo json_encode(['status' => ArticleStatus::Published]);
// {"status":"published"}

Lister tous les cas

La méthode statique cases() renvoie tous les cas, dans l'ordre de déclaration :

foreach (ArticleStatus::cases() as $status) {
    echo $status->name . ' => ' . $status->value . PHP_EOL;
}
// Draft => draft
// Scheduled => scheduled
// Published => published

Très pratique pour construire la liste déroulante d'un formulaire, sans jamais oublier de mettre à jour une liste écrite à la main.

Des méthodes dans les énumérations

C'est là que les énumérations dépassent vraiment les constantes : elles peuvent avoir des méthodes. Toute la logique liée à un statut peut ainsi vivre au même endroit.

enum ArticleStatus: string
{
    case Draft = 'draft';
    case Scheduled = 'scheduled';
    case Published = 'published';

    public function label(): string
    {
        return match ($this) {
            self::Draft => 'Brouillon',
            self::Scheduled => 'Programmé',
            self::Published => 'Publié',
        };
    }

    public function isVisible(): bool
    {
        return $this === self::Published;
    }
}

echo ArticleStatus::Scheduled->label(); // Programmé

L'expression match est le complément naturel des énumérations. Contrairement à un switch, elle compare avec === et lève une UnhandledMatchError si aucun cas ne correspond. Et surtout, PHPStan vous signale un match qui oublie un cas : le jour où vous ajouterez un statut Archived, toutes les méthodes à mettre à jour seront repérées immédiatement.

Constantes, méthodes statiques et interfaces

Une énumération peut aussi contenir des constantes, des méthodes statiques, et implémenter des interfaces :

interface HasColor
{
    public function color(): string;
}

enum ArticleStatus: string implements HasColor
{
    case Draft = 'draft';
    case Scheduled = 'scheduled';
    case Published = 'published';

    public const DEFAULT = self::Draft;

    public static function visibleValues(): array
    {
        return array_map(
            fn (self $status) => $status->value,
            array_filter(self::cases(), fn (self $status) => $status->isVisible()),
        );
    }

    public function isVisible(): bool
    {
        return $this === self::Published;
    }

    public function color(): string
    {
        return match ($this) {
            self::Draft => 'gray',
            self::Scheduled => 'orange',
            self::Published => 'green',
        };
    }
}

Une interface commune permet par exemple d'afficher un badge coloré pour n'importe quelle énumération qui l'implémente, qu'il s'agisse d'un statut d'article, de commande ou de ticket.

Ce que les énumérations ne peuvent pas faire

Quelques limites à connaître, qui sont en réalité des choix volontaires :

  • Pas de propriétés. Une énumération n'a pas d'état : chaque cas est une valeur fixe, identique partout dans l'application.
  • Pas de new. Les seules instances possibles sont les cas déclarés.
  • Pas de clés de tableau. Un cas est un objet, il ne peut pas servir de clé : utilisez sa ->value, ou un WeakMap si vous avez besoin d'associer des données à chaque cas.
  • Pas d'héritage. Une énumération ne peut ni étendre une autre classe, ni être étendue. Les interfaces et les traits (sans propriétés) restent possibles.

Et avec les frameworks ?

Les énumérations sont aujourd'hui bien prises en charge par l'écosystème. Doctrine sait par exemple enregistrer une énumération backed dans une colonne et la reconvertir automatiquement à la lecture : c'est ainsi que fonctionne le statut des articles de ce blog. Les composants de formulaire, de sérialisation et de validation des principaux frameworks les gèrent également.

Conclusion

Dès que vous écrivez une série de constantes qui représentent les valeurs possibles d'une même chose, posez-vous la question : ne serait-ce pas une énumération ? Vous y gagnez un typage strict, l'autocomplétion, une validation gratuite avec from() et tryFrom(), et surtout un endroit unique pour toute la logique associée.

Et vous, quelle est la première énumération que vous avez créée dans vos projets ? Dites-le en commentaire !

Commentaires (0)

Connecte-toi ou crée un compte pour laisser un commentaire.

Aucun commentaire pour le moment.