Question

PHP Enumerations: How to Model Enums Before and With Native Enums

phpenumeration

Question

In PHP, there was historically no native support for enumerations like the enum feature in Java. When you want a fixed set of predefined values, plain constants can work, but they may cause naming collisions if not grouped well, and older styles of constants can feel too global. Arrays avoid some of that, but they are loose structures, can be modified at runtime, and IDEs often cannot autocomplete their allowed values reliably.

What are the common PHP approaches or workarounds for representing enumerations? Also, has PHP introduced any official solution for enums, or were they only discussed for future versions?

Short Answer

By the end of this page, you will understand what an enumeration is, why developers use enums, how PHP developers modeled enum-like values before native support existed, and how to use modern native enums in PHP. You will also learn when to choose class constants, native enums, or simple value objects in real projects.

Concept

An enumeration is a type that represents a small, fixed set of allowed values.

For example:

  • order status: pending, paid, shipped
  • user role: admin, editor, viewer
  • payment method: card, cash, bank_transfer

The main idea is that a variable should not accept any value. It should accept only one of the approved options.

In programming, enums matter because they help with:

  • correctness: fewer invalid values
  • readability: clearer intent than raw strings like 'x' or '1'
  • autocomplete: IDEs can suggest valid options
  • refactoring safety: renaming enum cases is safer than changing many scattered strings
  • type safety: the language can reject invalid values earlier

Before native enums in PHP

For many years, PHP did not have a built-in enum keyword. Developers commonly used:

  • class constants to group allowed values
  • value objects with private constructors
  • validation methods like isValid()
  • strings or integers plus checks

These approaches worked, but they were not true enums. A string like 'paid' was still just a string unless your code explicitly validated it.

Native enums in modern PHP

PHP now has native enums starting from PHP 8.1. This gives PHP an official enum feature similar in spirit to other languages.

There are two main kinds:

  • Unit enums: just named cases
  • Backed enums: cases that also store a string or integer value

Example:

enum OrderStatus: string
{
    case Pending = 'pending';
    case Paid = 'paid';
    case Shipped = 'shipped';
}

This is better than loose strings because the type itself expresses the allowed choices.

Why this matters in real PHP code

Enums are useful in:

  • API request validation
  • database state fields
  • domain logic
  • configuration options
  • form processing
  • event types

When a value should come from a controlled list, an enum makes the code easier to trust and maintain.

Mental Model

Think of an enum like a menu with fixed choices.

If a coffee shop menu allows only:

  • Small
  • Medium
  • Large

then a customer cannot order Gigantic unless the shop adds it to the menu.

An enum works the same way. Instead of letting code pass any random string, you define the approved options once and reuse them everywhere.

  • A plain string is like writing any drink size on paper.
  • A class constant is like having a printed list on the wall.
  • A native enum is like the cash register itself only accepting valid menu options.

That is why enums are stronger than just storing strings: the allowed choices become part of the program structure.

Take Quiz

Syntax and Examples

1. Older PHP approach: class constants

Before native enums, a common pattern was grouping constants in a class:

class OrderStatus
{
    public const PENDING = 'pending';
    public const PAID = 'paid';
    public const SHIPPED = 'shipped';

    public static function all(): array
    {
        return [
            self::PENDING,
            self::PAID,
            self::SHIPPED,
        ];
    }

    public static function isValid(string $value): bool
    {
        return in_array($value, self::all(), true);
    }
}

$status = OrderStatus::PAID;

if (OrderStatus::isValid($status)) {
    echo "Valid status";
}

Why this helped

  • constants are grouped inside a class, so naming collisions are reduced
  • IDEs can often autocomplete OrderStatus::...
  • validation can be centralized

Limitation

$status is still just a string, not a special enum type.

2. Modern PHP approach: native enums

In PHP 8.1+, use native enums:

enum OrderStatus: string
{
    case Pending = 'pending';
    case Paid = 'paid';
    case Shipped = 'shipped';
}

$status = OrderStatus::Paid;

echo $status->value; // paid

What this gives you

  • a real type called OrderStatus
  • fixed allowed values
  • strong IDE support
  • cleaner function signatures

Example function:

function sendOrderEmail(OrderStatus $status): void
{
    if ($status === OrderStatus::Paid) {
        echo "Send payment confirmation";
    }
}

Now the function only accepts OrderStatus values.

3. Converting user input to a backed enum

