PHP 8.3 Enums as Domain Citizens in Laravel | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com) [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles  ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

  [ home ](https://msaied.com)    [ articles ](https://msaied.com/articles)    Typed Enums as First-Class Domain Citizens in Laravel with PHP 8.3        On this page       1. [  Why Enums Deserve More Than a label() Method ](#why-enums-deserve-more-than-a-codelabelcode-method)
2. [  Modelling Domain State with Interface-Backed Enums ](#modelling-domain-state-with-interface-backed-enums)
3. [  Eloquent Cast: Zero Boilerplate ](#eloquent-cast-zero-boilerplate)
4. [  Route Model Binding for Enum Segments ](#route-model-binding-for-enum-segments)
5. [  Validation Rule from Enum Cases ](#validation-rule-from-enum-cases)
6. [  PHP 8.3 Enum Constants for Grouping ](#php-83-enum-constants-for-grouping)
7. [  Takeaways ](#takeaways)

  ![Typed Enums as First-Class Domain Citizens in Laravel with PHP 8.3](https://cdn.msaied.com/282/71a8fc3e4cf4239b1bf6d38d57e0b985.png)

  #laravel   #php8.3   #enums   #domain-driven-design   #eloquent  

 Typed Enums as First-Class Domain Citizens in Laravel with PHP 8.3 
====================================================================

     24 Jun 2026      3 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   Why Enums Deserve More Than a label() Method  ](#why-enums-deserve-more-than-a-codelabelcode-method)
2. [  02   Modelling Domain State with Interface-Backed Enums  ](#modelling-domain-state-with-interface-backed-enums)
3. [  03   Eloquent Cast: Zero Boilerplate  ](#eloquent-cast-zero-boilerplate)
4. [  04   Route Model Binding for Enum Segments  ](#route-model-binding-for-enum-segments)
5. [  05   Validation Rule from Enum Cases  ](#validation-rule-from-enum-cases)
6. [  06   PHP 8.3 Enum Constants for Grouping  ](#php-83-enum-constants-for-grouping)
7. [  07   Takeaways  ](#takeaways)

 Why Enums Deserve More Than a `label()` Method
----------------------------------------------

Most Laravel codebases treat backed enums as glorified constants — a `cases()` call for a select box and a `label()` helper bolted on. That leaves real domain logic scattered across services, form requests, and Blade templates. PHP 8.3 enums support interface implementation, constants, and static methods. Laravel 11+ wires them into the framework at every layer. Let's use all of it.

---

Modelling Domain State with Interface-Backed Enums
--------------------------------------------------

Start by defining a contract your enums must honour:

```php
interface HasColour
{
    public function colour(): string;
}

interface Transitionable
{
    /** @return static[] */
    public function allowedTransitions(): array;
}

```

Now implement both on a `OrderStatus` enum:

```php
enum OrderStatus: string implements HasColour, Transitionable
{
    case Pending   = 'pending';
    case Confirmed = 'confirmed';
    case Shipped   = 'shipped';
    case Cancelled = 'cancelled';

    public function colour(): string
    {
        return match($this) {
            self::Pending   => 'yellow',
            self::Confirmed => 'blue',
            self::Shipped   => 'green',
            self::Cancelled => 'red',
        };
    }

    public function allowedTransitions(): array
    {
        return match($this) {
            self::Pending   => [self::Confirmed, self::Cancelled],
            self::Confirmed => [self::Shipped,   self::Cancelled],
            self::Shipped   => [],
            self::Cancelled => [],
        };
    }

    public function canTransitionTo(self $next): bool
    {
        return in_array($next, $this->allowedTransitions(), strict: true);
    }
}

```

The transition guard lives on the enum itself — no service class required for this logic.

---

Eloquent Cast: Zero Boilerplate
-------------------------------

Laravel casts backed enums natively. Declare the cast and you get type-safe attribute access:

```php
class Order extends Model
{
    protected $casts = [
        'status' => OrderStatus::class,
    ];
}

// Usage
$order->status->colour();          // 'blue'
$order->status->canTransitionTo(OrderStatus::Shipped); // true/false

```

No accessor, no mutator, no string comparison scattered across the codebase.

---

Route Model Binding for Enum Segments
-------------------------------------

Laravel 11 supports explicit enum binding in routes. Register it in `AppServiceProvider`:

```php
Route::get('/orders/status/{status}', OrdersByStatusController::class)
    ->whereIn('status', array_column(OrderStatus::cases(), 'value'));

```

Or use the built-in enum binding — Laravel resolves the backed value automatically:

```php
Route::get('/orders/status/{status}', function (OrderStatus $status) {
    return Order::where('status', $status)->paginate();
});

```

A request to `/orders/status/invalid` returns a 404 without a single line of guard code.

---

Validation Rule from Enum Cases
-------------------------------

Avoid hardcoding allowed values in form requests:

```php
use Illuminate\Validation\Rules\Enum;

class TransitionOrderRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'status' => ['required', new Enum(OrderStatus::class)],
        ];
    }
}

```

Add a custom rule that also checks the transition is legal:

```php
use Illuminate\Contracts\Validation\ValidationRule;

class ValidTransition implements ValidationRule
{
    public function __construct(private readonly OrderStatus $current) {}

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $next = OrderStatus::tryFrom($value);

        if ($next === null || ! $this->current->canTransitionTo($next)) {
            $fail("Cannot transition from {$this->current->value} to {$value}.");
        }
    }
}

```

Inject the current order into the request and compose:

```php
public function rules(): array
{
    return [
        'status' => ['required', new Enum(OrderStatus::class), new ValidTransition($this->order->status)],
    ];
}

```

---

PHP 8.3 Enum Constants for Grouping
-----------------------------------

PHP 8.3 allows typed constants on enums, useful for grouping cases without a helper method:

```php
enum OrderStatus: string implements HasColour, Transitionable
{
    // ... cases above ...

    const OPEN_STATES = [self::Pending, self::Confirmed];
    const CLOSED_STATES = [self::Shipped, self::Cancelled];
}

// Scope
public function scopeOpen(Builder $query): void
{
    $query->whereIn('status', array_column(OrderStatus::OPEN_STATES, 'value'));
}

```

---

Takeaways
---------

- Implement domain interfaces on enums to keep behaviour co-located with state.
- Laravel's native enum cast eliminates accessor/mutator boilerplate entirely.
- Route model binding resolves backed enums automatically and returns 404 on invalid values.
- Compose the `Enum` validation rule with custom rules for business-logic guards.
- PHP 8.3 enum constants let you group cases without polluting models or services.
- `tryFrom()` is your safe entry point whenever deserialising external input.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fmsaied.com%2Farticles%2Ftyped-enums-as-first-class-domain-citizens-in-laravel-with-php-83&text=Typed+Enums+as+First-Class+Domain+Citizens+in+Laravel+with+PHP+8.3) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fmsaied.com%2Farticles%2Ftyped-enums-as-first-class-domain-citizens-in-laravel-with-php-83) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  Can I use a backed enum as an Eloquent cast without any extra configuration?        Yes. Since Laravel 9, you can set the cast to the fully-qualified enum class name and Laravel handles serialisation and deserialisation automatically, including returning null for nullable columns. 

      Q02  What happens if an invalid value is stored in the database for an enum cast?        Laravel will throw a ValueError when it tries to hydrate the model. Guard against this with a database CHECK constraint or a migration that validates existing data before adding the cast. 

      Q03  Are enum constants introduced in PHP 8.3 or were they available earlier?        Basic enum constants (without type enforcement on the constant itself) were available since PHP 8.1 when enums launched. PHP 8.3 refined constant visibility and allowed typed constants, making grouping patterns more explicit. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://msaied.com/articles) 

 [ ![RAG in Laravel: pgvector, Embeddings, and Retrieval-Augmented Generation in Practice](https://cdn.msaied.com/281/8d2ac57c0e69d3ff9f1e68faf0e4d10c.png) laravel ai pgvector 

### RAG in Laravel: pgvector, Embeddings, and Retrieval-Augmented Generation in Practice

Build a production-ready RAG pipeline in Laravel using pgvector, OpenAI embeddings, and a clean retrieval laye...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 24 Jun 2026     4 min read  

  Read    

 ](https://msaied.com/articles/rag-in-laravel-pgvector-embeddings-and-retrieval-augmented-generation-in-practice) [ ![Ship AI with Laravel: Failover, Queues, and Middleware for AI Agents](https://cdn.msaied.com/283/f0a6d6a6f22d9131bacb96bae1bfc10b.png) Laravel AI Agents Queues 

### Ship AI with Laravel: Failover, Queues, and Middleware for AI Agents

Learn how to make Laravel AI agents production-ready with automatic provider failover, background queue proces...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 24 Jun 2026     3 min read  

  Read    

 ](https://msaied.com/articles/ship-ai-with-laravel-failover-queues-and-middleware-for-ai-agents) [ ![Laravel Queues: Reliable Job Middleware, Idempotency, and Graceful Failure Handling](https://cdn.msaied.com/280/3b6ba5c26ef22b77f3c39c5d9a6b76e9.png) laravel queues jobs 

### Laravel Queues: Reliable Job Middleware, Idempotency, and Graceful Failure Handling

Beyond basic dispatching: how to build production-grade queue jobs with custom middleware, idempotency guards,...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 24 Jun 2026     4 min read  

  Read    

 ](https://msaied.com/articles/laravel-queues-reliable-job-middleware-idempotency-and-graceful-failure-handling) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

Explore

- [Home](https://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [Contact](https://msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
