PHP 8.3 Enums as Domain Citizens in Laravel | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://www.msaied.com/public) - [Home](https://www.msaied.com/public)
- [Projects](https://www.msaied.com/public/projects)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [About](https://www.msaied.com/public#about)

           [  Contact](https://www.msaied.com/public#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://www.msaied.com/public)
- [ProjectsCase studies](https://www.msaied.com/public/projects)
- [ArticlesEngineering notes](https://www.msaied.com/public/articles)
- [CertificatesCredentials](https://www.msaied.com/public/certificates)
- [AboutHow I work](https://www.msaied.com/public#about)
- [ContactGet in touch](https://www.msaied.com/public#contact)

  [Start a conversation](https://www.msaied.com/public#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://www.msaied.com/public)
2. /
3. [Articles](https://www.msaied.com/public/articles)
4. /
5. Typed Enums as First-Class Domain Citizens in Laravel with PHP 8.3

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

 Go beyond simple enum labels. Learn how to attach behaviour, implement interfaces, and use backed enums as Eloquent casts, route bindings, and validation rules without polluting your models.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://www.msaied.com/public#person) Published 24 Jun 2026 · Updated 24 Jun 2026 · 3 min read

ShareCopy linkCopied

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

  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)

 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.

- [laravel](https://www.msaied.com/public/articles?search=laravel)
- [php8.3](https://www.msaied.com/public/articles?search=php8.3)
- [enums](https://www.msaied.com/public/articles?search=enums)
- [domain-driven-design](https://www.msaied.com/public/articles?search=domain-driven-design)
- [eloquent](https://www.msaied.com/public/articles?search=eloquent)

 Frequently asked questions 
---------------------------

  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.

   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.

   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.

   ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)About the author
----------------

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

[About](https://www.msaied.com/public#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articleRAG in Laravel: pgvector, Embeddings, and Retrieval-Augmented Generation in Practice](https://www.msaied.com/public/articles/rag-in-laravel-pgvector-embeddings-and-retrieval-augmented-generation-in-practice) [Next articleLaravel Reverb WebSocket Presence Channels: Private State Without a Redis Bottleneck](https://www.msaied.com/public/articles/laravel-reverb-websocket-presence-channels-private-state-without-a-redis-bottleneck)  

   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)

 ###  Have a technical challenge?

 Tell me what you’re building. I reply within two working days.

[Start a conversation](https://www.msaied.com/public#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/740/cce86edc21eddcbdd2f2454fadaf9c70.png)  · 3 min read### The Pipeline Pattern in Laravel: Custom Pipelines Beyond Middleware

5 Oct 2026 ](https://www.msaied.com/public/articles/the-pipeline-pattern-in-laravel-custom-pipelines-beyond-middleware-1) [ ![](https://cdn.msaied.com/739/2d6897fdcdcf090613f96f72a64b8a78.png)  · 4 min read### MySQL Full-Text Search in Laravel: Indexes, Relevance Scoring, and Boolean Mode

4 Oct 2026 ](https://www.msaied.com/public/articles/mysql-full-text-search-in-laravel-indexes-relevance-scoring-and-boolean-mode) [ ![](https://cdn.msaied.com/738/073696a3fefe18bec825beec5ac658f5.png)  · 4 min read### Laravel Queue Rate-Limited Middleware: Throttling Jobs Without Losing Work

4 Oct 2026 ](https://www.msaied.com/public/articles/laravel-queue-rate-limited-middleware-throttling-jobs-without-losing-work) 

  Have a technical challenge?
----------------------------

Tell me what you’re building. I reply within two working days.

 [Discuss your project ↗](https://www.msaied.com/public#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://www.msaied.com/public)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://www.msaied.com/public/sitemap.xml)
