bensondevs/supercharged-enums

Supercharged backed enum helpers (find, options, comparisons) with no framework dependencies.

Maintainers

Package info

github.com/bensondevs/supercharged-enums

pkg:composer/bensondevs/supercharged-enums

Statistics

Installs: 12

Dependents: 0

Suggesters: 0

Stars: 3

Open Issues: 0

0.1.0 2026-05-16 09:21 UTC

This package is auto-updated.

Last update: 2026-05-18 04:32:21 UTC


README

Backed enum helpers (find, options, comparisons, labels) via the EnumExtension trait—no framework dependencies. The package also ships optional ready-made enums for everyday domains (HTTP, calendar and time, measurement units, finance, logging, deployment environments, and more) under BensonDevs\SuperchargedEnums\Common\, each wired with the same helpers. See Bundled Common enums.

Requirements: PHP 8.2 or later. The EnumExtension trait targets backed enums (string or int). Pure unit enums without a backing type are not supported by lookup normalization.

Table of contents

Installation

composer require bensondevs/supercharged-enums

Quick start

Add the trait to your own enum, or use a bundled one from Common\—same helpers either way.

Your enum

use BensonDevs\SuperchargedEnums\EnumExtension;

enum Status: string
{
    use EnumExtension;

    case Draft = 'draft';
    case Published = 'published';
}

Lookup — request / DB strings → cases (null when unknown)

Status::find('published');               // Status::Published
Status::findOrDefault('archived');         // Status::Draft (falls back to default)
Status::find('published', strict: true);   // Backing values only, no aliases

Labels and forms

Status::options();                // ['draft' => 'Draft', 'published' => 'Published']
Status::Published->getName();     // "Published"

Comparisons — declaration order, not backing-value sort

Status::Draft->is('draft');                      // true
Status::Draft->isBefore(Status::Published);      // true
Status::Draft->next();                           // Status::Published

Bundled time unitsDurationUnit with conversions and the same helpers

use BensonDevs\SuperchargedEnums\Common\Time\DurationUnit;

DurationUnit::find('hour');             // DurationUnit::Hour
DurationUnit::findOrDefault('eon');     // DurationUnit::Second (unknown → default)

DurationUnit::Hour->toSeconds(2);       // 7200
DurationUnit::Hour->isBefore(DurationUnit::Week);                        // true
DurationUnit::Hour->isBetween(DurationUnit::Minute, DurationUnit::Day);  // true
DurationUnit::Hour->next();             // DurationUnit::Day

Bundled measurement unitsLengthUnit, MassUnit, and others under Common\Measure

use BensonDevs\SuperchargedEnums\Common\Measure\LengthUnit;

LengthUnit::find('mile');               // LengthUnit::Mile
LengthUnit::Mile->toKilometers(1);      // Convert 1 mile → kilometers
LengthUnit::Mile->toMeters(1, decimalDigits: 4);

LengthUnit::Meter->isBefore(LengthUnit::Kilometer);  // true (declaration order)
LengthUnit::Meter->next();              // LengthUnit::Kilometer

More domains (HTTP status codes, money rounding, data sizes) are listed under Bundled Common enums.

Features

EnumExtension reference. For a walkthrough, see Quick start. To use individual traits, see Modular composition.

Core helpers

Status::default();     // First declared case (cases()[0]); override on your enum to return any case
Status::getDefault();  // Alias for default()
Status::random();      // A random declared case

default() follows declaration order, not the smallest or largest backing value. For example, HttpStatusCode defaults to Continue (100) because it is declared first, even though other codes have smaller numeric semantics in different contexts.

There is no separate config flag: declare the case you want as the default first, or override default() on your enum to return any case you prefer. findOrDefault() and any code that falls back to default() will use whichever you define.

enum Priority: string
{
    use EnumExtension;

    // Declared first → default() returns Priority::Medium
    case Medium = 'medium';
    case Low = 'low';
    case High = 'high';
}

Priority::default();              // Priority::Medium
Priority::findOrDefault('urgent'); // Priority::Medium (unknown key)

