Photo by Ben Griffiths on Unsplash

Enum in PHP — Real life examples

Daniel Archer
5 min readOct 31, 2023

--

Recently I saw an article here on medium that was explaing about Enums in PHP and how they could be used. But I was not fully satisfied about the examples given and not really showing the full spectrum of the potencial gain of Enums. So let me try to complement the article with a more indept usage.

This was the article for reference: https://medium.com/@miladev95/unlocking-the-power-of-php-enums-best-practices-for-effective-use-6bbd6b0aac02

The structure

Before enums were a thing on PHP, we were used to represent those values using simple const variables in the classes and in some cases, using libraries that encapsulate that functionality. But not most of us were needed it.
So it was very common to have something like this:

class RetailStore extends Model
{
const OPEN = 1;
const CLOSED = 0;
const UNDER_MAINTENANCE = -1;

public function __construct() {
/*...*/
}
}

In this example we are not discussing numbers or strings stored on the database. Focusing on the naming to value mapping aspect, this was already a step forward because you may still see inline values used in a couple of scenarios. Fortunatelly this can be in the past.

Now we can have the same code exported to a much more readable structure that also enforces the separation of concerns by default.

enum RetailStoreStatus: int {
case OPEN = 1;
case CLOSED = 0;
case MAINTENANCE = -1;
}

But this coul be achieved by any separation class, what enums brings to the table is all builtIn functionalities like:

$status = RetailStoreStatus::from(-1); //getting value from DB

// Direct comparison
if ($status === RetailStoreStatus::CLOSED) {
...
}

// Loop over
foreach (RetailStoreStatus::cases() as $status) {
...
}

Someting to keep in mind when looping over the enum status is that each element will return the Enum object as expected, so if you try to echoing will get an error, unless you call the internal value directly:

foreach (RetailStoreStatus::cases() as $status) {
echo $status;
}
// Fatal error: Uncaught Error: Object of class
// RetailStoreStatus could not be converted to string

foreach (RetailStoreStatus::cases() as $status) {
echo $status->value;
}
// Ok

Another way to retrieve the value, without converting, is to start to enrich your Enum class.

enum RetailStoreStatus: int {
case OPEN = 1;
case CLOSED = 0;
case MAINTENANCE = -1;

public function toString(): string {
return match($this) {
self::OPEN => 'Open',
self::CLOSED => 'Closed',
self::MAINTENANCE => 'Under Maintenance',
};
}
}

foreach (RetailStoreStatus::cases() as $status) {
echo $status->toString();
}

Now we have a much cleaner user interface and from here you can add a lot of more logic, that can encapsulate all into a single object. Don’t forget to keep it strict to the necessary logic and concerns keeping it SOLID.

One last example, adding some helper methods to use in the your views for example.

namespace App\Models;

enum VirtualQueueStatus: string {
case BeingServed = 'being_server';
case Completed = 'completed';
case NoShow = 'no_show';
case Removed = 'removed';
case Abandoned = 'abandoned';

public function title(): string {
return match ($this) {
VirtualQueueStatus::BeingServed => 'Being served',
VirtualQueueStatus::Completed => 'Completed',
VirtualQueueStatus::NoShow => 'No show',
VirtualQueueStatus::Removed => 'Removed',
VirtualQueueStatus::Abandoned => 'User left',
};
}

public function cssClass(): string {
return match ($this) {
VirtualQueueStatus::BeingServed => 'bg-yellow-200 text-yellow-700',
VirtualQueueStatus::Completed => 'bg-green-200 text-green-700',
VirtualQueueStatus::NoShow => 'bg-red-200 text-red-700',
VirtualQueueStatus::Removed => 'bg-red-200 text-red-700',
VirtualQueueStatus::Abandoned => 'bg-red-200 text-red-700',
};
}
}

Best Use cases

Now that we know how we can use it, let’s know where can we apply this cases, is important to remember this can replace a lot of previous classes/attributes.

The first candidates to be migrated to this structure are, obviously, Classes that relies on multiple values, replacing <select> values and Object status.

Good to mention that State machines benefit from this, see 3 examples below:

v1: simple state transition,

enum State: string {
case START = 'start';
case RUN = 'run';
case STOP = 'stop';
}

class StateMachine {
private State $currentState;

public function __construct() {
$this->currentState = State::START;
}

public function transition(State $newState): void {
// ... some transition validation
$this->currentState = $newState;
}

public function getState(): State {
return $this->currentState;
}
}

// Usage
$machine = new StateMachine();
echo $machine->getState(); // Output: start

$machine->transition(State::RUN);
echo $machine->getState(); // Output: run

More advanced state machine:

enum PaymentState: string {
case PENDING = 'pending';
case PROCESSING = 'processing';
case SUCCESS = 'success';
case FAILURE = 'failure';
}

class PaymentStateMachine {
private PaymentState $currentState;

public function __construct() {
$this->currentState = PaymentState::PENDING;
}

public function toProcessing(): void {
if ($this->currentState === PaymentState::PENDING) {
$this->currentState = PaymentState::PROCESSING;
}
}

public function toSuccess(): void {
if ($this->currentState === PaymentState::PROCESSING) {
$this->currentState = PaymentState::SUCCESS;
}
}

public function toFailure(): void {
if ($this->currentState === PaymentState::PROCESSING) {
$this->currentState = PaymentState::FAILURE;
}
}

public function getState(): PaymentState {
return $this->currentState;
}
}

// Usage
$payment = new PaymentStateMachine();
echo $payment->getState(); // Output: pending

$payment->toProcessing();
echo $payment->getState(); // Output: processing

$payment->toSuccess();
echo $payment->getState(); // Output: success

[Note] More complex state machines don’t have the need of Enums like this:

interface PaymentState {
public function toProcessing(): PaymentState;
public function toSuccess(): PaymentState;
public function toFailure(): PaymentState;
public function getState(): string;
}

class PendingState implements PaymentState {
public function toProcessing(): PaymentState {
return new ProcessingState();
}

public function toSuccess(): PaymentState {
throw new Exception("Can't go directly to Success from Pending");
}

public function toFailure(): PaymentState {
throw new Exception("Can't go directly to Failure from Pending");
}

public function getState(): string {
return 'pending';
}
}

class ProcessingState implements PaymentState {
...
}

class SuccessState implements PaymentState {
...
}

class FailureState implements PaymentState {
...
}

class PaymentStateMachine {
private PaymentState $currentState;

public function __construct() {
$this->currentState = new PendingState();
}

public function transition(PaymentState $newState): void {
$this->currentState = $newState;
}

public function getState(): string {
return $this->currentState->getState();
}
}

// Usage
$payment = new PaymentStateMachine();
echo $payment->getState(); // Output: pending

$payment->transition($payment->currentState->toProcessing());
echo $payment->getState(); // Output: processing

$payment->transition($payment->currentState->toSuccess());
echo $payment->getState(); // Output: success

Frameworks Integration

All major frameworks today already have some support for Enums but I don’t think would be a good fit for this article so I will link some very good articles about it.

Conclusion

Enums are extremelly powerful and you can start refacting your code today, they bring similar benefits as the Value Objects from DDD, refactor your code and take advantage of this new feature as soon as possible. They will make your code more readable, reusable and simple to maintaing.

Keeping the logic and possible scenarios in a single place will help you to avoid bigger changes when requirements changes, and you know they are going to change over time.

Cheers!

--

--

Daniel Archer

Senior PHP Developer with 12+ years experience. Expert in high-availability systems, clean code, and best practices. Based in Dublin, Ireland.