enum UserRole: string
{
    case Admin = 'admin';
    case Editor = 'editor';
    case Viewer = 'viewer';
}

$input = 'editor';
$role = UserRole::tryFrom($input);

if ($role === null) {
    echo "Invalid role";
} else {
    echo $role->value;
}

tryFrom() safely returns null if the value is not valid.

4. Listing all enum cases

foreach (UserRole::cases() as $role) {
    echo $role->value . PHP_EOL;
}

This is useful for forms, dropdowns, and validation rules.

Step by Step Execution

Consider this native enum example:

enum PaymentMethod: string
{
    case Card = 'card';
    case Cash = 'cash';
    case BankTransfer = 'bank_transfer';
}

$input = 'cash';
$method = PaymentMethod::tryFrom($input);

if ($method === null) {
    echo 'Invalid payment method';
} else {
    echo 'Selected: ' . $method->value;
}

Step by step

  1. PHP defines an enum called PaymentMethod.
  2. It declares three allowed cases:
    • Card
    • Cash
    • BankTransfer
  3. $input contains the string 'cash'.
  4. PaymentMethod::tryFrom($input) checks whether 'cash' matches one of the backed values.
  5. Because 'cash' is valid, $method becomes PaymentMethod::Cash.
  6. The if ($method === null) check fails, because a valid enum case was found.
  7. The else block runs.
  8. $method->value returns the string 'cash'.
  9. The program outputs:
Selected: cash

If the input were invalid

If $input = 'cheque';, then:

  • PaymentMethod::tryFrom('cheque') returns null
  • the program prints Invalid payment method

This is a safe and common way to validate incoming strings from forms, APIs, or query parameters.

Real World Use Cases

Enums are useful when values come from a fixed set

Status fields

enum OrderStatus: string
{
    case Pending = 'pending';
    case Paid = 'paid';
    case Cancelled = 'cancelled';
}

Used in:

  • e-commerce orders
  • invoice states
  • shipment workflows

User roles and permissions

enum UserRole: string
{
    case Admin = 'admin';
    case Editor = 'editor';
    case Viewer = 'viewer';
}

Used in:

  • access control
  • admin dashboards
  • content publishing systems

API event types

enum EventType: string
{
    case Created = 'created';
    case Updated = 'updated';
    case Deleted = 'deleted';
}

Used in:

  • webhooks
  • message queues
  • audit logs

Configuration choices

Enums help when configuration should allow only specific options, such as:

  • cache driver
  • environment type
  • output format

Form validation

When users submit a known list of values, tryFrom() gives a clean way to reject invalid input.

Take Quiz

Real Codebase Usage

In real PHP projects, developers often use enums in a few recurring patterns.

Typed method parameters

function setStatus(OrderStatus $status): void
{
    // ...
}

This prevents invalid values from being passed accidentally.

Guard clauses for invalid input

$role = UserRole::tryFrom($_POST['role'] ?? '');

if ($role === null) {
    throw new InvalidArgumentException('Invalid role');
}

This is a clean validation pattern.

Match expressions with enums

function labelForStatus(OrderStatus $status): string
{
    return match ($status) {
        OrderStatus::Pending => 'Waiting for payment',
        OrderStatus::Paid => 'Paid successfully',
        OrderStatus::Shipped => 'Sent to customer',
    };
}

Enums pair very well with match because each case is explicit.

Generating dropdown options

$options = array_map(
    fn(UserRole $role) => $role->value,
    UserRole::cases()
);

This keeps form options synchronized with the enum definition.

Database mapping

A common pattern is:

  • store the backed value like 'paid' in the database
  • convert it into an enum in application code
  • convert it back when saving

Example:

$status = OrderStatus::from($row['status']);

Replacing magic strings

Instead of repeating raw strings across files:

if ($status === 'paid') {
    // ...
}

use:

if ($status === OrderStatus::Paid) {
    // ...
}

This reduces typos and improves refactoring.

Common Mistakes

1. Using raw strings everywhere

Broken style:

function process(string $status): void
{
    if ($status === 'payed') {
        echo 'done';
    }
}

Problem:

  • typo: 'payed' instead of 'paid'
  • no type safety

Better:

function process(OrderStatus $status): void
{
    if ($status === OrderStatus::Paid) {
        echo 'done';
    }
}

2. Assuming class constants are true enums

class Status
{
    public const PAID = 'paid';
}

$status = 'anything';