// To change the default, reorder cases — put Low first instead:
enum Priority: string
{
    use EnumExtension;

    case Low = 'low';
    case Medium = 'medium';
    case High = 'high';
}

Priority::default(); // Priority::Low

// Or override default() — keep declaration order, pick any case:
enum Priority: string
{
    use EnumExtension;

    case Medium = 'medium';
    case Low = 'low';
    case High = 'high';

    public static function default(): static
    {
        return self::Low;
    }
}

Priority::default(); // Priority::Low

If you use a partial trait stack without EnumExtension, define default() yourself (for example return self::cases()[0];) so findOrDefault() keeps working.

Lookup

Status::find('published');                    // Status::Published — resolve self, backing scalar, or null
Status::find('unknown');                      // null when nothing matches
Status::findOrDefault('unknown');             // Status::Draft — find() or default() when lookup fails
Status::find('published', strict: true);      // Backing values only (no aliases)

find() accepts:

  • An enum instance (returned as-is)
  • null (returns null)
  • A backing value: string keys for string-backed enums; int keys for int-backed enums, with numeric strings coerced to int (e.g. '2'2)

When $strict is false and tryFrom() does not match, cases may define alternate keys via an instance method:

public function alias(): array
{
    return match ($this) {
        self::Active => ['legacy_active'],
        default => [],
    };
}
  • Aliases are consulted only when $strict is false.
  • With $strict true, resolution is limited to tryFrom() (backing values only).
  • If the same alias appears on multiple cases, behavior is undefined; the first match in cases() iteration order wins.
EnumWithAliases::find('legacy_active');              // Active
EnumWithAliases::find('legacy_active', strict: true); // null
EnumWithAliases::findOrDefault('nope');              // First (default case)

Case listing

Status::names();   // ['Draft', 'Published'] — PHP case names
Status::values();  // ['draft', 'published'] — backing values

Naming

$status = Status::Draft;

$status->getKey();  // 'draft' — the backing value ($this->value)
$status->getName(); // 'Draft' — human label derived from the case name

getName() normalizes case names for display: underscores and hyphens become spaces, PascalCase is split, and the result is title-cased (NoShow"No show", FirstOption"First option").

Select maps

Build value => label maps for HTML <select> elements, JSON APIs, and similar UIs.

Status::options();              // ['draft' => 'Draft', 'published' => 'Published'] — backing value → short label
Status::asSelectOptions();      // Alias for options()
Status::asSelectDescriptions(); // Backing value → longer description text

Label resolution (options()), first match wins:

  1. label() instance method
  2. getLabel()
  3. getName()
  4. Raw PHP case name

Description resolution (asSelectDescriptions()):

  1. getDescription()
  2. getLabel()
  3. Raw PHP case name

Filtering which cases appear in maps

Define either an allow-list or a deny-list as a static method returning enum cases and/or backing scalars:

public static function selectables(): array
{
    return [self::Beta, 'gamma'];
}

public static function unselectables(): array
{
    return [self::Hidden, 'archived'];
}
  • When both selectables() and unselectables() exist, selectables() wins.
  • Filtered cases keep declaration order from the enum.

Comparisons and ordering

Operands accept enum instances, backing scalars, or null. Scalars and aliases resolve through find() (non-strict), same as lookup.

Declaration order, not backing values

Ordering methods (compareTo, isBefore, isAfter, next, previous, min, max, and related helpers) use the index in cases(), not numeric or lexical order of backing values. A case declared first with backing value 2 is still “before” a case declared second with backing value 1.

Notes:

  • isIn / isNotIn ignore entries that do not resolve to a case.
  • compareTo returns -1, 0, 1, or null when the other operand does not resolve.
  • isBetween is inclusive on both ends by default; pass includeStart: false or includeEnd: false for exclusive bounds.
  • next / previous return null at the end of the list unless $wrap is true.
  • min / max skip unresolvable operands; return null if none resolve.
use BensonDevs\SuperchargedEnums\Common\Calendar\Month;

$month = Month::March;

Equality

// Backing string (or other scalar the enum resolves)
$month->is('march');                           // true
$month->isNot('december');                     // true

