PHP 8.6 Makes State Machines Easy (Enums + Roles)

PHP 8.6 Makes State Machines Easy (Enums + Roles)

Oct 29, 2026

If your PHP app has a status column, you already have a state machine, but its rules live nowhere. Any code can set an episode to published, whether it's a rough draft or the person clicking the button lacks access, and PHP saves it without complaint.

Scott builds a state machine for a podcast episode workflow with a backed enum, adds editor and admin roles that decide who may make each move, and uses three PHP 8.6 features along the way: readonly property defaults, #[\Override] on class constants, and __debugInfo() on enums.

Chapters: 00:00 Your status column is a state machine 01:11 What is a finite state machine? 02:01 Backed enums for states and transitions 02:50 Enum __debugInfo() in PHP 8.6 03:59 Adding role-based permissions 04:28 Readonly property defaults in PHP 8.6 05:12 #[\Override] on class constants 06:08 Checking legality and permission separately 06:49 Recap 07:35 When PHP 8.6 ships

PHP #SoftwareEngineering #WebDevelopment #PHPDeveloper #PHP86 #StateMachine #Enums

Links: Our Discord - https://discord.gg/aMTxunVx Buy our shirts - https://store.phparch.com/products/community-corner-podcast-t-shirt

Mentioned in this video: Stop Writing PHP Like This (8.6 Fixes It) - https://www.phparch.com/2026/09/stop-writing-php-like-this-8-6-fixes-it/ RFC: Readonly Property Defaults - https://wiki.php.net/rfc/readonly_property_defaults RFC: #[\Override] on Class Constants - https://wiki.php.net/rfc/override_constants RFC: Debugable Enums - https://wiki.php.net/rfc/debugable-enums PHP 8.6 Release Plan - https://wiki.php.net/todo/php86

Scott's Social Media: Website - https://scott.keck-warren.com/ Bluesky - https://bsky.app/profile/scottkeckwarren.bsky.social LinkedIn - https://www.linkedin.com/in/scott-keck-warren-91689810/ Mastodon - https://phpc.social/@scottkeckwarren

PHP Architect Social Media: X: https://x.com/phparch Mastodon: https://phparch.social/@phparch Bluesky: https://bsky.app/profile/phparch.com Discord: https://discord.phparch.com

Subscribe to our magazine: https://www.phparch.com/subscribe/

Thanks to our Partners:

OurCVEs - Your security posture, on autopilot https://ourcves.com/

CodeRabbit - Cut code review time & bugs in half instantly with CodeRabbit. https://www.coderabbit.ai/

PHP Architect Consulting - Your PHP codebase deserves a partner, not a contractor https://www.phparch.com/consulting/

Music provided by Epidemic Sound https://www.epidemicsound.com/

Get PHP Architect Merch at our Store https://store.phparch.com/

Transcript

