wkdb.yt/enumswkdb.yt/enumswkdb.yt/enumsA 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
wkdb.yt/enumspublic function processPayment(): int
{
$result = $this->gateway->run();
if ($result) {
if ($result->approval_code) {
return 1;
}
return 2;
}
return 0;
}PaymentProcessor
wkdb.yt/enumsclass PaymentResult
{
public const APPROVED = 'approved';
public const DECLINED = 'declined';
public const ERROR = 'error';
}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
wkdb.yt/enumsPaymentModel
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/enumsclass 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;This is perfectly legal...
wkdb.yt/enumsClass Constants help with code comprehension and centralization.
They are just syntactic sugar around scalars.
wkdb.yt/enumsScalar Type Definitions reduce type errors, but are poor representations of "things"
Working directly with scalars allows illogical operations.
wkdb.yt/enumsValidation reduces runtime errors.
Only applies to the current context.
Cannot trust a scalar value to not change.
wkdb.yt/enumswkdb.yt/enumswkdb.yt/enumswkdb.yt/enumsThings that Exist in Known, Limited, and Discrete States
wkdb.yt/enumsEnum 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
wkdb.yt/enumsA 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 |
wkdb.yt/enumswkdb.yt/enumsAn 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/enumswkdb.yt/enumswkdb.yt/enumswkdb.yt/enumsPaymentResult::Approved === PaymentResult::Approved;
PaymentResult::Approved !== PaymentResult::Declined;
PaymentResult::Approved !== AccountStatus::Approved;wkdb.yt/enums// PHP Serialization
PaymentResult::Approved === \unserialize(\serialize(PaymentResult::Approved));
// Casting/Mutating, e.g. Database
PaymentResult::Approved->value === 'approved';
PaymentResult::from('approved') === PaymentResult::Approved;wkdb.yt/enums"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/enumswkdb.yt/enumsenum boolean {
false = 0,
true = 1
};wkdb.yt/enumsenum unit_type {
null = 0,
};nullwkdb.yt/enumswkdb.yt/enumsenum PaymentResult
{
case Approved;
case Declined;
case Error;
}enum ChessPiece
{
case Pawn;
case Knight;
case Bishop;
case Rook;
case Queen;
case King;
}
ChessPiece::Knight->name === 'Knight';
ChessPiece::Knight === ChessPiece::Knight;wkdb.yt/enumsinterface 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,
];enum CardSuit: string
{
case Clubs = '♣';
case Diamonds = '♦';
case Hearts = '♥';
case Spades = '♠';
}
CardSuit::Hearts->name === 'Hearts';
CardSuit::Hearts->value === '♥';wkdb.yt/enumsCardSuit::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;
}wkdb.yt/enumswkdb.yt/enumsArrayAccess)serialize() and igbinary_serialize()
json_encode()
(string) or (int) operatorswkdb.yt/enumsfinal)__construct() or __destruct() methods__call(), __callStatic(), and __invoke()
wkdb.yt/enumsenum 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/enumsIf 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/enumsUse 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(): Lightsenum 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/enumswkdb.yt/enums...and that's ok - enums aren't strings.
"Stringable" !== "Serializable"
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];
}
}return RectorConfig::configure()
->withConfiguredRule(StringToBackedEnumValueRector::class, \array_map(
StringToBackedEnumValue::make(...),
HttpMethod::cases(),
));wkdb.yt/enumsenum 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/enumsenum 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/enumsenum Caseless
{
}
var_dump(Caseless::cases()); // []wkdb.yt/enumsThis is perfectly legal...
...but probably useless
enum UnitType
{
case Null;
}wkdb.yt/enumsfunction 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/enumsfunction 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/enumsenum ValidationError
{
case MissingOrEmpty;
case Overflow;
case Underflow;
case InvalidValue;
}
function validate(mixed $value): string|ValidationError
{
//
}wkdb.yt/enumsenum 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/enumsinterface 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/enumsfunction check(mixed $value): mixed
{
$value = validate($mixed);
if($value instanceof ValidationError){
throw new UnexpectedValueException($value->name);
}
return $value->value;
}wkdb.yt/enumsfunction check(mixed $value): mixed
{
return validate($mixed)->validated()->value
?? throw new UnexpectedValueException();
}wkdb.yt/enumswkdb.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');
}
}wkdb.yt/enumsCardSuit::instance('♣') === CardSuit::Clubs;
CardSuit::instance(CardSuit::Diamonds) === CardSuit::Diamonds;
CardSuit::values() === [
'CLUBS' => '♣',
'DIAMONDS' => '♦',
'HEARTS' => '♥',
'SPADES' => '♠',
];wkdb.yt/enumswkdb.yt/enums