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 Enum Laravel Package

mad-web/laravel-enum

A Laravel package that provides a lightweight enum implementation with model attribute casting, validation rules, translations, and helper methods for working with predefined values throughout your application.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish (one-time):

composer require mad-web/laravel-enum
php artisan vendor:publish --provider="MadWeb\Enum\EnumServiceProvider"

Laravel's package auto-discovery registers the service provider, which in turn registers the make:enum Artisan command and the Eloquent cast. Publishing gives you the config and the lang/en/enums.php file used for labels. If you only need the enum class and cast, you can skip publishing and create the lang file yourself.

Look here first, in this order:

  1. config/enum.php (if published) — namespace/root path for generated enums and label conventions.
  2. lang/{locale}/enums.php — the translation file the label helpers read from.
  3. The base MadWeb\Enum\Enum class in vendor/mad-web/laravel-enum/src — this tells you exactly which helpers (getValue(), getValues(), getLabel(), etc.) exist in the version you installed. Confirm names against the README; the package is low-activity so docs and code drift.
  4. The cast class (typically MadWeb\Enum\Casts\EnumCast or similar) — you'll reference its FQCN in $casts.

First use case (the canonical one): a status column on a model, rendered as a human label.

php artisan make:enum UserStatus
// app/Enums/UserStatus.php
namespace App\Enums;

use MadWeb\Enum\Enum;

class UserStatus extends Enum
{
    const Active   = 'active';
    const Pending  = 'pending';
    const Disabled = 'disabled';
}
// app/Models/User.php
protected $casts = [
    'status' => \App\Enums\UserStatus::class, // or explicit cast class: \MadWeb\Enum\Casts\EnumCast::class.':'.UserStatus::class
];
// lang/en/enums.php
return [
    'user_status' => [
        'active'   => 'Active',
        'pending'  => 'Awaiting approval',
        'disabled' => 'Disabled',
    ],
];
{{ $user->status->getLabel() }}

If getLabel() isn't the method your version ships, expose your own thin method on the enum (see Implementation Patterns) rather than sprinkling __() calls at the call sites.


Implementation Patterns

1. Casting on models

Use the package cast in $casts so $model->status is an enum instance instead of a raw scalar. Nullable columns need the cast to respect null — verify your version does (older casts sometimes instantiate on null). Prefer explicit cast syntax in one place:

'status' => EnumCast::class . ':' . UserStatus::class,

Then guard reads with ?-> if the column is nullable, or add a default in the migration so it never is.

2. Select options for forms

Expose the enum as a [value => label] map for Blade selects and for API-driven forms:

// app/Enums/UserStatus.php
public static function options(): array
{
    return collect(self::getValues())
        ->mapWithKeys(fn ($v) => [$v => self::from($v)->getLabel()])
        ->all();
}
<select name="status">
    @foreach (\App\Enums\UserStatus::options() as $value => $label)
        <option value="{{ $value }}" @selected(old('status', $user->status?->getValue()) === $value)>
            {{ $label }}
        </option>
    @endforeach
</select>

3. Validation

Keep validation tied to the enum, not a duplicated array of strings, so adding a case is a one-line change:

$request->validate([
    'status' => ['required', Rule::in(UserStatus::getValues())],
]);

4. Query scopes and where clauses (this is where casting does not help)

Query builder clauses are raw DB values. Pass ->getValue() or the constant, never the enum instance:

User::where('status', UserStatus::Active()->getValue())->get();
User::whereIn('status', [UserStatus::Active()->getValue(), UserStatus::Pending()->getValue()]);

Wrap common ones in a model scope so callers don't have to remember:

public function scopeActive(Builder $q): Builder
{
    return $q->where('status', UserStatus::Active()->getValue());
}

5. Domain behaviour on the enum

Because the enum is a real class, put domain logic there instead of in controllers:

public function isFinal(): bool
{
    return in_array($this->getValue(), [self::Disabled], true);
}

