cerbero/laravel-enum
Cerbero Laravel Enum adds type-safe enum classes to Laravel, with validation rules, model casting, and helper methods for labels and values. A clean way to replace scattered constants with organized, reusable enum objects.
Install with Composer:
composer require cerbero/laravel-enum
There is nothing to publish and no service provider to register — it's a trait-only package. Check composer.json for the PHP/Laravel constraint of the version you install; enums require PHP 8.1+, so older projects will need to upgrade first.
Where to look first:
README.md — canonical, versioned list of what the trait exposes.src/Concerns/LaravelEnum.php — the trait you add to your enums.src/Concerns/Metable.php — metadata helpers for labels/colors/icons.tests/ — executable examples of every helper. When in doubt, the tests are more reliable than blog posts.First use case — a <select> in five minutes:
use Cerbero\LaravelEnum\Concerns\LaravelEnum;
enum Status: string
{
use LaravelEnum;
case Draft = 'draft';
case Published = 'published';
case Archived = 'archived';
}
Status::names(); // ['Draft', 'Published', 'Archived']
Status::values(); // ['draft', 'published', 'archived']
Status::options(); // name => value map, designed for form select generation
<select name="status">
@foreach (Status::options() as $name => $value)
<option value="{{ $value }}">{{ $name }}</option>
@endforeach
</select>
Verify the direction of options() in the README for your version — it is built for <select> generation but the map direction has been flipped in some helper packages. If your template expects value => label, wrap it in array_flip().
Rule::enum(Status::class) for value validation. The package does not replace it."Published") rather than the value, normalize in prepareForValidation():
protected function prepareForValidation(): void
{
$this->merge([
'status' => Status::fromName($this->input('status'))->value,
]);
}
Status::values() or Status::names() and feed it into Rule::in(...).protected $casts = ['status' => Status::class];. The trait doesn't replace it — it only adds helpers around the enum.where('status', Status::Published) works on backed enums), or use ->value if you want explicit scalars.Status::tryFrom($request->input('status')) for values, tryFromName() for names. Never call the throwing variants on user input.Replace growing === chains with collection-style comparisons:
if ($post->status->is(Status::Draft, Status::Archived)) { /* ... */ }
if ($post->status->in([Status::Draft, Status::Archived])) { /* ... */ }
if (! $post->status->isNot(Status::Published)) { /* ... */ }
Keep a plain === when there are only two cases — the helpers earn their keep as lists grow.
toArray() shape inside a resource or you'll get a map {Name: value} instead of the scalar.return [
'status' => $this->status->value,
'status_label' => $this->status->name,
];
Status::options() for lookup endpoints / config dumps instead of building them ad hoc.Metable keeps presentation data (colors, icons, badge classes) out of the enum's core semantics:
public function meta(): array
{
return match ($this) {
self::Draft => ['color' => 'gray', 'icon' => 'pencil'],
self::Published => ['color' => 'green', 'icon' => 'check'],
self::Archived => ['color' => 'slate', 'icon' => 'archive'],
};
}
Then resolve metadata in your Blade component or API transformer. This is the right place for UI-only attributes — not business logic.
Add your own static convenience methods (default(), fromRequest(), forAdminPanel()). If you need to override a trait helper, alias it:
use LaravelEnum { options as frameworkOptions; }
public static function options(): array
{
return self::frameworkOptions();
}
$this->assertSame(Status::Published, Status::fromName('Published'));
$this->assertNull(Status::tryFromName('Nope'));
$this->assertTrue(Status::Published->is(Status::Published, Status::Draft));
values(), from(), tryFrom(), option maps) only makes sense on backed enums. Pure enums still get name-based helpers, but don't expect scalars.fromName() throws; tryFromName() returns null. Always use the try* variant on request input and validate the null case.fromName('published') will not match case Published. Normalize input (ucfirst, Str::studly) before calling, or add a small wrapper.values(), options(), or is() yourself causes a compile-time error. Alias the trait method and call through the alias.->value in job payloads and rehydrate with tryFrom() on consume. Same rule applies to cache entries and signed URLs.$casts is still your job. The trait does not register casts automatically. Hydration is native Eloquent behavior; the package only adds convenience methods.src/Concerns/ rather than assuming. If IDE autocompletion feels incomplete on a large enum, add @method docblocks — the trait methods are real, but static analysis sometimes needs help.composer show cerbero/laravel-enum to confirm the installed version, then cross-reference src/Concerns/LaravelEnum.php. A quick Status::cases() sanity check tells you whether the problem is the package or the enum definition itself.tearDown if a test asserts on metadata, and clear on Octane workers when underlying data changes.How can I help you explore Laravel packages today?