frostiede/enum-bundle
A Symfony bundle that simplifies working with PHP enums, providing integration helpers and utilities to use enum types more cleanly across your application. Ideal for projects adopting PHP 8.1+ enums with Symfony.
Reality check first: frostiede/enum-bundle is a Symfony bundle, not a Laravel package. There is no ServiceProvider, no Eloquent cast, no Rule::enum integration, and no Flex recipe. For a Laravel developer the day-to-day value is the pattern, not the package — you will almost certainly end up copying 40–60 lines into app/Enums/Concerns/ rather than composer require-ing it into your composer.json.
With that framing, here's the minimal path.
Read the source before installing anything. You don't need it in your tree to evaluate it. Clone or browse the repo and read, in this order:
*Bundle.php — what it registers;DependencyInjection/*Extension.php — the config tree (and whether options exist at all);Resources/config/services.* — service IDs you'd have to replicate in a Laravel provider;tests/ — often more documentation than the README.
If the enums are classes extending an abstract Enum base, stop: that pattern is incompatible with PHP 8.1 native enum and cannot be made to coexist with Laravel's enum casting.Confirm what native Laravel already gives you. Before porting anything, inventory the gap. Modern Laravel covers the vast majority of what a generic enum helper offers:
enum OrderStatus: string casts directly on Eloquent attributes;Rule::enum(OrderStatus::class) validates inbound input;$table->enum() or string columns mirror it at the DB level;->enum() on AsEnumCollection and enum-aware factories/seeding work out of the box.Fill the actual gap. The one thing native enums don't ship is a values() / labels() / options() helper. Drop this in app/Enums/Concerns/HasValues.php:
trait HasValues
{
public static function values(): array
{
return array_column(self::cases(), 'value');
}
public static function options(): array
{
return array_combine(self::values(), array_map(
fn (self $c) => $c->label(), self::cases()
));
}
public function label(): string
{
return ucfirst(str_replace('_', ' ', $this->value));
}
}
Pick a first use case — an OrderStatus enum on an orders.status column, cast on the model, validated in a FormRequest with Rule::enum, and rendered via OrderStatus::options() in a Blade <select>. That's the exact surface the bundle advertises, done in ~30 lines you own outright.
The trait pattern (the one you'll actually use). Define every backed enum with use HasValues; and a label() implementation. This keeps the source of truth in the enum case itself, so OrderStatus::Pending->label() and OrderStatus::options() never drift apart — the failure mode you get when labels live in a separate config array.
Validation in FormRequests. Rule::enum(OrderStatus::class) produces a clear error for free. If you want the human label in the message, customize it in the request's messages() and call OrderStatus::label() — don't let the raw 'pending' leak into user-facing copy.
Serialization. Cast enums on the model and let Laravel serialize the backed value in JSON automatically. If you need both the raw value and a label in an API response (common for admin/mobile clients), use an Attribute or JsonResource method that returns ['value' => $this->status->value, 'label' => $this->status->label()]. Never serialize the enum object itself across a queue boundary — jobs holding enum instances break silently after a refactor, and they break post-deploy.
Form and filter options. Model::options() feeds every dropdown in the admin panel. For a status filter on an index page, Rule::enum on the query string plus options() on the view is the whole integration — no extra dependency.
The one place the bundle's idea is genuinely valuable: transitioning between enum states (e.g., approvals, order lifecycle). Write an explicit canTransitionTo() method on the enum itself rather than scattering if chains:
public function canTransitionTo(self $next): bool
{
return match ($this) {
self::Pending => in_array($next, [self::Shipped, self::Cancelled], true),
self::Shipped => $next === self::Cancelled,
self::Cancelled => false,
};
}
If you're working in a Symfony service (a polyglot codebase, a shared monolith), the honest integration is the same trait above — you don't need the bundle for it there either. Install it only if you've confirmed from the source that the enum implementation is standalone and framework-agnostic, and pin it to an exact tag or commit SHA.
EnumBundle assumes a Symfony kernel and registerBundles(). There is no Laravel service provider, so composer require will pull in transitive symfony/* constraints that frequently collide with Laravel's own (console, http-foundation, routing). Expect a resolution failure before you write code.Enum base class. If enums extend a base class, that's the pre-8.1 pattern and is architecturally incompatible with PHP 8.1 native enums. Adopting it is a one-way door — you can't incrementally convert class-based enums to native ones."frostiede/enum-bundle": "dev-master#<sha>" or a fixed tag. ^1.x leaves you exposed to an unpinned push that breaks CI on a fresh clone.bin/console config:dump-reference <alias> will show the tree — read it before guessing options. Service IDs from Resources/config/services.* are what you'd have to replicate in a Laravel provider.int or string. If a column or cache key is on the wrong side of that line, comparisons pass in PHP but fail against the DB. Enforce a rule that only scalar values cross storage, cache, and queue boundaries — never enum instances.--optimize. If the bundle's classes assume Symfony kernel registration, composer dump-autoload --optimize on a fresh deploy can surface them as failures. That's the worst time to discover the problem.interface HasLabels { public function label(): string; } and interface HasValues { public static function values(): array; }. Call sites reference your interfaces, never FrostieDE\..., so migrating off the vendor is a swap, not a refactor.values(), labels(), options(), and casting — the entire advertised surface. Write that, record the decision in your engineering standards, and skip the dependency entirely.How can I help you explore Laravel packages today?