chadsikorra/php-simple-enum
A lightweight, dependency-free enum implementation for PHP. Define constant sets with helper methods for validation, value lookup, and comparison, useful where native enums aren't available. Simple API that's easy to drop into existing projects.
Install it like any other small utility package:
composer require chadsikorra/php-simple-enum
Before writing anything, do two things:
vendor/chadsikorra/php-simple-enum/composer.json and note the PSR-4 namespace. The package is tiny, so the entire API surface is one or two classes — read them end to end (5 minutes) rather than trusting blog snippets.There is almost certainly no service provider, no config file, and nothing to publish. Treat it as a library you use in your own classes, not a framework integration.
First use case: kill a magic-number column. The fastest win is any integer column where you've been writing if ($order->status === 2) in five places. Define a class once:
namespace App\Enums;
use ChadSikorra\SimpleEnum\Enum; // verify actual namespace first
final class OrderStatus extends Enum
{
public const PENDING = 0;
public const PAID = 1;
public const SHIPPED = 2;
public const CANCELLED = 3;
}
Then reference OrderStatus::PAID everywhere instead of 1. That alone is worth the dependency. The flag enum variant is the second thing to try — see below.
Validation at the boundary. Every enum class of this style exposes a way to check membership (typically isValid(), isValidValue(), or similar — confirm the exact name). Wrap it in a Laravel validation rule so bad input never reaches your domain:
use Illuminate\Contracts\Validation\Rule;
class EnumRule implements Rule
{
public function __construct(private string $enumClass) {}
public function passes($attribute, $value): bool
{
return $this->enumClass::isValid($value);
}
public function message(): string
{
return 'The :attribute is not a valid value.';
}
}
Register it once and reuse across Form Requests. This is where the package earns its keep in a Laravel app.
Populating selects. Enum classes usually expose the full value set (toArray(), getValues(), getConstants()). Use that to build dropdowns so the UI and the validator can never drift apart:
return view('orders.edit', [
'statuses' => OrderStatus::toArray(),
]);
If only labels are exposed, map them yourself in a small presenter rather than hardcoding labels in Blade.
Eloquent casts. The package is framework-agnostic, so it won't ship a cast. Write one — it's ten lines and pays off in every model:
class EnumCast implements CastsAttributes
{
public function __construct(private string $enumClass) {}
public function get($model, $key, $value, $attributes)
{
return $value === null ? null : (int) $value;
}
public function set($model, $key, $value, $attributes)
{
if (! $this->enumClass::isValid($value)) {
throw new InvalidArgumentException("Invalid {$key}");
}
return $value;
}
}
Validate in set() — that's the single choke point where a bad value can enter the database.
Flag enums for permissions. The bitmask variant is the more interesting half. Define values as powers of two:
final class Permissions extends FlagEnum
{
public const READ = 1;
public const WRITE = 2;
public const DELETE = 4;
public const ADMIN = 8;
}
Store the combined integer in one column, then use the package's flag helpers (hasFlag(), addFlag(), removeFlag(), or bitwise &/| if the API is thin) for checks. Keep a labels() map in the class so you can render checkboxes from a single source of truth.
Testing. Because everything is constants, assertions are trivial and readable: $this->assertTrue(Permissions::hasFlag($user->permissions, Permissions::WRITE)). Add a test that asserts every constant is unique and, for flag enums, that each value is a power of two — that catches the one mistake that silently corrupts bitmask data.
isValid() will still pass but toArray() key-collision behavior is undefined. Add a unit test asserting uniqueness across getConstants().3 looks fine but breaks combination logic. Enforce with a test: ($v & ($v - 1)) === 0.0 is falsy. If your enum has a 0 case (very common for "pending"), guard against if ($status) idioms — always use isValid() or !== null."1" !== 1 bugs in queries.Enum::labels(), Enum::toSelectArray(), a validation rule, a cast — you write. Put these in one App\Enums\Concerns trait so every enum gets them for free.json_encode($model) will output the raw int/string, not a label. If your API needs labels, add an accessor rather than changing the stored value.app/Support and drop the Composer requirement in an afternoon — keep your usage funneled through your own base class or trait to make that swap trivial.BaseEnum extends Enum and have every application enum extend that, not the package class directly. Then any package-level behavior change, or a future migration off the package, touches one file.How can I help you explore Laravel packages today?