wkdb.yt/enums
wkdb.yt/enums
wkdb.yt/enums
A payment result will have only one of three values:
APPROVED
DECLINED
ERROR
wkdb.yt/enums
public function processPayment(): string
{
$result = $this->payment_gateway->run();
if ($result) {
if ($result->approval_code) {
return 'approved';
}
return 'declined';
}
return 'error';
}
PaymentProcessor
wkdb.yt/enums
public function processPayment(): int
{
$result = $this->gateway->run();
if ($result) {
if ($result->approval_code) {
return 1;
}
return 2;
}
return 0;
}
PaymentProcessor
wkdb.yt/enums
class PaymentResult
{
public const APPROVED = 'approved';
public const DECLINED = 'declined';
public const ERROR = 'error';
}
wkdb.yt/enums
class 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/enums
PaymentModel
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/enums
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/enums
class 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/enums
Class Constants help with code comprehension and centralization.
They are just syntactic sugar around scalars.
wkdb.yt/enums
Scalar Type Definitions reduce type errors, but are poor representations of "things"
Working directly with scalars allows illogical operations.
wkdb.yt/enums
Validation reduces runtime errors.
Only applies to the current context.
Cannot trust a scalar value to not change.
wkdb.yt/enums
wkdb.yt/enums
wkdb.yt/enums
wkdb.yt/enums
Things that Exist in Known, Limited, and Discrete States
wkdb.yt/enums
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/enums
wkdb.yt/enums
Mercury
Venus
Earth
Mars
Jupiter
Saturn
Uranus
Neptune
Pluto
wkdb.yt/enums
Mercury
Venus
Earth
Mars
Jupiter
Saturn
Uranus
Neptune
Pluto
Enumeration of Planets in Our Solar System
wkdb.yt/enums
Mercury
Venus
Earth
Mars
Jupiter
Saturn
Uranus
Neptune
Pluto
Enumeration of Planets in Our Solar System
Invalid Enumeration
wkdb.yt/enums
Mercury
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/enums
Mercury
Venus
Earth
Mars
Jupiter
Saturn
Uranus
Neptune
Pluto
Enumeration of Habitable Planets in Our Solar System
wkdb.yt/enums
Mercury
Venus
Earth
Mars
Jupiter
Saturn
Uranus
Neptune
Pluto
Enumeration of Planets in Our Solar System with Intelligent Life
wkdb.yt/enums
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 |
wkdb.yt/enums
wkdb.yt/enums
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/enums
wkdb.yt/enums
wkdb.yt/enums
wkdb.yt/enums
PaymentResult::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/enums
wkdb.yt/enums
enum boolean {
false = 0,
true = 1
};
wkdb.yt/enums
enum unit_type {
null = 0,
};
null
wkdb.yt/enums
wkdb.yt/enums
enum 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/enums
interface UnitEnum {
public static cases(): array
}
wkdb.yt/enums
ChessPiece::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/enums
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/enums
Backed Enum
interface BackedEnum extends UnitEnum {
public static from(int|string $value): static;
public static tryFrom(int|string $value): ?static;
}
wkdb.yt/enums
wkdb.yt/enums
ArrayAccess
)serialize()
and igbinary_serialize()
json_encode()
(string)
or (int)
operatorswkdb.yt/enums
final
)__construct
() or __destruct()
methods__call()
, __callStatic()
, and __invoke()
wkdb.yt/enums
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/enums
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/enums
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(): Lights
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/enums
wkdb.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/enums
final 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/enums
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'); // true
wkdb.yt/enums
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
enum Caseless
{
}
var_dump(Caseless::cases()); // []
wkdb.yt/enums
This is perfectly legal...
...but probably useless
enum UnitType
{
case Null;
}
wkdb.yt/enums
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/enums
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/enums
enum ValidationError
{
case MissingOrEmpty;
case Overflow;
case Underflow;
case InvalidValue;
}
function validate(mixed $value): string|ValidationError
{
//
}
wkdb.yt/enums
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/enums
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/enums
function check(mixed $value): mixed
{
$value = validate($mixed);
if($value instanceof ValidationError){
throw new UnexpectedValueException($value->name);
}
return $value->value;
}
wkdb.yt/enums
function check(mixed $value): mixed
{
return validate($mixed)->validated()->value
?? throw new UnexpectedValueException();
}
wkdb.yt/enums
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');
}
}
wkdb.yt/enums
CardSuit::instance('♣') === CardSuit::Clubs;
CardSuit::instance(CardSuit::Diamonds) === CardSuit::Diamonds;
CardSuit::values() === [
'CLUBS' => '♣',
'DIAMONDS' => '♦',
'HEARTS' => '♥',
'SPADES' => '♠',
];
wkdb.yt/enums
wkdb.yt/enums