Coding problem · Medium · Language Idioms
Order Status Enum with Transitions
Medium Language practicals Seen at Shopify, Booking.com, Mollie
Model order statuses with a backed enum OrderStatus: string with cases Pending = 'pending', Paid = 'paid', Shipped = 'shipped', Cancelled = 'cancelled', plus:
label(): string→"Pending","Paid","Shipped","Cancelled";canTransitionTo(self $next): bool— allowed moves: Pending → Paid or Cancelled; Paid → Shipped or Cancelled; Shipped and Cancelled are final.
And a function transition(OrderStatus $from, string $to): OrderStatus that converts $to with OrderStatus::from() (an unknown value throws ValueError) and throws DomainException if the move isn't allowed.
Hints
Hint 1
match ($this) { ... } returns a value per case and is exhaustive.
Hint 2
OrderStatus::from('x') throws ValueError; tryFrom returns null.
Write and run your solution
Loading the editor… (JavaScript is required to run code in your browser.)
Solution and explanation
Show the solution
Time O(1) · Space O(1)
Backed enums give you a closed set of values that serialize to strings (->value, from(), tryFrom()), and enum methods keep behaviour next to the data. match is exhaustive — adding a case without handling it raises an UnhandledMatchError, which is exactly what you want in a state machine. Enum cases are singletons, so compare them with === (and in_array(..., true)).
Reference solution
<?php
enum OrderStatus: string
{
case Pending = 'pending';
case Paid = 'paid';
case Shipped = 'shipped';
case Cancelled = 'cancelled';
public function label(): string
{
return ucfirst($this->value);
}
public function canTransitionTo(self $next): bool
{
return in_array($next, match ($this) {
self::Pending => [self::Paid, self::Cancelled],
self::Paid => [self::Shipped, self::Cancelled],
self::Shipped, self::Cancelled => [],
}, true);
}
}
function transition(OrderStatus $from, string $to): OrderStatus
{
$next = OrderStatus::from($to);
if (!$from->canTransitionTo($next)) {
throw new DomainException("Cannot move order from {$from->value} to {$next->value}");
}
return $next;
}
Common mistakes
- Comparing statuses as strings throughout the codebase.
- Using
tryFromand silently continuing withnull. - Loose
in_arraywithout the strict flag.
Follow-up questions
- Persist the enum with Doctrine/Eloquent casts.
- Record transitions with timestamps for an audit trail.
More Language Idioms problems
- Type-safe groupBy<T, K>() · Easy
- Result Type Instead of Exceptions · Medium
- Typed pick() and omit() · Easy
- Leaderboard Sorting with usort and <=> · Easy
- Slugify a Title (Strings & Regex) · Easy
- Lazy chunked() Generator · Easy