enums
The Missing Data Type
Embracing
Slides/Code/Resources
+ Link to Joind.in
wkdb.yt/enumsHello, World...
wkdb.yt/enums- Contract PHP Developer and Consultant
- Currently Living in Dallas w/ Wife and Dog
- Never Intended to Be a Developer/Speaker

enums
The Missing Data Type
enums
The Missing Data Type
Embracing
PHP B.E.
wkdb.yt/enums(Before Enums)
ProcessING a Card Payment
A payment result will have only one of three values:
APPROVED
DECLINED
ERROR
wkdb.yt/enumspublic function processPayment(): string
{
$result = $this->payment_gateway->run();
if ($result) {
if ($result->approval_code) {
return 'approved';
}
return 'declined';
}
return 'error';
}PaymentProcessor
PROCESSING A CARD PAYMENT
"Magic Strings"
wkdb.yt/enumspublic function processPayment(): int
{
$result = $this->gateway->run();
if ($result) {
if ($result->approval_code) {
return 1;
}
return 2;
}
return 0;
}PaymentProcessor
PROCESSING A CARD PAYMENT
"Magic Integers"
wkdb.yt/enumsclass PaymentResult
{
public const APPROVED = 'approved';
public const DECLINED = 'declined';
public const ERROR = 'error';
}PROCESSING A CARD PAYMENT
Class Constants
wkdb.yt/enumsclass PaymentResult
{
public const APPROVED = 1;
public const DECLINED = 2;
public const ERROR = 0;
}public function processPayment(): string
{
$result = $this->gateway->run();
if ($result) {
if ($result->approval_code) {
return PaymentResult::APPROVED;
}
return PaymentResult::DECLINED;
}
return PaymentResult::ERROR;
}PaymentProcessor
PROCESSING A CARD PAYMENT
Class Constants
wkdb.yt/enumsPaymentModel
PROCESSING A CARD PAYMENT
Class Constants
public function setResult(string $result): void
{
$result = \trim(\strtolower($result));
in_array($result, [
PaymentResult::APPROVED,
PaymentResult::DECLINED,
PaymentResult::ERROR,
], true) || throw new UnexpectedValueException();
$this->result = $result;
}wkdb.yt/enumsSo, What's The Problem ?
class AccountStatus
{
public const APPROVED = 'approved';
public const ACTIVE = 'active';
public const DEACTIVATED = 'deactivated';
}AccountStatus
if($this->gateway->run($transaction)->status === 201){
$transaction->setResult(AccountStatus::APPROVED)
}This works, but it is oh, so wrong...
wkdb.yt/enumsclass WeekDay
{
public const MONDAY = 0;
public const TUESDAY = 1;
public const WEDNESDAY = 2;
public const THURSDAY = 3;
public const FRIDAY = 4;
public const SATURDAY = 5;
public const SUNDAY = 6;
}
WeekDay::SATURDAY + WeekDay::SUNDAY === 11;So, What's The Problem ?
This is perfectly legal...
wkdb.yt/enumsSo, What's The Problem ?
Class Constants help with code comprehension and centralization.

They are just syntactic sugar around scalars.
wkdb.yt/enumsSo, What's The Problem ?
Scalar Type Definitions reduce type errors, but are poor representations of "things"
Working directly with scalars allows illogical operations.

wkdb.yt/enumsSo, What's The Problem ?
Validation reduces runtime errors.

Only applies to the current context.
Cannot trust a scalar value to not change.
wkdb.yt/enumsIt's All About Representing Context and Comparability Across Code Boundaries
wkdb.yt/enumswkdb.yt/enums
There's Always A Relevant XKCD Comic
Enums in Theory
wkdb.yt/enumsWhat Does It Mean To Be Enumerable?

Things that Exist in Known, Limited, and Discrete States


