coosos/version-workflow-bundle
Installation
Add the bundle to your composer.json:
composer require coosos/version-workflow-bundle
Enable it in config/bundles.php:
return [
// ...
Coosos\VersionWorkflowBundle\CoososVersionWorkflowBundle::class => ['all' => true],
];
Basic Configuration
Define a workflow in config/packages/coosos_version_workflow.yaml:
coosos_version_workflow:
workflows:
my_workflow:
type: 'state_machine'
marking_store:
type: 'method'
method: 'getStatus'
supports:
- 'App\Entity\MyEntity'
initial_marking: 'draft'
places: ['draft', 'published', 'archived']
transitions:
publish:
from: 'draft'
to: 'published'
archive:
from: 'published'
to: 'archived'
First Use Case
Apply the workflow to an entity by adding the VersionableInterface trait:
use Coosos\VersionWorkflowBundle\Model\VersionableInterface;
use Coosos\VersionWorkflowBundle\Model\VersionableTrait;
class MyEntity implements VersionableInterface
{
use VersionableTrait;
// ...
}
Trigger a transition in a controller:
use Coosos\VersionWorkflowBundle\Workflow\WorkflowService;
public function publish(MyEntity $entity, WorkflowService $workflowService)
{
$workflowService->apply($entity, 'publish');
}
Entity Versioning Workflow
Use the bundle to track state transitions (e.g., draft → published → archived) while preserving SEO by merging final states into the original table.
// Apply transition
$workflowService->apply($entity, 'publish');
// Check current state
$state = $workflowService->getMarking($entity);
Doctrine Integration
Extend the bundle’s VersionableTrait to handle versioned entities:
class MyEntity extends BaseEntity implements VersionableInterface
{
use VersionableTrait;
#[ORM\Column]
private ?string $status = 'draft';
public function getStatus(): string
{
return $this->status;
}
}
Serialization
Leverage JMS Serializer to handle versioned entities in APIs:
# config/packages/jms_serializer.yaml
jms_serializer:
metadata:
directories:
App:
namespace_prefix: "App\\Serializer"
path: "%kernel.project_dir%/config/serializer"
Create a custom serializer context for versioned entities:
// src/Serializer/VersionedEntityHandler.php
use Coosos\VersionWorkflowBundle\Model\VersionableInterface;
class VersionedEntityHandler extends AbstractHandler
{
public function serialize($data, SerializationContext $context)
{
if ($data instanceof VersionableInterface) {
$context->addGroups(['versioned']);
}
}
}
Event-Driven Transitions Use Symfony events to trigger workflows:
// src/EventListener/WorkflowTransitionListener.php
class WorkflowTransitionListener
{
public function onPrePersist(MyEntity $entity, WorkflowService $workflowService)
{
if ($entity->isVersioned()) {
$workflowService->apply($entity, 'publish');
}
}
}
State Machine vs. Workflow
The bundle defaults to state_machine but may require method_marking_store for dynamic states. Ensure your marking_store config matches your entity’s state retrieval logic:
marking_store:
type: 'method'
method: 'getCustomStatusMethod' # Must exist in your entity
SEO Merging Quirks
The "merge into original table" feature assumes a merge() method exists in your entity. Override it to handle custom logic:
public function merge(VersionedEntity $versionedEntity)
{
$this->status = $versionedEntity->getStatus();
$this->content = $versionedEntity->getContent();
}
Doctrine Proxy Issues
If using proxies, ensure lazy-loaded properties (e.g., status) are accessible during workflow checks. Add @ORM\HasLifecycleCallbacks and initialize properties in preLoad():
#[ORM\HasLifecycleCallbacks]
class MyEntity
{
#[ORM\PreLoad]
public function initializeStatus()
{
if (null === $this->status) {
$this->status = 'draft';
}
}
}
Transition Validation Validate transitions before applying them to avoid silent failures:
if (!$workflowService->can($entity, 'publish')) {
throw new \RuntimeException('Cannot publish entity: invalid state');
}
Workflow Dump
Use the WorkflowService to debug states:
$workflow = $workflowService->getWorkflow('my_workflow');
dump($workflow->getEnabledTransitions($entity));
Symfony Profiler
Enable the workflow panel in the profiler to inspect active workflows and transitions.
Logging Log workflow events for auditing:
$logger->info('Transition applied', [
'entity' => $entity->getId(),
'transition' => 'publish',
'from' => $workflowService->getMarking($entity),
]);
Custom Marking Stores
Extend MethodMarkingStore to support non-Doctrine entities or custom logic:
class CustomMarkingStore extends MethodMarkingStore
{
protected function getMarkingValue($entity)
{
return $entity->getCustomState(); // Your logic
}
}
Post-Merge Hooks Add callbacks after merging versioned entities into the original:
// src/EventSubscriber/PostMergeSubscriber.php
class PostMergeSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
'coosos.version_workflow.post_merge' => 'onPostMerge',
];
}
public function onPostMerge(PostMergeEvent $event)
{
$event->getOriginalEntity()->updateSeoMetadata();
}
}
Async Transitions Use Symfony Messenger to decouple workflow transitions from requests:
$message = new ApplyWorkflowTransitionMessage($entity->getId(), 'publish');
$bus->dispatch($message);
How can I help you explore Laravel packages today?