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
Suggests
- illuminate/support: Required for Laravel stub publishing (included in all Laravel apps).
This package is auto-updated.
Last update: 2026-07-13 12:46:44 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
- Quick start
- Features
- Runtime supercharge
- Modular composition
- Bundled Common enums
- Development
- Support
- License
Installation
composer require bensondevs/supercharged-enums
Laravel Boost
This package ships Laravel Boost AI resources for coding agents:
- Skill (
supercharged-enums-development) — on-demand patterns forEnumExtension, lookup, select maps, comparisons, and bundledCommon\enums - Guidelines — always-loaded overview and conventions
Boost discovers these automatically from resources/boost/ in the installed package. No ServiceProvider or extra configuration is required in this library.
In a Laravel project that uses Boost:
composer require laravel/boost --dev # if not already installed php artisan boost:install # select skills; enable bensondevs/supercharged-enums # or, for an existing Boost setup: php artisan boost:update --discover
Boost integration is opt-in on the consumer side. The package remains framework-agnostic.
Laravel Artisan stubs
For Laravel projects, you can publish a customized enum.backed.stub so php artisan make:enum generates backed enums with EnumExtension already applied.
Enable (one-time, manual):
php artisan vendor:publish --tag=supercharged-enums-stubs
Generate enums as usual:
php artisan make:enum Status --string
# or --int
Example output:
use BensonDevs\SuperchargedEnums\EnumExtension; enum Status: string { use EnumExtension; // }
Notes:
- Laravel-only and opt-in — publishing is required; non-Laravel projects are unaffected.
- Only backed enums (
--string/--int) use this stub. Pure unit enums are unchanged. - If your app already has
stubs/enum.backed.stub(for example fromphp artisan stub:publish), use--forceto overwrite — that replaces your existing customization. - Stub publishing is separate from Laravel Boost; Boost skills and guidelines do not require publishing stubs.
Beta: Enum import commands are experimental. APIs and generated output may change in minor releases.
Import enum from a legacy lookup table (beta)
When migrating from database lookup tables to backed enums, generate a ready-to-use enum from existing rows:
php artisan supercharged-enums:import-from-table order_statuses \ --class=OrderStatus # optional; defaults from the table name --string|--int # optional; overrides auto backing detection --value-column=name # optional; overrides auto-detection --label-column=title # optional --aliases # optional; emit alias() for legacy keys --path=app/Enums # optional --force # overwrite an existing file
Column auto-detection defaults:
| Purpose | Detection order |
|---|---|
| Backing value | slug, code, name, then id |
| Human label | label, title, description (skips the value column) |
| Case order | sort_order, position, order, then id |
Backing type: auto-detects int when the value column is id (or an integer column); otherwise string. Use --string or --int to override.
Generated output includes EnumExtension, one case per table row, optional getLabel() when a label column exists, and optional alias() with --aliases (maps legacy integer IDs for string-backed enums, or legacy string keys for int-backed enums).
A progress bar is shown while rows are read, followed by a summary of unique cases found.
This command is Laravel-only and requires a database connection. It does not drop the legacy table or update models automatically. This feature is currently beta.
Enum importers (beta)
For repeatable imports across multiple tables, query filters, or custom column mappings, scaffold an importer class:
php artisan make:enum-importer OccupancyEnumImporter
This creates app/EnumImporters/OccupancyEnumImporter.php extending EnumImporter. Configure sources and optional resolvers:
public function sources(): array { return [ 'occupancy_types', 'legacy_occupancies' => fn (Builder $query) => $query->where('active', true), ]; } public function resolveUsing(): array { return [ 'occupancy_types' => fn (array $attributes) => [ 'id' => $attributes['id'], 'value' => $attributes['code'], 'name' => $attributes['code'], 'label' => $attributes['name'], 'sort' => $attributes['id'], ], ]; } // public function as(): string { return 'PowerfulEnum'; } // public function onDuplicate(): string { return 'last-wins'; }
Run the importer:
php artisan supercharged-enums:import-enum-using OccupancyEnumImporter
Importer API:
| Method | Purpose |
|---|---|
sources() |
Table names, optionally with query closures |
resolveUsing() |
Per-table row resolver returning value, name, label, id, sort from $attributes['column'] |
as() |
Target enum class name (default: OccupancyEnumImporter → OccupancyEnum) |
onDuplicate() |
fail (default) or last-wins when backing values collide across sources |
aliases() |
Emit alias() mappings for legacy keys |
Overwrite behavior: if the enum file already exists, the command prompts for confirmation. Use --force to skip the prompt, or --no-interaction to fail without overwriting (CI-safe). Override duplicate handling per run with --duplicates=last-wins.
Both import commands show a progress bar while reading rows and report how many unique cases were found.
Use import-from-table for one-off imports; use importers when the same migration will be re-run or spans multiple sources. This feature is currently beta.
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 Status::all(); // [Status::Draft, Status::Published] — filtered cases as array Status::collect(); // Illuminate\Support\Collection of filtered cases Status::filteredCases(); // Same cases as all() (alias)
collect() requires illuminate/support (included in Laravel apps). Without it, calling collect() throws a runtime exception with install instructions.
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
Runtime supercharge
When you cannot add EnumExtension to an enum (vendor code, generated enums, or one-off usage), wrap it at runtime with supercharge(). PHP cannot attach methods to enum instances dynamically, so this returns a wrapper object — not the native enum type. Use unwrap() when an API expects the raw case.
use function BensonDevs\SuperchargedEnums\supercharge; enum VendorStatus: string { case Open = 'open'; case Closed = 'closed'; } // Instance API — comparisons, naming, navigation $status = supercharge(VendorStatus::Open); $status->is('open'); // true $status->getName(); // "Open" $status->value; // 'open' (via property access) $status->unwrap(); // VendorStatus::Open // Static API — lookup, select maps, aggregates (pass the class string) supercharge(VendorStatus::class)->find('open'); // VendorStatus::Open supercharge(VendorStatus::class)->findOrDefault('nope'); // VendorStatus::Open (first case) supercharge(VendorStatus::class)->options(); // ['open' => 'Open', 'closed' => 'Closed']
| Scenario | Recommended approach |
|---|---|
| You own the enum | use EnumExtension — native Status::Draft->is(...) |
| Third-party / vendor enum | supercharge(VendorEnum::class)->find($key) |
| One-off comparison | supercharge($case)->is('foo') |
Notes:
- Backed enums only — same constraint as
EnumExtension. Pure unit enums throwInvalidArgumentException. find()/findOrDefault()on the class wrapper return native enum cases (not wrappers). Wrap the result withsupercharge()when you need instance helpers.- Custom enum methods remain available via
supercharge($case)->yourMethod()(forwarded with__call). - Enums that already use
EnumExtensionwork withsupercharge()too; runtime configuration overridesdefault(), select filtering, and related helpers when set.
Runtime configuration
Configure vendor enums at bootstrap (e.g. in AppServiceProvider::boot()) when you cannot add EnumExtension or need app-specific defaults and select lists:
use BensonDevs\SuperchargedEnums\SuperchargedEnumType; use function BensonDevs\SuperchargedEnums\supercharge; use Vendor\Package\VendorStatus; supercharge(VendorStatus::class)->configureUsing( fn (SuperchargedEnumType $type) => $type ->setDefault(VendorStatus::Closed) ->setSelectables([VendorStatus::Open, 'closed']) // ->setUnselectables([...]) // ignored when selectables is set ); // Anywhere in the app supercharge(VendorStatus::class)->default(); // VendorStatus::Closed supercharge(VendorStatus::class)->all(); // only configured selectables supercharge(VendorStatus::class)->findOrDefault('unknown'); // falls back to configured default
Runtime configuration takes precedence over native enum methods (default(), selectables(), etc.). find() still resolves any declared case — filtering applies to all(), options(), and related select helpers only.
Laravel Eloquent casting
For Laravel models whose attributes use vendor enums, add SuperchargedEnumCast so the attribute is a SuperchargedEnum on read while the database stores the backing scalar.
use BensonDevs\SuperchargedEnums\Laravel\Casts\SuperchargedEnumCast; use Vendor\Package\OrderStatus; class Order extends Model { protected function casts(): array { return [ // Strict (default) — invalid DB value throws on read 'status' => SuperchargedEnumCast::of(OrderStatus::class), // Lenient — unknown DB value falls back to default case 'legacy_status' => SuperchargedEnumCast::of(OrderStatus::class, lenient: true), // Via supercharge() helper 'status' => supercharge(OrderStatus::class)->cast(), ]; } } $order->status->is('open'); $order->status->unwrap(); // native OrderStatus for type-hinted APIs $order->toArray()['status']; // 'open' (backing value, not wrapper)
JSON array columns
For attributes that store arrays of enum backing values in a JSON column:
use BensonDevs\SuperchargedEnums\EnumExtension; use BensonDevs\SuperchargedEnums\Laravel\Casts\EnumExtensionCollectionCast; use BensonDevs\SuperchargedEnums\Laravel\Casts\SuperchargedEnumArrayCast; class Order extends Model { protected function casts(): array { return [ // EnumExtension enums — Collection of native cases on read 'statuses' => EnumExtensionCollectionCast::of(OrderStatus::class), 'legacy_statuses' => EnumExtensionCollectionCast::of(OrderStatus::class, lenient: true), // Vendor / wild enums — array of SuperchargedEnum wrappers on read 'permissions' => SuperchargedEnumArrayCast::of(VendorPermission::class), 'legacy_permissions' => SuperchargedEnumArrayCast::of(VendorPermission::class, lenient: true), // Via supercharge() helper 'permissions' => supercharge(VendorPermission::class)->arrayCast(), ]; } } $order->statuses[0]->is('open'); // native enum with EnumExtension $order->permissions[0]->is('read'); // SuperchargedEnum wrapper $order->toArray()['statuses']; // ['open', 'closed'] — backing values
| Scenario | Recommended approach |
|---|---|
| You own the enum (scalar) | use EnumExtension on the enum + Laravel's built-in enum cast |
| You own the enum (JSON array) | EnumExtensionCollectionCast::of(YourEnum::class) |
| Vendor enum on a model attribute (scalar) | SuperchargedEnumCast::of(VendorEnum::class) |
| Vendor enum on a model attribute (JSON array) | SuperchargedEnumArrayCast::of(VendorEnum::class) |
| Legacy column with bad data | lenient: true on the relevant cast |
Requires illuminate/database (included in Laravel). Invalid values on assignment always throw; lenient mode applies to reading unknown DB values only.
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 (check only) composer analyse # PHPStan composer type-coverage # PHPStan type coverage (100% enforced)
Support
If this package saves you time, consider supporting its maintenance:
Thank you for helping keep Supercharged Enums maintained.
License
MIT