wkdb.yt/enumsWhat Does It Mean To Be Enumerable?
Enum values are important to us because of what they represent and in context to other values in the set
| UserRole |
|---|
| admin |
| manager |
| user |
| guest |
| InvoiceType |
|---|
| standard |
| prorated |
| comped |
| CardBrand |
|---|
| visa |
| mastercard |
| discover |
| amex |
wkdb.yt/enumswkdb.yt/enumsMercury
Venus
Earth
Mars
Jupiter
Saturn
Uranus
Neptune
Pluto
wkdb.yt/enumsMercury
Venus
Earth
Mars
Jupiter
Saturn
Uranus
Neptune
Pluto
Enumeration of Planets in Our Solar System
wkdb.yt/enumsMercury
Venus
Earth
Mars
Jupiter
Saturn
Uranus
Neptune
Pluto
Enumeration of Planets in Our Solar System
Invalid Enumeration
wkdb.yt/enumsMercury
Venus
Earth
Mars
Jupiter
Saturn
Uranus
Neptune
Pluto
Enumeration of Planets in Our Solar System
Enumeration of Rocky Planets in Our Solar System
wkdb.yt/enumsMercury
Venus
Earth
Mars
Jupiter
Saturn
Uranus
Neptune
Pluto
Enumeration of Habitable Planets in Our Solar System
wkdb.yt/enumsMercury
Venus
Earth
Mars
Jupiter
Saturn
Uranus
Neptune
Pluto
Enumeration of Planets in Our Solar System with Intelligent Life
Enums in Practice
wkdb.yt/enumsWhat is an Enumerated Type?
A type is an attribute of a unit of data that tells the computer what values it can take and what operations can be performed upon it.
| PHP Types |
|---|
| boolean |
| integer |
| float |
| string |
| array |
| object |
| callable |
| iterable |
| resource |
| NULL |
What is a Type?
wkdb.yt/enumswkdb.yt/enumsWhat is an Enumerated Type?
An enumerated type is a named, language-level construct that restricts the possible values of an instance to a discrete set of named members, called enumerators, at compile time.
In practice, this means that an enum variable can have any value in the set and only values in the set can be values.
wkdb.yt/enumsWhat Properties Does an Enumeration Need to Preserve Context Across Code Boundaries?
wkdb.yt/enumsProperties of An Ideal Enumeration
Immutable

wkdb.yt/enumsSelf-Valid

Properties of An Ideal Enumeration
wkdb.yt/enumsComparable
PaymentResult::Approved === PaymentResult::Approved;
PaymentResult::Approved !== PaymentResult::Declined;
PaymentResult::Approved !== AccountStatus::Approved;Properties of An Ideal Enumeration
wkdb.yt/enumsSerializable
// PHP Serialization
PaymentResult::Approved === \unserialize(\serialize(PaymentResult::Approved));
// Casting/Mutating, e.g. Database
PaymentResult::Approved->value === 'approved';
PaymentResult::from('approved') === PaymentResult::Approved;Properties of An Ideal Enumeration
wkdb.yt/enumsThis Sounds Familiar...
Value Object
"A small simple object, like money or a date range, whose equality isn't based on identity."
"...I follow a simple but important rule: value objects should be immutable"
-Martin Fowler
wkdb.yt/enumsThe Ideal VAlue Objects
wkdb.yt/enumsEnums
Domain Objects
PHP Objects
Value Objects
Enums
Degenerate Enumerations
The Two Enums PHP Had Prior to 8.1...
enum boolean {
false = 0,
true = 1
};wkdb.yt/enumsenum unit_type {
null = 0,
};null
bool
wkdb.yt/enumsPHP's Enumerated Data Type
wkdb.yt/enumsenum PaymentResult
{
case Approved;
case Declined;
case Error;
}TWo Types of Enums: Pure
enum ChessPiece
{
case Pawn;
case Knight;
case Bishop;
case Rook;
case Queen;
case King;
}
ChessPiece::Knight->name === 'Knight';
ChessPiece::Knight === ChessPiece::Knight;wkdb.yt/enumsTWo Types of Enums: Pure
interface UnitEnum {
public static cases(): array
}wkdb.yt/enumsChessPiece::cases() === [
0 => ChessPiece::Pawn,
1 => ChessPiece::Knight,
2 => ChessPiece::Bishop,
3 => ChessPiece::Rook,
4 => ChessPiece::Queen,
5 => ChessPiece::King,
];TWo Types of Enums: Backed
enum CardSuit: string
{
case Clubs = '♣';
case Diamonds = '♦';
case Hearts = '♥';
case Spades = '♠';
}
CardSuit::Hearts->name === 'Hearts';
CardSuit::Hearts->value === '♥';wkdb.yt/enumsTWo Types of Enums: Backed
CardSuit::from('♥') === CardSuit::Hearts;
CardSuit::tryFrom('♥') === CardSuit::Hearts;
CardSuit::tryFrom('☹') === null;
CardSuit::cases() === [
0 => CardSuit::Clubs,
1 => CardSuit::Diamonds,
2 => CardSuit::Hearts,
3 => CardSuit::Spades,
];wkdb.yt/enumsBacked Enum
interface BackedEnum extends UnitEnum {
public static from(int|string $value): static;
public static tryFrom(int|string $value): ?static;
}PHP's enums are fancy objects with a lot of limitations.
wkdb.yt/enums...and that's what makes them useful
wkdb.yt/enumsPHP ENUMS BEHAVE LIKE OBJECTS
- Can declare public/protected/private static and non-static methods
- Can declare public/protected/private class constants
- Can implement arbitrary interfaces (including
ArrayAccess) - Can declare class and method attributes
- Can use arbitrary traits that only define static and non-static methods
- Safely serialize/deserialize with
serialize()andigbinary_serialize() - Backed enums serialize to their value when transformed with
json_encode() - Cannot be used as an array key
-
Cannot be cast to scalar value with
(string)or(int)operators
wkdb.yt/enumsExcept When They Don't
- Designed and intended to be compared by identity, not equality
- Cannot be extended or abstract (no inheritance, always
final) - Cannot be cloned
- Cannot declare static or object properties (or PHP 8.4 property hooks)
- Cannot declare
__construct() or__destruct()methods - Cannot implement magic methods except
__call(),__callStatic(), and__invoke() - Stateless (almost...)
wkdb.yt/enumsExcept When They Don't
enum CardSuit: string implements \Countable
{
case Clubs = '♣';
case Diamonds = '♦';
case Hearts = '♥';
case Spades = '♠';
public function count(): int
{
static $counter = 0;
return ++$counter;
}
}
Statelessness Doesn't Apply to Method Scope

