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() vs toValueArray()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-implements to enforce the correct enum type at static analysis time. Mixing BitFlagTrait or ComparableTrait with non-int-backed enums will surface as a PHPStan error.

License

MIT

S
Description
A collection of PHP traits that supercharge native enums with comparison, bit-flag, stepping, collection, and translation helpers — zero dependencies, PHP 8.3+.
Readme MIT 100 KiB
Languages
PHP 98.2%
Makefile 1.4%
Dockerfile 0.4%