Workflows & state machines
A blog post is drafted, reviewed, then published or rejected. Instead of juggling boolean flags, you declare the states and the allowed transitions between them. Invalid state jumps (draft straight to published) become impossible by design.
In your code, ask the state machine what's allowed: can() and apply() replace all the hand-written status checks. Every transition also dispatches events, so you can hook side effects (notify the author, clear a cache) without touching this code.
And because the definition is data, Symfony can draw it: one command exports a diagram of your real process, perfect for discussing business rules with non-developers.
framework: workflows: blog_publishing: type: state_machine marking_store: type: method property: status supports: [App\Entity\BlogPost] initial_marking: draft places: [draft, in_review, published, rejected] transitions: submit: from: draft to: in_review publish: from: in_review to: published reject: from: in_review to: rejected<?phpnamespace App\Service;use App\Entity\BlogPost;use Symfony\Component\Workflow\WorkflowInterface;class BlogPublisher{ public function __construct( // autowired by name: the "blog_publishing" state machine private WorkflowInterface $blogPublishingStateMachine, ) { } public function publish(BlogPost $post): void { $workflow = $this->blogPublishingStateMachine; if ($workflow->can($post, 'publish')) { // updates $post->status after checking the transition // is valid from the current state $workflow->apply($post, 'publish'); } }}$ php bin/console workflow:dump blog_publishing | dot -Tsvg > flow.svg# generates a diagram of your states and transitions,# always in sync with your actual configuration