public function transitionsTo(self $next): bool
{
    return match ($this->getValue()) {
        self::Pending => $next->getValue() === self::Active,
        self::Active  => $next->getValue() === self::Disabled,
        default       => false,
    };
}

6. Policies, gates, factories

  • Gates: compare with is()/equals() (or === on getValue()), never on the object identity of two freshly constructed instances.
  • Factories: feed raw values, since the DB layer bypasses the cast on write in some versions — 'status' => UserStatus::Pending()->getValue() is the safe form. If the cast supports assignment of instances you can pass the instance directly, but the explicit value is version-proof.
  • Migrations: match column type to value type — string('status')->default(UserStatus::Pending). Note that a constant in a migration is a code dependency; if you reorder enum values later, old migrations still run fine but new environments will use whatever the current constant is.

7. API resources

Serialize the scalar, not the object, to keep payloads stable:

return [
    'status' => $this->status?->getValue(),
    'status_label' => $this->status?->getLabel(),
];

8. Translation labels

Keep label keys in lang/enums.php grouped by enum name; that way php artisan lang:publish and translation review flows pick them up. If you need per-locale labels, add lang/es/enums.php and let the package's label helper resolve through __(). Don't put fallback logic at call sites — centralize it in the enum so every screen behaves the same.

9. Custom helper methods you'll likely want

public static function labels(): array
{
    return array_map(fn ($v) => self::from($v)->getLabel(), self::getValues());
}

public function equals(self $other): bool
{
    return $this->getValue() === $other->getValue();
}

Gotchas and Tips

  • Cast vs query builder is the #1 source of bugs. Eloquent casts fire on model attribute access and assignment, not on where/whereIn/orderBy. Always pass raw values to query clauses.
  • $casts must match how the DB stores the value. String-backed enums on an integer column (or vice versa) will fail on read or silently hydrate differently. Decide once: string values are far more readable in a DB dump and less brittle to reordering, so prefer them unless you have a strong reason.
  • Translation keys are brittle. The label helper usually looks up something like enums.{enum}.{value} — a typo key or a renamed constant produces a raw-key fallback, not an exception. Add an in-app test that iterates getValues() and asserts getLabel() !== $value so missing translations fail CI.
  • Case sensitivity. Constants (Active) and values ('active') are distinct; translation keys and DB values follow the value, not the constant name. Be consistent about camelCase constants and snake/string values.
  • Cache invalidation. Translations and config get cached. After adding new label files or cases, run php artisan optimize:clear (or just config:clear + cache:clear) in local and CI, or you'll chase phantom "missing translation" bugs.
  • Enum instances are not singletons across the app in every version. Compare by value (equals()), not by object identity (===) between two independently created instances.
  • IDE support. Constant-heavy enums with magic static methods (UserStatus::Active()) confuse some static analyzers. Add @method static self Active() docblocks, or stick to constants + a small from() wrapper, so autocompletion and PHPStan keep working.
  • Don't put heavy logic in constructors or static initializers. The base class may instantiate on first access, and constructor side-effects (DB, __(), config reads) are hard to debug when triggered during container boot.
  • Version pinning matters. This package has very few dependents and low activity — pin a specific version in composer.json ("mad-web/laravel-enum": "^X.Y") and check the changelog before major bumps. If the package is ever abandoned, your enum classes are mostly plain constants plus a base class; you can vendor a ~100-line Enum base into app/Enums and swap a single import to escape it.
  • make:enum namespace/path is config-driven. If the generated class lands in the wrong namespace, fix config/enum.php and regenerate rather than editing the stub.
  • Testing enums. Assert on getValue()/getLabel(), not on assertSame($enumInstance, ...) of a new instance. For model tests, use assertDatabaseHas('users', ['status' => UserStatus::Active()->getValue()]).
  • Extension points worth knowing: the base Enum class (subclass it as an app-level base if you want shared helpers), the language file (translate, don't hardcode), and the cast class (extend it if you need e.g. null tolerance or JSON-array casting for multi-select enums). Custom casts are the safest place to add behaviour without forking the package.
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