davidianbonner/enumerated
Enumerated brings first-class enum support to Laravel. Define backed enums and use them in Eloquent casts, validation rules, and form requests, with helpers for labels, options, and comparisons. Keeps domain values type-safe and readable.
davidianbonner/enumerated is a thin base class that turns a plain PHP class full of const declarations into a queryable enumeration. v8.0.1 is a patch release — the notes are just a diff link between v8.0.0...v8.0.1, with no documented features, deprecations, or breaking changes. Treat ^8.0 as the current line; if you're coming from 7.x, read that diff rather than expecting a migration guide.
Minimal setup (about two minutes):
composer require davidianbonner/enumerated
There is nothing to publish and no service provider wiring to remember — it's a single base class plus a couple of traits. Your first stop should be vendor/davidianbonner/enumerated/src/Enum.php: it's short, and confirming the exact static method names (values(), keys(), toArray(), isValid(), fromValue(), fromKey()) against the source is faster than trusting a blog post.
Then create your first enum:
namespace App\Enums;
use DAB\Enumerated\Enum;
class OrderStatus extends Enum
{
const PENDING = 'pending';
const PAID = 'paid';
const SHIPPED = 'shipped';
const CANCELLED = 'cancelled';
}
First real use case: replace magic strings in a model. Instead of $order->status = 'paid' scattered across controllers, jobs, and Blade views, you write OrderStatus::PAID, and you get OrderStatus::values() for validation and OrderStatus::toArray() for a select box for free. That single swap — status column plus a form — is where the package pays for itself.
Validation. The most common daily use is feeding the value list into a rule:
$request->validate([
'status' => ['required', Rule::in(OrderStatus::values())],
]);
Wrap it in a custom rule class once you have more than three enums, so the Rule::in(...) boilerplate doesn't spread.
Eloquent casting. Store the scalar (string or int) in the column and translate at the boundary. A custom cast keeps models clean:
class OrderStatusCast implements CastsAttributes
{
public function get($model, $key, $value, $attributes)
{
return OrderStatus::fromValue($value); // returns the constant *name*
}
public function set($model, $key, $value, $attributes)
{
return [$key => $value];
}
}
If the package's own accessor trait fits your version, prefer it — but confirm its behaviour on null before adopting it model-wide.
Form selects and Blade. OrderStatus::toArray() gives ['PENDING' => 'pending', ...], which is directly usable in Form::select-style helpers or @foreach. For display, prefer a label map over the raw constant name:
public static function labels(): array
{
return [
self::PENDING => __('orders.status.pending'),
self::PAID => __('orders.status.paid'),
self::SHIPPED => __('orders.status.shipped'),
self::CANCELLED => __('orders.status.cancelled'),
];
}
Because the enum is just a normal class, adding static helpers (labels(), badgeColor(), next()) is the intended extension point — there is no plugin system to learn.
Behaviour, not just data. The biggest win over raw constants is pairing the enum with match:
return match ($order->status) {
OrderStatus::PENDING => 'Awaiting payment',
OrderStatus::PAID => 'Ready to ship',
default => 'Unknown',
};
Factories and seeders. fake()->randomElement(OrderStatus::values()) keeps test data in sync automatically, so adding a new status doesn't silently leave your seeders behind.
API resources. Expose both the machine value and the label — clients should never have to hardcode your constant names:
'status' => [
'value' => $this->status,
'label' => OrderStatus::labels()[$this->status] ?? $this->status,
],
Integration points to standardise on: one enum class per domain concept, a labels() map, a form request that validates with Rule::in(...), and a cast on the model. Once those four are in place, a new status is a one-line constant plus a label — and everything else follows.
values() follows declaration order. It uses reflection over the class constants, so reordering constants changes array order. Never rely on positional/index logic against values().fromValue() ambiguous — it returns the first match. Keep values unique, or write a test that asserts count(values()) === count(array_unique(values())).isValid() and in_array can surprise you with '1' vs 1, 0 vs '', or null. Cast request input before validating, and guard null explicitly rather than assuming isValid(null) is false.ENUM, the migration and the PHP class can drift. A string column plus application-level validation is usually the lower-maintenance choice.values() returns array, so completion is lost. Add @return array<string>-style docblocks or PHPStan type aliases on your subclasses; it's cheap and pays back immediately.composer dump-autoload after adding a new enum namespace. PSR-4 misses are the single most common "class not found" cause here.dd(OrderStatus::toArray(), OrderStatus::keys()) tells you instantly whether a value mismatch is in your code or your data.^8.0, let CI prove the bump, and review the diff only if you have anything subclassing the base Enum or relying on its internals. That's the one place a patch-level change can reach you.How can I help you explore Laravel packages today?