Before 8.1 a status was a class constant such as Order::STATUS_PAID = 'paid' passed to a string parameter, which accepts 'piad' as happily as 'paid'. Typing the parameter as the enum moves the check to the call. Bit flags translate too: give an int-backed enum one power of two per case, pass lists of cases around, and fold them into a mask only where an integer is stored.
<?php
enum OrderStatus: string { case Paid = 'paid'; case Shipped = 'shipped'; } // trimmed
enum Extra: int {
case GiftWrap = 1; case Express = 2; case Insured = 4; // one bit per case
public static function toMask(Extra ...$extras): int {
return array_sum(array_column($extras, 'value'));
}
public static function fromMask(int $mask): array {
return array_filter(self::cases(), fn(self $e) => ($mask & $e->value) !== 0);
}
}
function mark(OrderStatus $status): string { return "status={$status->value}"; }
$mask = Extra::toMask(Extra::GiftWrap, Extra::Insured);
echo mark(OrderStatus::Paid), "; extras $mask = ";
echo implode(', ', array_column(Extra::fromMask($mask), 'name')), "\n";
try { mark('piad'); } catch (TypeError $e) { echo $e->getMessage(), "\n"; }Output
status=paid; extras 5 = GiftWrap, Insured mark(): Argument #1 ($status) must be of type OrderStatus, string given, called in /tmp/ch04-11/flags.php on line 17
Everywhere but storage, in_array(Extra::Express, $extras, true) reads better than $mask & 2. PHP's own libraries follow the same path: 8.4's RoundingMode enum (Subsection 4.7.2) replaced the PHP_ROUND_* integer constants.