Each WorkflowDefinition can define zero or more WorkflowMatchRule rows (parameter_key + parameter_value).
- 0 rules — default workflow for the subject class when nothing more specific matches.
- 1 rule — e.g.
document_type=invoice. - 2+ rules — all must match (AND), e.g.
tenant=acme+department=finance.
Resolution order:
- Filter enabled definitions by subject class.
- Keep definitions whose rules all match the runtime context.
- Pick the one with the most rules (highest specificity).
- On tie, pick the highest priority.
- Throw
WorkflowAmbiguousMatchExceptionif still tied.
use Nowo\WorkflowBundle\Model\WorkflowContext;
use Nowo\WorkflowBundle\Service\WorkflowResolver;
use Nowo\WorkflowBundle\Service\WorkflowApplicator;
// Explicit context
$definition = $resolver->resolve(new WorkflowContext(
subjectClass: DemoExpense::class,
parameters: ['tenant' => 'acme', 'department' => 'finance'],
));
// Subject implementing WorkflowContextAwareInterface
$applicator->applyForSubject($expense, 'approve');
$applicator->getEnabledTransitionsForSubject($expense);
$slug = $resolver->resolveSlugForSubject($expense);- Add
metadataJSON onWorkflowDefinition/WorkflowMatchRulefor custom data. - Subclass entities in your app (Doctrine inheritance) if you need extra columns.
- Implement
WorkflowContextAwareInterfaceon domain subjects to expose lookup parameters.
By default every request to nowo_workflow_* routes is allowed. Implement WorkflowUiAccessCheckerInterface and alias it in the container to enforce your own access policy (roles, IP allowlist, etc.). See SECURITY.md.
WorkflowRegistryInterface is the extension point for resolving Symfony WorkflowInterface instances. The default implementation is DatabaseWorkflowRegistry; WorkflowApplicator depends on the interface.
See demo playgrounds: 0-param orders, 1-param documents, 2-param expenses, 3-param purchase orders, plus /playground/resolver.
Prefer configuring ui.layout_template + ui.css_framework (see CONFIGURATION.md) so host apps integrate without copying every page. Full overrides remain available under templates/bundles/NowoWorkflowBundle/ with the same relative path as in src/Resources/views/:
templates/bundles/NowoWorkflowBundle/layout.html.twig
templates/bundles/NowoWorkflowBundle/base.html.twig
templates/bundles/NowoWorkflowBundle/dashboard/index.html.twig
templates/bundles/NowoWorkflowBundle/workflow_definition/index.html.twig
UI class helpers come from UiKitBundle ({% import '@NowoUiKitBundle/macros/ui.html.twig' as ui %}). Prefer remapping --nowo-ui-* tokens or overriding kit Twig under templates/bundles/NowoUiKitBundle/ rather than forking page templates.
Application overrides always win. Render using @NowoWorkflowBundle/... logical names.
Stable blocks for host stacking: nowo_ui_content, nowo_ui_styles, nowo_ui_scripts (always call {{ parent() }} when overriding styles/scripts).
Translation domain: NowoWorkflowBundle. Override in the application:
translations/NowoWorkflowBundle.en.yaml
translations/NowoWorkflowBundle.es.yaml
Clear cache after adding overrides: php bin/console cache:clear.