Scott Keck-Warren Scott Keck-Warren 0:00
If your PHP app has a status column, you already have a state machine, but if you're like most developers, you haven't written down its rules. Any code can set an episode to published, whether it's still a rough draft or the person clicking on the button lacks access. PHP saves it without a complaint. A finite state machine fixes this by listing every state a record can be in and allows transitions between those states. If we add role-based permissions on top of that, we, you can answer 2 questions. Is this a legal move? And is the user allowed to make it? In this video, we'll build a PHP state machine for a podcast episode workflow, add an editor and admin permission layer, and use 3 new PHP 8.6 features along the way: readonly property defaults, override on class constants, and debug information on enums. Hello developers and welcome to the PHP Architect channel. I'm Scott Keck Warren, and today we're turning a loose status column into a state machine that knows who is allowed to move it. If you're new here, we cover topics related to the PHP ecosystem. Make sure you hit subscribe so you don't miss the next one. So before we get started, this video builds on our previous What's New in PHP 8.6. There's a link in the description, so watch that for an overview. At a very basic level, a state machine represents a fixed or finite set of states that a model can be in. We define the allowed transitions out of each state, and the model sits in one state at a time. A state with no transitions out is a terminal state. A traffic light is a simple example. It can only ever have one light on at a time, and they always go green to yellow to red and back to green. We never have Yellow and green on at the same time, and we can never jump from green to red. For our episode example, we're going to allow the following. The published state is a terminal state because once it's in the feed, it should stay there and never come out. And we also are going to define our transitions as what follows on the screen. Back to nums is one of my favorite features in the PHP 8 release line, and they map into state machines with one case per state. Here we're using the match statement, which has no default. So if we add a 6th case and forget its transitions, PHP is going to throw an unhandled match error, which will show up at the runtime, maybe in production, but it stops an episode from landing in the wrong state. The terminal state returns an empty array from next, which makes, which makes it very easy to spot. This code works in PHP 8.1 and up and replaces scattered if status equals draft checks with one place that defines every legal move. Before 8.6, debugging an enum took extra steps because var_dump shows only the case name. PHP 8.6 lets enums define the debug_info magic method so we can add more information to the output, which looks like this. In this example, we're defining— we can define where am I and where can I go, and this same output shows up for our debuggers. We'll be back after this word from our partners. As a programmer, you know that code reviews are critical but are also time-consuming. CodeRabbit acts as your AI copilot, providing instant code review comments and potential impacts of every pull request. Beyond just flagging issues, CodeRabbit provides one-click fix solutions and lets you define custom code quality rules using AST grep patterns, catching subtle issues that traditional static analysis tools might miss. CodeRabbit reviews 1 million pull requests every week across 3 million repositories and is used by 100,000 open source projects. CodeRabbit is free for all open source project repos. Get started today at phpa.me/coderabbit. That's phpa.me/coderabbit. So valid transitions stop illegal jumps, but anyone can still make a legal one. Roles define who may touch which states, so an editor can't publish an episode that isn't ready yet. Each role decides whether it permits a change based on the table on the screen. Our rule is that a role may change state only if both the from_state and the to_state appear in the states list, which allows us to define the abstract role class that's on the screen. So this launches us into our new features in PHP 8.6. Our role class will only run on PHP 8.6 because it uses something called readonly property defaults. If you run this in a pre-PHP 8.6 environment, you're going to get a fatal error, readonly property role states cannot have a default value. PHP 8.1 introduced readonly properties without defaults. So the only place to set one up was the constructor, but 8.6 lifts that restriction. For you, this means cleaner and safer code. Before 8.6, we had to set the value in the constructor or drop the readonly attribute and let a piece of bad code just widen the permissions without, without recourse. Our other role is gonna be the ViewOnly, which inherits the empty default. So it can't move anything. The override attribute is another one of my favorite features. PHP 8.3 added the attribute to mark a method as an override, and PHP reports an error if no parent method exists. It makes renaming safer because overridden logic can't be ignored without a warning or an error in this case. PHP 8.6 is expanding this to class constants so we can mark our name and level as overrides. If we later decide that we want to rename level to ID in the abstract class but not the concrete classes, we'll get an error. Without this, any code that now reads the ID will get the default value when working with an editor role. It might error out, or it might go undetected until one of your users can't access their information anymore. So let's go back to our state machine. Now that we have our roles, the episode class checks whether the move is legal and whether the role may make it. So in our example, 2 reasons can deny a transition. So if we have 2 if blocks, a combined check would leave the user guessing, and separating the messages tells them whether the workflow forbids the move or whether they need someone. So this example started as part of Damien Segway's phenomenal project, and you can see mine at phenomenal pull request number 3. My version stretches this further with a clamp sort direction using the time duration and the partial function applications, which is all covered in our What's New in PHP 8.6 video. As a brief recap, a state machine is a fixed set of states, the allowed transitions between them, one current state, and terminal states with no way out. A backed enum with next and canMoveTo replaces a loose string status and blocks illegal jumps like idea to published. Permissions are a second check. The move may be legal and the role must list both the from state and the to state and report each denial with its own message. PHP 8.6 readonly property defaults and override on constants let each role declare its permissions without a constructor and fail loud when a parent constant changes. The enum debug information shows the current state and its next move in var_dump and for your debugger. PHP 8.6 ships November 19th, 2026, so start using these features then. Thank you for making it to the end, you truly are a superfan. Make sure you subscribe, comment, share, and like this video as it does help boost it to other viewers. And definitely throw it in your work Slack. Let me know what other patterns and practices you would like me to cover. This is Scott Keck Warren for the PHP Architect channel signing off and reminding you to keep watching, keep coding, and keep reading.