Speeding Up Laravel Tests with tmpfs (MySQL in RAM)
If your Laravel test suite is painfully slow and does a lot of database work (multi-tenancy, migrations, seeding), the bottleneck is almost…
PHP 8.1 introduced enums as a native language feature, giving developers a new way to work with sets of related constants. Enums are an improvement over traditional approaches like class constants or arrays:
In Laravel applications, enums are particularly useful for managing things like:
At its simplest, an enum is a special class that defines a set of named constants. Here's a basic example:
enum OrderStatus
{
case PENDING;
case PROCESSING;
case COMPLETED;
case CANCELLED;
}
This simple structure already gives you several advantages:
cases()Here's how you might use it:
$status = OrderStatus::PENDING;
// The match expression ensures you handle all possible cases
$message = match ($status) {
OrderStatus::PENDING => 'Order is waiting for processing',
OrderStatus::PROCESSING => 'Order is being processed',
OrderStatus::COMPLETED => 'Order has been completed',
OrderStatus::CANCELLED => 'Order was cancelled',
};
If you're familiar with MySQL's ENUM type, you might be wondering how PHP enums work with databases. Unlike MySQL ENUMs, PHP enums are stored as regular strings or integers in your database. Here's a complete example:
// 1. First, define your enum
enum OrderStatus: string
{
case PENDING = 'pending';
case PROCESSING = 'processing';
case COMPLETED = 'completed';
case CANCELLED = 'cancelled';
}
// 2. Create your migration
class CreateOrdersTable extends Migration
{
public function up(): void
{
Schema::create('orders', function (Blueprint $table) {
$table->id();
// Store the enum as a string column
$table->string('status', 20);
$table->timestamps();
});
}
}
// 3. Set up your model
class Order extends Model
{
protected $fillable = ['status'];
// This tells Laravel to automatically convert
// between database strings and enum instances
protected $casts = [
'status' => OrderStatus::class
];
}
// 4. Now you can use it naturally in your code
$order = Order::create([
'status' => OrderStatus::PENDING
]);
// Laravel handles the conversion automatically
$order->status = OrderStatus::PROCESSING;
$order->save();
// You can query using the enum directly
$pendingOrders = Order::where('status', OrderStatus::PENDING)->get();
The key differences from MySQL ENUMs are:
Basic enums are useful, but they really shine when you add methods and properties. Let's enhance our OrderStatus enum with some practical features:
enum OrderStatus: string
{
case PENDING = 'pending';
case PROCESSING = 'processing';
case COMPLETED = 'completed';
case CANCELLED = 'cancelled';
// Get a user-friendly label
public function label(): string
{
return match($this) {
self::PENDING => 'Awaiting Processing',
self::PROCESSING => 'In Progress',
self::COMPLETED => 'Order Complete',
self::CANCELLED => 'Order Cancelled',
};
}
// Get a color for UI display
public function color(): string
{
return match($this) {
self::PENDING => 'yellow',
self::PROCESSING => 'blue',
self::COMPLETED => 'green',
self::CANCELLED => 'red',
};
}
// Business logic
public function canCancel(): bool
{
return in_array($this, [self::PENDING, self::PROCESSING]);
}
// Static helper for forms
public static function options(): array
{
return collect(self::cases())->mapWithKeys(
fn (self $status) => [$status->value => $status->label()]
)->all();
}
}
Now you can use these methods in your views and controllers:
// In your Blade view
<span class="text-{{ $order->status->color() }}-600">
{{ $order->status->label() }}
</span>
// In a dropdown
<select name="status">
@foreach(OrderStatus::options() as $value => $label)
<option value="{{ $value }}">{{ $label }}</option>
@endforeach
</select>
// In your controller
if ($order->status->canCancel()) {
$order->cancel();
}
Laravel makes it easy to validate enum values in your forms:
use Illuminate\Validation\Rules\Enum;
class UpdateOrderRequest extends FormRequest
{
public function rules(): array
{
return [
'status' => ['required', new Enum(OrderStatus::class)],
// You can add custom rules too
'status' => [
'required',
new Enum(OrderStatus::class),
function ($attribute, $value, $fail) {
$newStatus = OrderStatus::from($value);
if (!$this->order->status->canTransitionTo($newStatus)) {
$fail("Cannot change status from {$this->order->status->value} to {$value}");
}
}
]
];
}
}
Enums are perfect for implementing simple state machines. Here's an example that manages order processing:
enum OrderState: string
{
case CART = 'cart';
case CHECKOUT = 'checkout';
case PAID = 'paid';
case SHIPPED = 'shipped';
// Define valid transitions
public function canTransitionTo(self $newState): bool
{
return match($this) {
self::CART => $newState === self::CHECKOUT,
self::CHECKOUT => $newState === self::PAID,
self::PAID => $newState === self::SHIPPED,
self::SHIPPED => false, // End state
};
}
// Get possible next states
public function nextStates(): array
{
return match($this) {
self::CART => [self::CHECKOUT],
self::CHECKOUT => [self::PAID],
self::PAID => [self::SHIPPED],
self::SHIPPED => [],
};
}
// Get action required for this state
public function requiredAction(): string
{
return match($this) {
self::CART => 'Complete your order',
self::CHECKOUT => 'Process payment',
self::PAID => 'Prepare for shipping',
self::SHIPPED => 'No action needed',
};
}
}
// Use it in your Order model
class Order extends Model
{
protected $casts = [
'state' => OrderState::class
];
public function transition(OrderState $newState): bool
{
if (!$this->state->canTransitionTo($newState)) {
throw new InvalidStateTransitionException(
"Cannot transition from {$this->state->value} to {$newState->value}"
);
}
$this->state = $newState;
return $this->save();
}
}
Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.
Take a look at vroni.com