tez-utils-enum
Composable PHP 8.3 traits that extend native enums with lookup, comparison, navigation, bitmask, label, and collection helpers.
Zero dependencies · PHPStan level 9 · MIT
Installation
composer require tez/utils-enum
Requires PHP ≥ 8.3.
Traits
| Trait | Enum type | Purpose |
|---|---|---|
EnumTrait |
Unit | Base helpers: lookup, filtering, ordering |
BackedEnumTrait |
Backed | Extends EnumTrait with value-based helpers |
BitFlagTrait |
Int-backed | Bitmask operations |
ComparableTrait |
Int-backed | Comparison and range checks |
StepTrait |
Any | Navigate to adjacent cases |
TranslatableTrait |
Any | Human-readable labels |
EnumCollectionTrait |
Any | Collection-style selection |
EnumTrait
Base trait for pure (unit) enums. Use BackedEnumTrait instead when your enum has backing values.
use Tez\Utils\Enum\EnumTrait;
enum Status
{
use EnumTrait;
case Draft;
case Published;
case Archived;
}
Status::find('Published'); // Status::Published
Status::find('published', ignoreCase: true); // Status::Published
Status::Published->is('Published'); // true
Status::Published->is(Status::Published); // true
Status::Published->isOneOf([Status::Draft, Status::Published]); // true
Status::Published->isNoneOf([Status::Archived]); // true
Status::Published->ordinal(); // 1
Status::first(); // Status::Draft
Status::last(); // Status::Archived
Status::filter(fn($c) => $c !== Status::Archived); // [Draft, Published]
Status::toArray(); // ['Draft' => 'Draft', 'Published' => 'Published', 'Archived' => 'Archived']
Methods
| Method | Description |
|---|---|
is($value) |
True if the case matches by identity or name. Null always returns false. |
isOneOf(?array $values) |
True if the case matches any value in the list. |
isNoneOf(?array $values) |
Inverse of isOneOf(). |
find($value, $ignoreCase) |
Returns a matching case or null. Matches by identity or name. |
filter(callable $cb) |
Returns all cases for which the callback returns true. |
toArray() |
Returns [name => name] for all cases. |
ordinal() |
Zero-based position of this case in the enum definition. |
first() |
Returns the first defined case. |
last() |
Returns the last defined case. |
BackedEnumTrait
Extends EnumTrait for string- or int-backed enums. Overrides is() to match by backing value, and toArray() to return [value => name].
use Tez\Utils\Enum\BackedEnumTrait;
enum Color: string
{
use BackedEnumTrait;
case Red = 'red';
case Green = 'green';
case Blue = 'blue';
}
Color::find('red'); // Color::Red — matched by value
Color::find('Red'); // Color::Red — matched by name
Color::find('RED', ignoreCase: true); // Color::Red
Color::hasValue('blue'); // true
Color::values(); // ['red', 'green', 'blue']
Color::fromName('Green'); // Color::Green (throws ValueError if not found)
Color::tryFromName('X'); // null
Color::toArray(); // ['red' => 'Red', 'green' => 'Green', 'blue' => 'Blue']
Color::toValueArray(); // ['Red' => 'red', 'Green' => 'green', 'Blue' => 'blue']
toArray()vstoValueArray()—toArray()returns[value => name], useful for form selects.toValueArray()returns[name => value], useful for serialization. The directions are opposite.
Additional methods
| Method | Description |
|---|---|
values() |
All backing values as a list. |
hasValue($value) |
True if any case has this backing value. |
fromName(string $name) |
Returns a case by name, or throws ValueError. |
tryFromName(string $name) |
Returns a case by name, or null. |
toValueArray() |
Returns [name => value] for all cases. |
BitFlagTrait
Bitmask helpers for int-backed enums used as flag sets. Case values must be powers of 2 (1, 2, 4, 8, …). This constraint is not enforced at runtime.
Does not include BackedEnumTrait — combine them explicitly if needed.
use Tez\Utils\Enum\BitFlagTrait;
enum Permission: int
{
use BitFlagTrait;
case Read = 1;
case Write = 2;
case Execute = 4;
}
// Build a mask
$mask = Permission::combine(Permission::Read, Permission::Write); // 3
// Check flags
Permission::Read->isSetIn($mask); // true
Permission::Execute->isSetIn($mask); // false
// Add / remove flags
$mask = Permission::Execute->addTo($mask); // 7
$mask = Permission::Write->removeFrom($mask); // 5
// Expand a mask back to cases
Permission::fromBitmask($mask); // [Permission::Read, Permission::Execute]
Permission::fromBitmask(0); // []
Methods
| Method | Description |
|---|---|
combine(self ...$cases) |
OR-combines case values into a bitmask. Returns 0 with no arguments. |
fromBitmask(int $mask) |
Returns all cases whose bit is set in the mask. Skips cases with value 0. |
isSetIn(int $mask) |
True if this case's bit is set in the mask. |
addTo(int $mask) |
Returns the mask with this case's bit set. |
removeFrom(int $mask) |
Returns the mask with this case's bit cleared. |
ComparableTrait
Comparison helpers for int-backed enums, comparing cases by their backing values.
use Tez\Utils\Enum\ComparableTrait;
enum Priority: int
{
use ComparableTrait;
case Low = 1;
case Medium = 2;
case High = 3;
}
Priority::High->isGreaterThan(Priority::Low); // true
Priority::Medium->isGreaterThanOrEqual(Priority::Medium); // true
Priority::Medium->between(Priority::Low, Priority::High); // true
Priority::High->clamp(Priority::Low, Priority::Medium); // Priority::Medium
Methods
| Method | Description |
|---|---|
isGreaterThan(self $other) |
True if this case's value is strictly greater. |
isGreaterThanOrEqual(self $other) |
True if this case's value is greater or equal. |
isLessThan(self $other) |
True if this case's value is strictly less. |
isLessThanOrEqual(self $other) |
True if this case's value is less or equal. |
between($min, $max) |
True if within the inclusive range [$min, $max]. |
clamp($min, $max) |
Returns this case, or $min/$max if out of range. |
StepTrait
Navigate to adjacent cases by definition order. Designed for state machines and ordered workflows. Works with both unit and backed enums.
Order is determined by definition order (ordinal), not backing value. Returns null at boundaries — no wrap-around.
use Tez\Utils\Enum\StepTrait;
enum CheckoutStep
{
use StepTrait;
case PersonalInfo;
case Address;
case Payment;
case Confirmation;
}
CheckoutStep::Address->next(); // CheckoutStep::Payment
CheckoutStep::Address->prev(); // CheckoutStep::PersonalInfo
CheckoutStep::Confirmation->next(); // null (last step)
CheckoutStep::PersonalInfo->prev(); // null (first step)
Methods
| Method | Description |
|---|---|
next() |
Returns the next case by definition order, or null at the last case. |
prev() |
Returns the previous case by definition order, or null at the first case. |
TranslatableTrait
Human-readable labels for enum cases, without any framework dependency. Override labels() to supply custom mappings keyed by case name. Falls back to the case name when no label is defined.
use Tez\Utils\Enum\TranslatableTrait;
enum UserRole
{
use TranslatableTrait;
case Admin;
case Editor;
case Viewer;
protected static function labels(): array
{
return [
self::Admin->name => 'Administrator',
self::Editor->name => 'Content Editor',
// Viewer has no entry — falls back to 'Viewer'
];
}
}
UserRole::Admin->label(); // 'Administrator'
UserRole::Viewer->label(); // 'Viewer' (fallback)
UserRole::labelsArray();
// ['Admin' => 'Administrator', 'Editor' => 'Content Editor', 'Viewer' => 'Viewer']
Methods
| Method | Description |
|---|---|
label() |
Human-readable label for this case. Falls back to $case->name. |
labelsArray() |
All cases mapped to their labels as [caseName => label]. |
labels() (protected) |
Override in your enum to supply custom label mappings. |
EnumCollectionTrait
Collection-style selection helpers. Can be combined with EnumTrait or BackedEnumTrait. Works with any enum type.
use Tez\Utils\Enum\EnumCollectionTrait;
enum Suit
{
use EnumCollectionTrait;
case Hearts;
case Diamonds;
case Clubs;
case Spades;
}
Suit::except(Suit::Hearts, Suit::Diamonds); // [Suit::Clubs, Suit::Spades]
Suit::only(Suit::Clubs, Suit::Spades); // [Suit::Clubs, Suit::Spades]
Suit::random(); // a random Suit case
Both except() and only() preserve enum definition order.
Methods
| Method | Description |
|---|---|
random() |
Returns a random case. |
except(self ...$excluded) |
All cases except the given ones, in definition order. |
only(self ...$included) |
Only the given cases, in definition order. |
Combining traits
Traits are designed to be freely mixed. A few common combinations:
// Full-featured backed enum
enum Status: string
{
use BackedEnumTrait, TranslatableTrait, EnumCollectionTrait, StepTrait;
case Draft = 'draft';
case Review = 'review';
case Published = 'published';
protected static function labels(): array
{
return [
self::Draft->name => 'Draft',
self::Review->name => 'In Review',
self::Published->name => 'Published',
];
}
}
Status::Draft->next(); // Status::Review
Status::Review->label(); // 'In Review'
Status::except(Status::Draft); // [Status::Review, Status::Published]
Status::find('draft'); // Status::Draft
// Int-backed enum with bitmask + comparison
enum Permission: int
{
use BackedEnumTrait, BitFlagTrait, ComparableTrait;
case Read = 1;
case Write = 2;
case Execute = 4;
}
All traits use
@phpstan-require-implementsto enforce the correct enum type at static analysis time. MixingBitFlagTraitorComparableTraitwith non-int-backed enums will surface as a PHPStan error.
Requirements
- PHP 8.3+
- No runtime dependencies
License
MIT