datomatic/enum-helper
Laravel helper for PHP enums that adds labels, descriptions, colors, icons, select options, validation rules, and Eloquent casts. Expose enum values and names to build forms, APIs, and admin UIs with less boilerplate.
This is a small, focused package that adds ordering/comparison semantics to PHP 8.1+ enums — it is a language-level helper, not a Laravel service provider you configure. That shapes everything about how you adopt it.
composer update with no migration work.src/ and note: the trait's fully-qualified namespace, which comparison methods it exposes (greaterThan/lessThan/isAtLeast style vs. operator-like helpers), and whether it compares value, name, or cases() position.app/Enums on day one — a single enum where ordering genuinely matters.The best first use case is a state/severity enum in a domain you already own:
enum OrderStatus: int
{
use Comparable;
case Draft = 10;
case Placed = 20;
case Shipped = 30;
case Delivered = 40;
case Cancelled = 99; // note: not "last" semantically — see Gotchas
}
Then a single guard, e.g. "can't cancel once shipped," expressed in a policy or action class rather than scattered match statements.
Ordering guards in policies and actions. The trait's value is turning match/in_array ladders into one readable expression:
public function cancel(Order $order): bool
{
return $order->status->lessThan(OrderStatus::Shipped);
}
This is the pattern to standardise on. It reads as intent, works in tests without a database, and survives adding a new case in the middle of the ordering.
Explicit rank values, not declaration order. Prefer integer-backed enums with spaced values (10/20/30) so you can insert a case later without renumbering persisted data. If the trait compares by cases() position, be aware that reordering the case declarations silently changes business logic — that is the single biggest correctness risk with this package.
Keep the trait out of enums that are only categorical. PaymentMethod, Country, Currency — these should never be comparable. A comparable enum is a claim that there is a total order, and reviewers should push back on the trait appearing on enums where that claim is false.
State-machine transitions. Pair the trait with a transition map so comparisons control guards while an explicit map controls allowed edges; don't let comparisons become the transition table. Cancelled = 99 above is a good example: it sorts last numerically but isn't "further along" — model it as a terminal state, not a rank.
Sorting collections. Use the trait's comparison inside sort()/sortBy() callbacks rather than relying on ->value directly, so the ordering rule stays in one place:
$orders->sort(fn ($a, $b) => $a->status->compareTo($b->status));
Testing. Comparison logic is pure and cheap — cover it with a small matrix of enum pairs in a unit test, not feature tests. Assert both directions (a ≥ b and b ≤ a) since trait-based comparators frequently get the operand order inverted in one branch.
Blade and API layers. Expose ->value or a labelled string, not the enum comparison API. The trait is for server-side decision-making; the front end should receive a stable scalar and, if it needs ordering, the integer rank explicitly.
value unless the package does something smarter — almost always the wrong semantics ('shipped' < 'delivered' is false). Use integer backing for anything orderable, or confirm exactly what the trait compares before shipping.$rank property will fail at class-load time. If you need a custom rank, use a method or a match inside your own enum.compareTo or the trait adds value()-adjacent helpers, you'll get a fatal error. Grep your enums before applying the trait.OrderStatus to TicketStatus should throw — don't wrap the call in a try/catch to "make it work." Type the parameters tightly instead.where('status', '>', $x) still uses raw column values. Convert to whereIn('status', $allowedValues) built from a filtered cases() list; never assume the trait influences query building.$order->status, you have the enum, so the trait applies directly — but $order->status->value is what you persist. Don't pass the enum object into update() on a non-enum-cast column.value in JSON; adding comparison methods doesn't change payloads. But if you switch an enum from string- to int-backed to get sane ordering, that is a breaking API change — plan it as a migration with a cast/accessor, not a rename.^2.2 and let the lockfile prove it; there is no reason to hold back the bump. Contributed features in small packages can be less battle-tested than the maintainer's own code, though — worth a quick source read of the comparison implementation and one extra test on your own ordering assumptions before relying on it in a payment or fulfilment path.How can I help you explore Laravel packages today?