bensondevs / supercharged-enums
Supercharged backed enum helpers (find, options, comparisons) with no framework dependencies.
Requires
- php: ^8.2
Requires (Dev)
- laravel/pint: ^1.24
- pestphp/pest: ^3.8
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
- Quick start
- Features
- Modular composition
- Bundled Common enums
- Development
- Support
- License
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 units — DurationUnit 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 units — LengthUnit, 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(returnsnull)- 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
$strictisfalse. - With
$stricttrue, resolution is limited totryFrom()(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:
label()instance methodgetLabel()getName()- Raw PHP case
name
Description resolution (asSelectDescriptions()):
getDescription()getLabel()- 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()andunselectables()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 incases(), not numeric or lexical order of backing values. A case declared first with backing value2is still “before” a case declared second with backing value1.
Notes:
isIn/isNotInignore entries that do not resolve to a case.compareToreturns-1,0,1, ornullwhen the other operand does not resolve.isBetweenis inclusive on both ends by default; passincludeStart: falseorincludeEnd: falsefor exclusive bounds.next/previousreturnnullat the end of the list unless$wrapistrue.min/maxskip unresolvable operands; returnnullif 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
Navigation — null 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