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

Laravel Backed Enums Laravel Package

foxbytehq/laravel-backed-enums

A Laravel package that adds handy helpers to PHP backed enums, including human-readable labels, validation rules, select options, comparisons, and Eloquent-friendly utilities so you can use enums confidently across forms, requests, and models.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require foxbytehq/laravel-backed-enums

The package is auto-discovered; there is no service provider to register by hand. If the release ships a config file, publish it once:

php artisan vendor:publish --tag="backed-enums-config"

Where to look first

  1. The README's "Usage" section — it is the source of truth for the trait names and method signatures in your installed version (this package has moved fast; pin a version and read that tag's docs).
  2. src/ — the traits are small and readable. Skim them before adopting, especially the comparison and translation traits, so you know exactly what is()/label() normalize.
  3. tests/ — the package's own test suite is the best executable documentation for edge cases (raw values vs. cases, missing translations).

First use case — a status enum on a model

namespace App\Enums;

use Foxbytehq\BackedEnums\Traits\BackedEnum; // confirm trait name in README

enum OrderStatus: string
{
    use BackedEnum;

    case Pending  = 'pending';
    case Paid     = 'paid';
    case Refunded = 'refunded';
}

Cast it on the model:

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

Add translations (key layout is configurable; the default is typically enums.{enum}.{value}):

// lang/en/enums.php
return [
    'order_status' => [
        'pending'  => 'Awaiting payment',
        'paid'     => 'Paid',
        'refunded' => 'Refunded',
    ],
];

Then in a Blade view:

<span class="badge">{{ $order->status->label() }}</span>

That is the 10-minute win: no more match statements or __('...') chains in every view, and the label follows app()->setLocale() for free.

Implementation Patterns

1. Rich domain comparisons instead of ===

The value of the comparison trait is readability at call sites and tolerance for raw input:

// before
if ($order->status === OrderStatus::Paid || $order->status === OrderStatus::Refunded) { ... }

// after
if ($order->status->in(OrderStatus::Paid, OrderStatus::Refunded)) { ... }

Use is() / isNot() in policies, guards, and listeners; use in() / notIn() when branching on several states. Keep the enum as the single place that encodes "which states count as settled" — put it behind a helper method on the enum itself so the rule lives in one file:

public function isSettled(): bool
{
    return $this->in(self::Paid, self::Refunded);
}

2. API resources and Inertia props

Don't leak raw enum objects to the client — shape them explicitly:

return [
    'status'      => $order->status->value,
    'status_label'=> $order->status->label(),
];

For select inputs, build options once and cache the array per request:

public function options(): array
{
    return collect(OrderStatus::cases())
        ->map(fn ($c) => ['value' => $c->value, 'label' => $c->label()])
        ->all();
}

If the package exposes options() / toArray() / values(), prefer those over hand-rolled collect(cases()) loops, but verify the shape it returns — toArray() typically yields value => label, which is exactly what Form::select or a <select> loop wants.

3. Validation

Pair the enum with Laravel's enum rule and the package's value list:

$request->validate([
    'status' => ['required', Rule::enum(OrderStatus::class)],
]);

For a "filter by any of these" query string, validate against values() so a new case is accepted automatically:

'statuses'   => ['array'],
'statuses.*' => [Rule::in(OrderStatus::values())],

This is the pattern that pays for itself: adding a case to the enum propagates to validation, select boxes, and translations without touching controllers.

4. Localized output at request time

Resolve labels inside the request/response cycle, never at boot or in a cached static. label() reading __() at call time means the same enum serialises correctly for different Accept-Language headers — good for multi-locale APIs, but it also means you must not memoize labels in a long-running singleton if you switch locales per request.

5. Testing

Factory states read better with enums:

Order::factory()->create(['status' => OrderStatus::Paid]);
$this->assertDatabaseHas('orders', ['status' => OrderStatus::Paid->value]);

Assert labels explicitly in feature tests when the label is user-facing copy:

$this->get('/orders')->assertSee(OrderStatus::Paid->label());

This catches missing translation keys before production — a missing key usually renders the key string itself, which is easy to miss in review.

6. Extension points

Enums can carry their own methods alongside the package traits, so put app-specific behavior (colors, icons, transition maps) directly on the enum rather than in a helper. If you need shared behavior across many enums (e.g. a color() with a sensible default), define App\Enums\Concerns\HasColor and use it alongside the package trait.

Gotchas and Tips

  • Verify method names against your installed version. The public API has changed between releases. composer show foxbytehq/laravel-backed-enums and read that tag's README; don't copy snippets from an older blog post.
  • Trait method precedence. A method you define on the enum overrides the trait's method of the same name — silently. If label() starts returning something unexpected after you "just added a helper," check for a name collision. Don't shadow label(), is(), in(), values().
  • is() with raw input. Comparison against a request string works only if the package normalizes the type. For an int-backed enum, $case->is($request->input('status')) compares an int to a string. Cast at the boundary ((int) $request->input(...)) or use OrderStatus::tryFrom($value) and compare cases.
  • Translation cache. In production, new language keys may not appear until caches are cleared (php artisan optimize:clear on deploy). In tests, missing keys render the key path — search rendered HTML for enums. to catch regressions.
  • Fallbacks. Decide early what an untranslated case renders. Most implementations fall back to the key or the case name; if that is unacceptable, add a defaultLabel() or a match fallback in a wrapper trait.
  • Serialization differs from toArray(). Backed enums serialise to their scalar value in json_encode/response()->json(); the localized label is not included automatically. Always build API payloads explicitly.
  • Query building. where('status', OrderStatus::Paid) works; whereIn('status', $cases) needs OrderStatus::values() (or array_column($cases, 'value')). Don't pass an array of enum objects to the query builder and hope.
  • Database columns. Store string/integer columns, not native ENUM columns. Adding a case should be a code change only; a DB ENUM turns it into a migration and a locking schema change.
  • Static analysis. PHPStan/Psalm won't infer array shapes from values() or options() without docblocks. Add @return list<string> style annotations in a local wrapper trait to keep level 6+ clean.
  • Blade/Inertia caching. If you cache rendered fragments containing labels, the cache key must include the locale. This is the most common i18n bug introduced by label-aware enums.
  • Don't put container-dependent logic in the enum. Enums are instantiated statically; resolving services via app() inside enum methods works but makes unit tests fragile. Keep enum methods pure — pass dependencies in.
  • Debugging tip. When a label looks wrong, hit the enum directly in php artisan tinker (OrderStatus::Paid->label()) after app()->setLocale('fr'). That isolates "translation missing" from "trait not applied" from "wrong locale in the request."
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource