Author: Daryle De Silva

  • Hybrid State Management: Database Triggers + Eloquent Casts

    For complex state machines involving multiple boolean flags (like is_canceled, is_on_hold, etc.), using MySQL triggers to compute a single status column is a powerful pattern. This approach prevents data inconsistencies while keeping database queries fast and efficient.

    The Problem: Managing Competing States

    If an order has is_canceled = 1 and is_on_hold = 0, what should the status be? If you compute this logic in PHP every time you query the record, it adds overhead. Storing it redundantly in the database can lead to “drift” where the status column becomes out of sync with the boolean flags.

    The Solution: MySQL Triggers

    By using a database trigger, you can automatically compute the correct status based on the boolean flags whenever a record is inserted or updated. This ensures that your source of truth (the flags) is always reflected in your queryable column (the status).

    Implementation

    // Migration
    Schema::create('orders', function (Blueprint $table) {
        $table->id();
        $table->boolean('is_canceled')->default(false);
        $table->boolean('is_on_hold')->default(false);
        $table->boolean('is_archived')->default(false);
        $table->string('status')->default('active'); // Auto-computed column
        $table->datetime('status_changed_at')->nullable();
        $table->timestamps();
    });
    
    // Create trigger in migration
    DB::unprepared("
        CREATE TRIGGER orders_status_update BEFORE UPDATE ON orders
        FOR EACH ROW
        BEGIN
            DECLARE status_val VARCHAR(50);
            
            SET status_val = CASE
                WHEN NEW.is_canceled = 1 THEN 'canceled'
                WHEN NEW.is_on_hold = 1 THEN 'on_hold'
                WHEN NEW.is_archived = 1 THEN 'archived'
                ELSE 'active'
            END;
            
            IF NEW.status != status_val THEN
                SET NEW.status = status_val;
                SET NEW.status_changed_at = NOW();
            END IF;
        END
    ");
    

    Leveraging in Laravel

    Your business logic can continue using simple, expressive boolean flags while remaining confident that the queryable status is always correct.

    class Order extends Model
    {
        protected $casts = [
            'status' => OrderStatus::class, // Backed Enum
            'is_canceled' => 'boolean',
            'is_on_hold' => 'boolean',
        ];
        
        public function cancel(): void
        {
            $this->is_canceled = true;
            $this->save();
            // The MySQL trigger automatically sets status='canceled'
            // and status_changed_at=NOW()
        }
    }
    

    This hybrid approach combines the best of both worlds: highly expressive business logic in PHP and consistent, high-performance querying at the database layer.

  • Type-Safe Status Management with PHP 8.1+ Backed Enums

    PHP 8.1’s backed enums are perfect for managing database statuses with compile-time safety. Unlike magic strings, enums provide IDE autocomplete, prevent typos, and centralize status logic.

    Why It Matters

    In production applications, hardcoded strings for statuses like 'pending' or 'in_progress' are prone to typos and difficult to refactor. Backed enums solve this by providing a single source of truth for all possible states.

    Implementation

    enum OrderStatus: string
    {
        case PENDING = 'pending';
        case PROCESSING = 'processing';
        case COMPLETED = 'completed';
        case CANCELED = 'canceled';
        case ON_HOLD = 'on_hold';
        
        public function getLabel(): string
        {
            return match($this) {
                self::PENDING => 'Awaiting Review',
                self::PROCESSING => 'In Progress',
                self::COMPLETED => 'Successfully Completed',
                self::CANCELED => 'Order Canceled',
                self::ON_HOLD => 'Paused',
            };
        }
        
        public static function toArray(): array
        {
            return collect(self::cases())
                ->mapWithKeys(fn($status) => [
                    $status->value => $status->getLabel()
                ])
                ->toArray();
        }
        
        public function canTransitionTo(self $newStatus): bool
        {
            return match($this) {
                self::PENDING => in_array($newStatus, [self::PROCESSING, self::CANCELED, self::ON_HOLD]),
                self::PROCESSING => in_array($newStatus, [self::COMPLETED, self::CANCELED]),
                self::ON_HOLD => in_array($newStatus, [self::PROCESSING, self::CANCELED]),
                default => false,
            };
        }
    }
    

    Integrating with Eloquent

    Laravel makes it incredibly easy to use enums by casting the database value directly to the enum class.

    class Order extends Model
    {
        protected $casts = [
            'status' => OrderStatus::class,
        ];
    }
    
    // Usage
    $order->status = OrderStatus::COMPLETED;  // Type-safe!
    $order->status->getLabel();  // "Successfully Completed"
    OrderStatus::toArray();  // Perfect for