Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Enum Laravel Package

cerbero/enum

A powerful enum implementation for Laravel that turns class constants into rich, type-safe enum objects. It adds validation rules, localization, Eloquent casting, comparisons, and Blade helpers while keeping enums simple and extensible.

View on GitHub
Deep Wiki
Context7

Getting Started

Add the package:

composer require cerbero/enum

No service provider, no config file — it's a pure PHP library (PHP 8.1+) that ships a single trait. The only setup is opting in per enum:

use Cerbero\Enum\Concerns\Enum;

enum Status: string
{
    use Enum;

    case Pending  = 'pending';
    case Active   = 'active';
    case Archived = 'archived';
}

Where to look first: src/Concerns/Enum.php (the whole surface area lives there or in traits it composes), then the tests/ folder — the test suite doubles as the best documentation for edge cases around magic methods and attribute resolution.

First use case — replace the status arrays scattered across your Blade views and Form Requests. Annotate cases with display metadata:

use Cerbero\Enum\Attributes\Label;
use Cerbero\Enum\Attributes\Color;
use Cerbero\Enum\Concerns\Enum;

enum Status: string
{
    use Enum;

    #[Label('Pending review'), Color('yellow')]
    case Pending = 'pending';

    #[Label('Active'), Color('green')]
    case Active = 'active';

    #[Label('Archived'), Color('gray')]
    case Archived = 'archived';
}

Then, in a controller or view composer:

Status::collect()->mapWithKeys(fn ($status) => [
    $status->value => $status->label(),
]);
// ['pending' => 'Pending review', 'active' => 'Active', ...]

That single snippet replaces a hand-maintained options array and guarantees your labels stay in sync with the enum cases.

Implementation Patterns

Build select options / filters. collect() returns a collection of cases, so mapWithKeys plus label() is the canonical pattern. For a filter UI that also needs the colour, chain color().

Use magic per-case helpers instead of match blocks. For a case named Pending the trait synthesizes:

Status::pending();            // -> case Pending   (static accessor)
$status->isPending();         // bool
$status->isNotPending();      // bool
$status->inPending();         // bool, accepts arrays/enums/names/values
$status->notInPending();

These read well in guards:

abort_unless($order->status->isPending() || $order->status->isActive(), 403);

For comparisons against more than one case, use the variadic form: $status->in(Status::Pending, Status::Active).

Eloquent casts + Rule::enum. Because the trait is additive, casting works unchanged:

protected $casts = ['status' => Status::class];

// validation
'status' => ['required', Rule::enum(Status::class)],

The package's from() / tryFrom() are more lenient than native enum helpers — they accept either the backing value or the case name, which makes them ideal for request payloads that haven't been normalised yet. Pair with has()/hasNot() when you want a boolean check without try/catch.

Meta attributes for domain logic. Instead of hard-coding a canTransitionTo() switch, put the data on the case:

#[TransitionTo('active'), TransitionTo('archived')]
case Pending = 'pending';

#[Meta('key', value)] is the generic escape hatch — retrieve it with $status->meta('key'). Custom attribute classes are the extension point if Label/Color/Description don't fit your domain (e.g. an Icon attribute for your UI kit).

Rename the generated accessors via #[Naming]. Apply it at the class level to change the prefix/suffix of the methods generated from attributes, so $status->label() can become $status->getLabel() or $status->name() to match a project convention or an existing interface.

Bulk data for seeders and fixtures.

foreach (Status::values() as $value) {
    StatusModel::updateOrCreate(['name' => $value], ...);
}

flip() is useful when you need a value→name map (be careful, see gotchas).

Testing. Because values() returns a plain array, assertDatabaseHas loops and dataset providers are straightforward. For PHPUnit @dataProviders, Status::cases() (overridden by the trait) is the cleanest source.

Gotchas and Tips

Method name collisions are fatal. The trait throws if a synthesized method clashes with an existing one on your enum. If you already have label() defined, either rename yours or change the naming convention with #[Naming]. Do a grep for suspicious names before adding the trait to a large enum.

Attribute class imports are easy to get wrong. Label, Color, Description, Name, and Meta live under Cerbero\Enum\Attributes\*. These are not the same as Symfony's #[MapFrom]-style attributes — importing the wrong one silently produces a "method does not exist" error deep inside a Blade view.

Static magic methods are invisible to IDEs and PHPStan. Status::pending() is resolved by __callStatic, so your editor will flag it. Run php artisan list | grep -i enum — the package ships an artisan command to generate PHPDoc stubs/annotations for your enums. Commit the generated @method docblocks (or generate them in CI) so static analysis stays clean.

Backed vs pure enums change the contract. from(), tryFrom(), flip(), and values() assume backing values for full behaviour. On a pure (non-backed) enum, flip() and values() degrade — check before relying on them in shared helpers.

Duplicate backing values break flip(). flip() keys by value, so two cases with the same value overwrite each other. PHP doesn't stop you from defining duplicates, and the failure only surfaces at runtime. Add a unit test that asserts count(Status::values()) === count(Status::cases()).

collect() brings in illuminate/collections. It returns a Laravel collection, so you get mapWithKeys, pluck, etc. — perfect in a Laravel app, but a non-Laravel consumer of the package paying for that dependency would be surprised. If you're distributing an enum in a shared, framework-agnostic package, prefer cases() + array_map.

Reflection runs on first access. Attribute resolution reflects over cases lazily; in hot paths inside queue workers this is fine, but avoid calling label() inside a tight loop over 10k models — cache the map once (Status::collect()->mapWithKeys(...)) and reuse it.

#[Name] mutates the case's external identity. Overriding a case name via #[Name('Pending Review')] changes what the magic methods are built from. Renaming a case later becomes a breaking change for every isPending() call — treat those method names as part of your module's public API.

Extension point: custom attributes. When Label isn't enough, subclass or replicate it with your own attribute class, add the target case, and the package will synthesise the accessor (icon(), group()). This is the cleanest way to keep domain metadata off your policies and out of Blade.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource