Skip to content

Publication

phil/doctrine-behaviours
TypeKindRole
Contract\PublishableinterfaceThe state in the workflow's own accessors, isPublished(), the dates
PublishableEntitytraitThe columns publication_state, 32 characters, published_at and unpublished_at
PublicationWorkflowconstants, definition(), markingStore()The state machine, under the name phil_publication
PublicationListenerlistener: workflow.<name>.enteredStamps the dates, in UTC

The state is kept by symfony/workflow, an optional dependency, so a guard or a listener of the application can hold or follow a publication like any other transition.

TransitionFromTo
publishdraft, unpublishedpublished
unpublishpublishedunpublished
Enteringpublished_atunpublished_at
publishedset to now, unless the record already has oneforgotten
unpublishedkeptset to now

The date of the first publication stays when a record is withdrawn and published again. A record that was given a date beforehand, by an import or an editor choosing the date shown, keeps it. Flushing stays the application's, as with every workflow.

Outside Symfony:

$dispatcher->addListener('workflow.'.PublicationWorkflow::NAME.'.entered', new PublicationListener($clock));
$workflow = new StateMachine(PublicationWorkflow::definition(), PublicationWorkflow::markingStore(), $dispatcher, PublicationWorkflow::NAME);

$workflow->apply($article, PublicationWorkflow::PUBLISH);
$manager->flush();

Publishing at a set time

A date in the future is a job for a scheduler, not a column: the state changes when the time comes, through the workflow, with its guards. phil/scheduled-transitions-bundle does that for any object workflow.registry knows, and reaches this behaviour with the transition names alone, so neither package depends on the other. The application writes the SubjectResolver that finds its records:

$scheduler->schedule(ResourceReference::of('article', $article->id), 'publish', new DateTimeImmutable('2026-11-09 09:00'));

A workflow with more places, such as a review before publication, keeps these three names for the listener to stamp the dates; the bundle then leaves its declaration to the application.