Problem:

  • constants define allowed values, but nothing forces $status to use them

Avoid this by validating values or using native enums when available.

3. Using from() when input may be invalid

$role = UserRole::from($input);

Problem:

  • from() throws an error if the value is invalid

Safer when input is external:

$role = UserRole::tryFrom($input);
if ($role === null) {
    // handle invalid input
}

4. Comparing enum objects to strings directly

Broken code:

if ($status === 'paid') {
    // ...
}

If $status is an enum case, compare properly:

if ($status === OrderStatus::Paid) {
    // ...
}

Or use the backed value intentionally:

if ($status->value === 'paid') {
    // ...
}

5. Choosing enums for values that are not fixed

Enums are best for a known, limited set of options.

Bad fit examples:

  • usernames
  • article titles
  • search queries

Use enums only when the value set is controlled and finite.

Comparisons

ApproachPHP VersionType SafetyIDE AutocompleteCan Validate Allowed ValuesBest Use Case
Global constantsOlder PHPLowLimitedManualLegacy code
Class constantsOlder PHPLowGoodManualPre-PHP 8.1 enum-like groups
Arrays of valuesAnyLowWeakManualSimple lists, not strong domain rules
Value object patternOlder PHPMediumGoodYesStrict pre-8.1 designs
Native enumsPHP 8.1+HighExcellentBuilt inModern PHP applications

Class constants vs native enums

FeatureClass constantsNative enums
Defines fixed namesYesYes
Is its own typeNoYes
Can be used as parameter typeNot directlyYes
Built-in list of casesNoYes, with cases()
Built-in string/int conversionNoYes, for backed enums
Best for modern codeSometimesYes

from() vs tryFrom()

MethodBehavior
from($value)Returns the enum case or throws an error if invalid
tryFrom($value)Returns the enum case or null if invalid

Use tryFrom() when input comes from users, APIs, or databases that may contain invalid data.

Take Quiz

Cheat Sheet

Quick reference

Pre-PHP 8.1 workaround

class Status
{
    public const ACTIVE = 'active';
    public const INACTIVE = 'inactive';
}

Native enum in PHP 8.1+

enum Status: string
{
    case Active = 'active';
    case Inactive = 'inactive';
}

Use an enum as a type

function setStatus(Status $status): void
{
}

Convert string to enum

Status::from('active');    // throws if invalid
Status::tryFrom('active'); // returns null if invalid

Access backed value

$status = Status::Active;
echo $status->value;

Get all cases

Status::cases();

Compare enum cases

if ($status === Status::Active) {
    // ...
}

Rules to remember

  • Use native enums in PHP 8.1+ when values are fixed.
  • Use class constants only as a fallback in older PHP versions.
  • Use tryFrom() for untrusted input.
  • Compare enum cases to enum cases, not directly to strings unless you mean to compare ->value.
  • Enums are for small, known sets of values.

FAQ

Does PHP have native enums now?

Yes. PHP introduced native enums in PHP 8.1.

How were enums represented in older PHP versions?

Most developers used class constants, sometimes combined with validation methods like all() and isValid().

Are class constants the same as enums?

No. They can group fixed values, but they do not create a true enum type.

Should I use strings or enums in PHP?

If the value belongs to a fixed set and you are using PHP 8.1+, prefer enums.

What is the difference between unit enums and backed enums?

Unit enums have named cases only. Backed enums also carry a string or integer value.

When should I use from() instead of tryFrom()?

Use from() when the value must be valid and invalid data should fail immediately. Use tryFrom() when input may be invalid.

Can enums be stored in a database?

Usually, you store the backed value such as 'paid' or 1, then convert it to an enum in PHP code.

Are enums better for IDE autocomplete?

Yes. Native enums usually provide strong autocomplete and clearer code navigation in modern IDEs.

Take Quiz

Mini Project

Description

Build a small order-status handler that accepts a status value, validates it, and prints a user-friendly message. This demonstrates why enums are useful for fixed workflow states in real applications such as shops, booking systems, and admin dashboards.

Goal

Create a PHP program that safely converts a string into an order status enum and responds based on the selected status.

Requirements

  1. Define a native PHP backed enum called OrderStatus.
  2. Include at least three cases: pending, paid, and shipped.
  3. Accept a string input and convert it with tryFrom().
  4. Print an error message for invalid input.
  5. Print a different message for each valid status.
Take Quiz

Keep learning

Related questions

View more