wkdb.yt/enumsInheritance is Prohibited
If an enumeration could be extended, it would not be an enumeration!
enum TrafficLight
{
case Red;
case Yellow;
case Green;
}
enum WeirdLight extends TrafficLight
{
case Blue;
}wkdb.yt/enumsInheritance is Prohibited
Use Unit Types to Combine Enumerations
public function getLight(): TrafficLight|WeirdLight;Alternatively, both enums can implement a dummy interface
interface Lights extends \BackedEnum
{
// dummy interface
}
public function getLight(): LightsENUMS Can Have Constants
enum HttpMethod: string
{
public const string GET = 'GET';
public const string POST = 'POST';
public const string PUT = 'PUT';
public const string PATCH = 'PATCH';
public const string DELETE = 'DELETE';
public const string OPTIONS = 'OPTIONS';
public const string HEAD = 'HEAD';
public const string TRACE = 'TRACE';
public const string CONNECT = 'CONNECT';
case Get = self::GET;
case Post = self::POST;
case Put = self::PUT;
case Patch = self::PATCH;
case Delete = self::DELETE;
case Options = self::OPTIONS;
case Head = self::HEAD;
case Trace = self::TRACE;
case Connect = self::CONNECT;
}wkdb.yt/enumsEnums Are Not Stringable
wkdb.yt/enums...and that's ok - enums aren't strings.
"Stringable" !== "Serializable"
Enums Are Not Stringable
final class SomeSubscriber
{
public static function getSubscribedEvents()
{
return ['http_method' => 'GET'];
}
}wkdb.yt/enumsfinal class SomeSubscriber
{
public static function getSubscribedEvents()
{
return ['http_method' => HttpMethod::GET->value];
}
}Enums Are Not Stringable
return RectorConfig::configure()
->withConfiguredRule(StringToBackedEnumValueRector::class, \array_map(
StringToBackedEnumValue::make(...),
HttpMethod::cases(),
));wkdb.yt/enumsENUMS Can Be Aliased
enum ChessPiece
{
public const self Horsey = self::Knight;
case Pawn;
case Knight;
case Bishop;
case Rook;
case Queen;
case King;
}
\assert(ChessPiece::Horsey->name === 'Knight'); // truewkdb.yt/enumsENUMS Can Be Callable
enum ChessPiece
{
case Pawn;
case Knight;
case Bishop;
case Rook;
case Queen;
case King;
public function __invoke(): int
{
return match ($this) {
self::King, self::Queen => 1,
self::Rook, self::Bishop, self::Knight => 2,
self::Pawn => 8,
};
}
}wkdb.yt/enums
The Simplest PHP ENUM
enum Caseless
{
}
var_dump(Caseless::cases()); // []wkdb.yt/enumsThis is perfectly legal...
...but probably useless
The Next Simplest PHP ENUM: Unit Type
enum UnitType
{
case Null;
}wkdb.yt/enumsUnit Type
function transform(array $values): array
{
$transformed = [
"Package" => $values['package'] ?? null,
"Code" => $values['promo_code'] ?? null,
];
if (isset($values['extra_1'])) {
$transformed['Extra 1'] = $values['extra_1'];
}
if (isset($values['extra_2'])) {
$transformed['Extra 2'] = $values['extra_2'];
}
if (isset($values['users'])) {
$transformed['Extra Users'] = $values['users'];
}
return $transformed;
}
wkdb.yt/enumsUnit Type
function transform(array $values): array
{
return \array_filter([
"Package" => $values['package'] ?? null,
"Code" => $values['promo_code'] ?? null,
'Extra 1' => $values['extra_1'] ?? UnitType::Null,
'Extra 2' => $values['extra_2'] ?? UnitType::Null,
'Extra Users' => $values['users'] ?? UnitType::Null,
], static fn(mixed $value): bool => $value !== UnitType::Null);
}wkdb.yt/enumsEnums for Error Handling
enum ValidationError
{
case MissingOrEmpty;
case Overflow;
case Underflow;
case InvalidValue;
}
function validate(mixed $value): string|ValidationError
{
//
}wkdb.yt/enumsEnums for Error Handling
enum ValidationError
{
case MissingOrEmpty;
case Overflow;
case Underflow;
case InvalidValue;
}
readonly class ValidatedValue
{
public function __construct(public mixed $value)
{
}
}
function validate(mixed $value): ValidatedValue|ValidationError
{
//
}wkdb.yt/enumsEnums for Error Handling
interface Validation
{
public function validated(): ValidatedValue|null;
}
enum ValidationError implements Validation
{
case MissingOrEmpty;
case Overflow;
case Underflow;
case InvalidValue;
public function validated(): null
{
return null;
}
}
readonly class ValidatedValue implements Validation
{
//
}wkdb.yt/enumsEnums for Error Handling
function check(mixed $value): mixed
{
$value = validate($mixed);
if($value instanceof ValidationError){
throw new UnexpectedValueException($value->name);
}
return $value->value;
}wkdb.yt/enumsEnums for Error Handling
function check(mixed $value): mixed
{
return validate($mixed)->validated()->value
?? throw new UnexpectedValueException();
}wkdb.yt/enumsEnums Can Use Traits
wkdb.yt/enums/**
* @phpstan-require-implements \BackedEnum
*/
trait HasBackedEnumFunctionality
{
public static function instance(int|string|self $value): static
{
return $value instanceof static ? $value : self::from($value);
}
public static function values(): array
{
return \array_column(self::cases(), 'value', 'name');
}
}Enums Can Use Traits
wkdb.yt/enumsCardSuit::instance('♣') === CardSuit::Clubs;
CardSuit::instance(CardSuit::Diamonds) === CardSuit::Diamonds;
CardSuit::values() === [
'CLUBS' => '♣',
'DIAMONDS' => '♦',
'HEARTS' => '♥',
'SPADES' => '♠',
];What About PHP 8.4?
wkdb.yt/enumsNo Significant Changes
Questions?
wkdb.yt/enumsSlides/Code/Resources
@andysnell@phpc.social
Embracing Enums
By Andy Snell
Embracing Enums
In January 2020, I delivered a conference talk titled "Enums: The Missing Data Type", which ended on a sour note: PHP probably would not have a native type for handling enumerations any time soon. To my surprise and delight, PHP 8.1 would release with a new "Enum" type less than two years later. Now we can really explore enumerations: both the theory behind them and the current PHP implementation. We’ll cover how representing things like statuses with enums improves immutability, readability, and type safety; the different types of enums available to us; and address the most common questions around the current limitations of enums. Resources: https://github.com/andysnell/embracing-enums
- 466