// Enum instance
$month->is(Month::March);                      // true
$month->isNot(Month::December);                // true
$month->isIn(['october', 'march', 'june']);    // true — backing values in the list
$month->isIn([Month::October, Month::March]);   // true — cases in the list
$month->isNotIn(['december']);                 // true
$month->isNotIn([Month::December]);            // true

Order — declaration order, not backing-value sort

// Backing string
$month->isBefore('october');                   // true
$month->isAfter('october');                    // false

// Enum instance
$month->isBefore(Month::October);              // true
$month->isAfter(Month::October);               // false
$month->isBeforeOrEqual(Month::October);       // true
$month->isAfterOrEqual(Month::October);        // false
$month->compareTo('october');                  // -1, 0, 1, or null when other does not resolve
$month->compareTo(Month::October);             // -1

Range — reversed bounds are swapped automatically

$month->isBetween('january', 'december');                    // true (inclusive both ends)
$month->isBetween(Month::January, Month::December);          // true
$month->isBetween(Month::December, Month::January, includeStart: false); // exclusive start

Position

$month->isFirst();                             // false
$month->isLast();                              // false
$month->diff('june');                          // 3
$month->diff(Month::June);                     // 3

Navigationnull at the end unless $wrap is true

$month->next();                                // Month::April
Month::January->previous(wrap: true);          // Month::December (wrap from first)

Aggregates — skip unresolvable operands; null if none resolve

Month::min(Month::March, Month::October);      // Month::March
Month::min('march', 'october');                // Month::March
Month::max(Month::March, 'october');           // Month::October

Modular composition

Individual concerns live under src/Concerns/ and can be used without the full trait:

use BensonDevs\SuperchargedEnums\Concerns\EnumComparisons;
use BensonDevs\SuperchargedEnums\Concerns\EnumLookup;

enum Direction: string
{
    use EnumComparisons;
    use EnumLookup;

    case Left = 'left';
    case Right = 'right';
}

Caveat: findOrDefault() calls default(), which is defined on EnumExtension, not on EnumLookup alone. With a partial stack, use find($key) ?? self::cases()[0] or add your own default() helper.

🔋 Bundled Common enums

Optional backed enums under BensonDevs\SuperchargedEnums\Common\. Each uses EnumExtension. Unless noted in the domain docs, default() is the first declared case; see Core helpers to override.

Per-enum case lists and domain-specific methods are documented under docs/common/.

Domain Documentation Enums
Angle Angle AngleUnit
Application Application DeploymentEnvironment
Calendar Calendar DateDisplayFormat, DayOfWeek, Month, Quarter
Cryptography Cryptography HashAlgorithm
Data size DataSize BinaryDataSizeUnit, DecimalDataSizeUnit
Database Database DatabaseEngine
Device Device DeviceType
Finance Finance CardBrand, MoneyRoundingMode
Geography Geography CompassDirection, Hemisphere
HTTP Http HttpMethod, HttpStatusCode
Identity Identity IdentityDocumentType
Logging Logging LogLevel
Measure Measure AreaUnit, EnergyUnit, FrequencyUnit, LengthUnit, MassUnit, PowerUnit, PressureUnit, SpeedUnit, TemperatureUnit, VolumeUnit
MIME Mime MediaTypeClass
Platform Platform CpuArchitecture, OperatingSystemFamily
Text Text TextCasing, TextTransform
Time Time DurationUnit, Season, TimePrecision

Usage examples

use BensonDevs\SuperchargedEnums\Common\Finance\MoneyRoundingMode;
use BensonDevs\SuperchargedEnums\Common\Measure\LengthUnit;
use BensonDevs\SuperchargedEnums\Common\Time\DurationUnit;

LengthUnit::Mile->toKilometers(1);
DurationUnit::Hour->toSeconds(2);
MoneyRoundingMode::HalfEven->roundMoney(10.005);

Behavior is covered by the Pest test suite.

Development

composer test   # run Pest tests
composer lint   # run Laravel Pint

Support

If this package saves you time, consider supporting its maintenance:

Thank you for helping keep Supercharged Enums maintained.

License

MIT