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://www.msaied.com) [ Home ](https://www.msaied.com) [ Projects ](https://www.msaied.com/projects) [ Articles  ](https://www.msaied.com/articles) [ Certificates ](https://www.msaied.com/certificates) [ Contact ](https://www.msaied.com#contact-section) 

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

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

  [ home ](https://www.msaied.com)    [ articles ](https://www.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%2Fwww.msaied.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%2Fwww.msaied.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://www.msaied.com/articles) 

 [ ![Read/Write Splitting, Connection Pooling, and Sticky Reads in Laravel](https://cdn.msaied.com/295/d977bd189583149245c03d6d763d9db5.png) laravel database performance 

### Read/Write Splitting, Connection Pooling, and Sticky Reads in Laravel

Learn how Laravel's database layer handles read/write splitting, when sticky reads matter, and how to layer Pg...

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

 26 Jun 2026     4 min read  

  Read    

 ](https://www.msaied.com/articles/readwrite-splitting-connection-pooling-and-sticky-reads-in-laravel-2) [ ![Laravel Service Container: Contextual Binding, Tagging, and Method Injection](https://cdn.msaied.com/294/e5b9d047bd33c3f8b80069ef6a178884.png) laravel service-container dependency-injection 

### Laravel Service Container: Contextual Binding, Tagging, and Method Injection

Go beyond basic binding. Learn how contextual binding resolves different implementations per consumer, how tag...

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

 26 Jun 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/laravel-service-container-contextual-binding-tagging-and-method-injection-1) [ ![PostgreSQL JSONB in Laravel: Indexing, Querying, and Casting Without the Pain](https://cdn.msaied.com/293/f392a5ea52536901eac9677ffa2d070d.png) laravel postgresql eloquent 

### PostgreSQL JSONB in Laravel: Indexing, Querying, and Casting Without the Pain

JSONB columns unlock flexible schemas, but most Laravel apps leave performance on the table. Learn how to inde...

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

 25 Jun 2026     4 min read  

  Read    

 ](https://www.msaied.com/articles/postgresql-jsonb-in-laravel-indexing-querying-and-casting-without-the-pain) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://www.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://www.msaied.com)
- [Projects](https://www.msaied.com/projects)
- [Articles](https://www.msaied.com/articles)
- [Certificates](https://www.msaied.com/certificates)
- [Contact](https://www.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)
