Enums vs Constants

Replacing Constants and Flags with Enums

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.

An enum parameter rejects a typo, and flags become an int-backed enumPHP
<?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.