f in x
PHP 8.1 Enums — Pure, Backed, and Methods for Cleaner Code
> cd .. / HUB_EDITORIALE
Sviluppo di siti web

PHP 8.1 Enums — Pure, Backed, and Methods for Cleaner Code

[2026-06-21] Author: Ing. Calogero Bono

Have you ever opened a project and found magic strings like 'pending', 'shipped', 'delivered' scattered across dozens of if statements? Every time you add a new status, you have to hunt down all the comparison points, hoping you didn't miss any. That's the problem PHP 8.1 enums solve once and for all. At Meteora Web, we use them in every Laravel or WooCommerce project we touch. Because a wrong order status or subscription state costs much more than a few well-written lines of code.

How does a pure enum work in PHP 8.1? No hidden values

A pure enum defines a closed set of possible cases. No associated values (neither integers nor strings). Use it when the identity of the case is enough, without needing an external representation.

<?php

declare(strict_types=1);

enum OrderStatus
{
    case Pending;
    case Processing;
    case Shipped;
    case Delivered;
    case Cancelled;
}

Each case is a singleton. Compare with ===, not strings. Trying to pass a non-existent value gives a compile-time error, not runtime. This eliminates typos in status names.

Using pure enum in your code: a concrete example

Imagine a function that updates an order status. With strings you'd write if ($status === 'shipped'). With a pure enum:

Sponsored Protocol

function updateStatus(OrderStatus $newStatus): void
{
    if ($newStatus === OrderStatus::Shipped) {
        // send notification
    }
    $this->status = $newStatus;
}

Type hinting protects you: nobody can pass a random string. If tomorrow you add Returned, your IDE shows everywhere it's used. No more hunting magic values.

Common mistake to avoid: using match on an enum without covering all cases. PHP 8.1 throws an UnhandledMatchError at runtime. Better use a default branch or a switch with default.

Backed enum in PHP 8.1: when you need an integer or string for the database

The reality of SMEs is existing databases, third-party APIs, numeric values in archives. A pure enum is nice, but if you need to save the status in a VARCHAR or INT column, you need a backed enum. Each case is associated with a scalar value (string or integer).

enum OrderStatusBacked: string
{
    case Pending = 'pending';
    case Processing = 'processing';
    case Shipped = 'shipped';
    case Delivered = 'delivered';
    case Cancelled = 'cancelled';
}

// With integers
enum UserRole: int
{
    case Admin = 1;
    case Editor = 2;
    case Subscriber = 3;
}

From database to code with from() and tryFrom()

When reading from the DB, use OrderStatusBacked::from($value) — throws ValueError if the value doesn't exist. Or tryFrom() returning null. Choose based on data cleanliness: if the data is clean, from is stricter; if legacy values may appear, use tryFrom with error handling.

Sponsored Protocol

$dbStatus = 'shipped'; // from database
$statusEnum = OrderStatusBacked::from($dbStatus);
echo $statusEnum->name; // 'Shipped'
echo $statusEnum->value; // 'shipped'

This eliminates manual mapping arrays and reduces lines in repositories. We've seen projects with hundreds of mapping function calls — enums remove them all.

Performance note: backed enums are slightly slower than pure ones due to conversion, but the difference is negligible in real applications. Type safety pays a hundred times over.

What methods can you add to PHP 8.1 enums? Encapsulated behavior

PHP enums aren't simple lists — they can have methods. This lets you encapsulate logic that would otherwise end up in helpers or scattered static functions.

enum OrderStatusBacked: string
{
    case Pending = 'pending';
    case Processing = 'processing';
    case Shipped = 'shipped';
    case Delivered = 'delivered';
    case Cancelled = 'cancelled';

    public function label(): string
    {
        return match($this) {
            self::Pending => 'Awaiting payment',
            self::Processing => 'In progress',
            self::Shipped => 'Shipped',
            self::Delivered => 'Delivered',
            self::Cancelled => 'Cancelled',
        };
    }

    public function canCancel(): bool
    {
        return match($this) {
            self::Delivered, self::Cancelled => false,
            default => true,
        };
    }

    public function nextStatuses(): array
    {
        return match($this) {
            self::Pending => [self::Processing, self::Cancelled],
            self::Processing => [self::Shipped, self::Cancelled],
            self::Shipped => [self::Delivered, self::Cancelled],
            default => [],
        };
    }
}

Now each status knows its behavior. No if statements scattered in controllers or services. Call $order->status->label() to get the translated label. Call $order->status->canCancel() to decide if the cancel button should show. All in one place.

Sponsored Protocol

Static methods on enums for factories or queries

You can also add static methods to create enums from complex input or return arrays of cases:

Sponsored Protocol

enum UserRole: int
{
    case Admin = 1;
    case Editor = 2;
    case Subscriber = 3;

    public static function valuesForForm(): array
    {
        return array_combine(
            array_map(fn($case) => $case->value, self::cases()),
            array_map(fn($case) => $case->name, self::cases())
        );
    }
}
// Usage: UserRole::valuesForForm() => [1 => 'Admin', 2 => 'Editor', 3 => 'Subscriber']

How does an enum handle serialization compared to constant arrays? Better safety and maintainability

Before PHP 8.1, many used class constant arrays or abstract classes with constants. It works, but has issues: constants are not type-safe (you can pass any integer), no advanced autocompletion, and more verbose code.

FeatureConstant arrayPHP 8.1 enum
Type safetyNo (int/string)Yes (specific type)
IDE autocompletionLimitedFull (cases and methods)
JSON serializationManualAutomatic (for backed, returns value)
Associated methodsNo (need static helpers)Yes (inside enum)

JSON serialization of a backed enum directly returns the scalar value. For pure enums, you need to implement JsonSerializable. Want to customize? Override jsonSerialize():

Sponsored Protocol

enum OrderStatus: string implements \JsonSerializable
{
    case Pending = 'pending';
    // ...

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

What to do next: adopt enums in your next project

  1. Identify closed domains in your code: order statuses, user roles, product types, sales channels. Every finite set of values is a candidate.
  2. Gradually replace constants and magic strings with enums. Start with one model (e.g. OrderStatus) and verify tests pass.
  3. Move logic into enum methods — labels, permissions, transitions. You'll see the code become more declarative and easier to modify.
  4. Use backed enums when interfacing with external databases or APIs. For purely internal logic, pure enums work fine.

At Meteora Web, we converted a legacy order management system with over 30 scattered statuses into a single enum with methods. The code went from 2000 lines of if-else to 300 clean lines. Maintenance cost plummeted. Now it's your turn.

For more on the modern PHP ecosystem, check out our comprehensive guide on Advanced PHP 8.

Ing. Calogero Bono

> AUTHOR_EXTRACTED

Ing. Calogero Bono

Ingegnere Informatico, co-fondatore di Meteora Web. Esperto in architetture software, sicurezza informatica e sviluppo sistemi scalabili.
[ Read Full Dossier ]

> METEORA_WEB // DIGITAL AGENCY

We build the digital presence your business deserves.

Websites, social media, online advertising, e-commerce and high-performance hosting, engineered with method by computer engineers in Sciacca, for all of Italy.

> MW_JOURNAL

> READ_ALL()