dive-be/php-enum-utils
Utilities for PHP 8.1+ enums in Laravel: convert enum cases to arrays, values, options, and labels for forms and APIs, compare and inspect cases, and cut boilerplate with a small, dependency-free helper set that makes working with native enums painless in everyday application code.
composer require dive-be/php-enum-utils
Requires PHP 8.1+ (native enums). There is no config file, no service provider to publish, no migrations — it's a trait library you use inside your own enums. Start by reading src/Concerns/ in the installed vendor directory (or the README); the whole package is a handful of traits and static helpers, so you can digest it in one sitting.
The first use case to wire up is the boring one you hit on every project: turning an enum into arrays for selects, validation, and API responses.
use Dive\EnumUtils\Concerns\EnumToArray;
enum OrderStatus: string
{
use EnumToArray;
case Pending = 'pending';
case Paid = 'paid';
case Refunded = 'refunded';
}
OrderStatus::names(); // ['Pending', 'Paid', 'Refunded']
OrderStatus::values(); // ['pending', 'paid', 'refunded']
OrderStatus::toArray(); // ['Pending' => 'pending', ...]
Add the trait to your first enum, replace the hand-rolled const arrays or array_column(self::cases(), 'value') snippets scattered around the codebase, and you'll immediately see where the package earns its keep.
Trait-per-enum, not inheritance. PHP enums can't extend, so the package's trait approach is the only sane way to share behaviour. Put EnumToArray on domain enums, and optionally EnumToAssoc (name => value map) where you need label/value pairs for a <select> or an Inertia prop.
Single source of truth for form options. Build one method (or enum) that feeds validation, Blade/Inertia, and factories:
// FormRequest
'status' => ['required', Rule::in(OrderStatus::values())],
// Blade / Inertia
<select>
@foreach (OrderStatus::toArray() as $label => $value)
<option value="{{ $value }}">{{ $label }}</option>
@endforeach
</select>
Labels belong in the enum, not the trait. The package gives you names/values; add a label(): string match-expression method yourself and a EnumToArray::toArray() override (or a labels() helper) if you want human-readable names. Keeps translation (__('enums.status.paid')) close to the cases.
Wrap it in a base contract for consistency. Define interface HasValues {} and a shared trait alias so every enum in the app exposes the same API. Then a Blade component or a toSelectOptions() macro works uniformly.
Test data providers. OrderStatus::values() is the obvious source for PHPUnit/Pest #[DataProvider] methods; OrderStatus::names() for asserting that every case is handled by match statements:
it('handles every status', function (string $name) {
expect(OrderStatus::fromName($name))->toBeInstanceOf(OrderStatus::class);
})->with(OrderStatus::names());
That "exhaustive match" test is the highest-value thing this package enables — it catches the enum case you added but forgot to handle in a match.
Factories, seeders, and API resources. Use values() with fake()->randomElement(...), and expose ->value in API resources. Keep the enum in the domain layer, serialize to the backed value at the boundary.
Comparison helpers. For code that filters collections or checks state transitions, enum_equals()-style helpers (or your own is()/in() methods) read better than $a === $b on enum instances, especially when you're comparing nullable enums. If the package's helper doesn't cover your case, wrap it once rather than scattering null checks.
use EnumToArray; inside a normal class blows up at autoload/analysis time with a confusing error. If you want the same helpers on a non-enum, copy the methods to a separate class rather than trying to share the trait.cases(), from(), tryFrom(). Don't define your own values() in the enum body and import the trait — fatal "trait method collision". If you need custom formatting, alias or override deliberately.values() only makes sense for backed enums. On a pure (unit) enum you'll get names or an error depending on the helper — decide early whether your enum is persisted (backed) or just a state marker (unit).toArray() is not JsonSerializable. Backed enums serialize as their ->value via json_encode()/Laravel's JSON responses, but a toArray() that produces ['Pending' => 'pending'] will be treated as a plain array. Don't assume the two paths line up; be explicit in JsonResources.names()/values()/toArray() preserve declaration order. Sorting alphabetically or by business priority will change every dropdown and test expectation — do it once, consciously, or sort at the call site.fromName('Paid') will throw ValueError on a typo; reach for tryFromName() when parsing user input or API payloads. Prefer the backed value (OrderStatus::tryFrom($request->input('status'))) at HTTP boundaries.@method static array values() docblocks on the enum or a stub file. The traits are generic-ish; older analysers need help.config/ looking for options, you're in the wrong package.App\Support\Enum is a ten-minute migration. Don't let it become load-bearing infrastructure.ValueError: "x" is not a valid backing value for enum Y almost always means a DB row, fixture, or API payload predates a case rename. Add a data-migration check or an assertion in the seeder using Y::values().values() inside a loop. It's cheap, but Rule::in(...) inside foreach over 10k rows is wasted work; compute once and pass it in.How can I help you explore Laravel packages today?