somnambulist/enumeration
Strongly-typed enumeration value objects for PHP and Laravel. Define constants once and get validation rules, Eloquent casting, instance comparison, and attribute access, plus helpers for safe construction. Ideal for modelling fixed sets such as statuses, types, and roles.
composer require somnambulist/enumeration — this is a plain PHP library. There is no service provider, no config file to publish, and Laravel's package discovery has nothing to do. You just use the classes.src/ directory, specifically the base Enumeration / AbstractEnumeration class. It is a small file — read __callStatic, members(), value(), key(), and the comparison helper before you write your first enum. The test suite is the real documentation; the README is thin.Somnambulist\Components\Enumeration\…, not Eloquent\…. If you are migrating off the original eloquent/enumeration, a project-wide namespace find/replace plus a composer remove / composer require is the whole migration.use Somnambulist\Components\Enumeration\Enumeration;
final class OrderStatus extends Enumeration
{
public const PENDING = 'pending';
public const PAID = 'paid';
public const SHIPPED = 'shipped';
public const CANCELLED = 'cancelled';
}
$status = OrderStatus::PENDING();
$status->value(); // 'pending' — the persisted representation
$status->key(); // 'PENDING' — the human / log representation
value() or key() is what you write to the database. The library stores both; the choice is a domain decision, not a library one.OrderStatus::members() returns every instance — that is your drop-in source for select options, in: validation rules, and factory states.enum column or a DB check constraint, and stop typing 'pending' anywhere in the app.CastsAttributes for recent Laravel in the base library — write a ten-line cast: OrderStatus::from($value) on get, $value->value() on set (check src/ for a shipped cast first). Laravel 9+ native backed enums work too, but you lose the enumeration helpers.Rule::in(array_map(fn ($m) => $m->value(), OrderStatus::members()))
Better: a small custom rule that takes the class string — new EnumRule(OrderStatus::class) — so it is reusable across requests.
Order::factory()->state(['status' => OrderStatus::PENDING()]). Referencing the enum in factories is what keeps seed data honest.===, since it survives serialization round-trips:if ($order->status->equals(OrderStatus::PAID())) { /* ... */ }
->value() for machine clients, ->key() for labels, or ['key' => ..., 'value' => ...] for a dropdown payload.members() to value/label pairs keeps views readable:@foreach (OrderStatus::members() as $status)
<option value="{{ $status->value() }}">{{ $status->key() }}</option>
@endforeach
switch statements scattered across controllers.members() to assert every status is handled by policies, factories, and admin filters. That exhaustive-coverage test is where enums pay for themselves.eloquent/enumeration, but the namespace is Somnambulist\Components\Enumeration. Every blog post and Stack Overflow answer you find will use the old one — translate mentally.OrderStatus::PENDING() (a singleton instance), not OrderStatus::PENDING (a string constant). Forgetting the parentheses is the number-one bug with this library, and PHP will not warn you.members() inside a loop over thousands of rows. Hoist it.from()-style lookups silently pick one. If you need aliases, add a separate method; do not duplicate values.json_encode on the enum object depends on JsonSerializable support and will leak internal properties if you are not careful. Always convert explicitly at the API boundary.from() throws on an unrecognised value. In your cast, decide deliberately whether legacy rows should throw (fail fast) or return null (tolerant read) — and document it.var_dump shows the internal value and key. Log ->key() so you can grep for the human name rather than the storage value.spatie/enum later a mechanical change.App\Enum base to override __toString and add consistent label() / description() / badgeColor() helpers. The library deliberately leaves presentation policy to you — centralise it once.How can I help you explore Laravel packages today?