kaliop/ezworkflowenginebundle
A Symfony bundle that adds a configurable workflow engine to eZ Platform and eZ Publish. Define states, transitions, triggers, and actions in YAML to automate editorial and content publishing processes, with support for custom workflow services.
Reality check first: this is a Symfony bundle for eZ Publish 5 / eZ Platform (now Ibexa), not a Laravel package. There is no service provider, no config/workflow.php, and it depends on eZ's content repository API (eZ\Publish\API\Repository\...). You cannot composer require kaliop/ezworkflowenginebundle into a Laravel app and get anything useful. The two ways a Laravel developer actually "uses" it are:
composer require kaliop/ezworkflowenginebundle
Register the bundle in app/AppKernel.php (or config/bundles.php on newer Symfony):
new Kaliop\eZWorkflowEngineBundle\KaliopEzWorkflowEngineBundle(),
Import the bundle's routing for the admin UI if you want the manual transition screens, then define your first workflow in YAML:
kaliop_ez_workflow_engine:
workflows:
editorial_approval:
triggers:
- { type: content/publish/pre, max_execution: 1 }
actions:
- { type: set_state, params: { state_group: editorial, state: pending } }
- { type: send_mail, params: { to: [email protected], template: approval } }
Resources/config/ — the canonical list of available trigger types and action types. This is the real documentation.DependencyInjection/Configuration.php — the exact YAML schema, including defaults and required params.Action/ directory — every built-in action class is a small, readable example of the extension contract.As of 2.1.0 the CLI is the best onboarding tool:
bin/console kaliop:workflows:debug # list configured workflows and triggers
bin/console kaliop:workflows:debug --show-path # print the resolution path for a workflow
bin/console kaliop:workflows:workflow <name> # run a workflow manually
Use debug to confirm your YAML actually compiled into the container before you go hunting for logic bugs, and workflow to exercise a definition on a real content item without waiting for an editorial event.
Start with publish gating: intercept content/publish/pre, check a condition (e.g. "author is not an editor"), and either block the transition or set a content state to pending_review. This is the single most common reason teams install the bundle, and it exercises the trigger, condition, and action layers at once.
The whole bundle is a fan-out pipeline: content events fire triggers, triggers evaluate matching workflows, workflows run an ordered action list. Treat the YAML as a declarative pipeline definition and keep each workflow small and single-purpose. A 20-action workflow is a maintenance liability — split it into multiple workflows bound to distinct triggers.
Built-in actions (set_state, send_mail, wait, etc.) rarely cover real business logic. Write a small action class and wire it in YAML:
class NotifySlackAction implements ActionInterface
{
public function execute(array $params): void
{
// $params come straight from the YAML block
}
}
This is the pattern to copy into Laravel: an ActionInterface, a container tag or app()->bind() registration, and YAML/PHP config mapping trigger names to action lists.
The kaliop:workflows:workflow command is the integration point for anything outside the editorial UI — bulk backfills, migrations, smoke tests in CI. From a Symfony/Ibexa console command (or a cron wrapper), call it with the workflow name and target content. It is far more reliable than writing a one-off script that boots the repository and pokes at content states directly, because it reuses the same condition/action resolution path as production.
For Laravel developers porting the idea: an artisan workflow:run {name} command wrapping a Pipeline::send($model)->through($actions) is the direct equivalent, and it gives you the same "run the real pipeline, not a reimplementation" guarantee.
The bundle's admin UI lets editors manually advance a content item through states. In practice this is the piece users touch daily — the YAML workflows mostly enforce invariants around it. Don't design workflows that assume automation handles every transition; assume a human will click the button at the wrong moment.
| Bundle concept | Laravel equivalent |
|---|---|
content/publish/pre trigger |
Eloquent saving / creating model events |
| Trigger + condition match | Event listener with an early return guard |
| Action list | Queued job chain / Pipeline::send()->through() |
set_state action |
Status enum column + $model->update() |
| Manual transition UI | Filament action / Nova action |
kaliop:workflows:debug --show-path |
A workflow:show {model} {id} artisan command that prints the resolved transition path |
| YAML config | config/workflows.php or a PHP enum of transitions |
The cleanest port is symfony/workflow itself, or a small state-machine package — you get most of this bundle's value without the eZ coupling.
send_mail resolves them without custom params.WorkflowServiceInner::executeMigration() / executeMigrationInner() directly, budget time to migrate to the 2.1.0 signature — the old one still runs but is deprecated and will be dropped.License. GPL-2.0. If you're evaluating this for a proprietary Laravel product, that's a hard stop for direct inclusion. Reading the source for architectural inspiration is fine; vendoring action classes into a commercial app is not.
Maintenance. Still a small, thinly maintained project (7 stars, 0 dependents), but 2.1.0 is a recent release, so the "abandoned since 2022" assumption no longer holds — the bundle is alive enough to ship features and to change internal signatures. Pin the exact version, read the changelog before bumping, and still expect to fork for major Symfony/Ibexa upgrades. The event API it hooks predates modern Ibexa versions.
Deprecated extension API as of 2.1.0. WorkflowServiceInner::executeMigration() and executeMigrationInner() (spelled WorflowServiceInner in the release notes) changed signature. The old call style still works but is deprecated. This only bites developers extending or invoking the bundle programmatically, not YAML authors — but if you subclass or call the service, treat this as the start of a migration, not a warning to ignore.
--fail changes CLI semantics. On 2.1.0, kaliop:workflows:workflow accepts --fail, which makes the command exit non-zero when the workflow fails. Use it in CI and cron so a broken action doesn't silently pass; without it, a failing workflow can look like a successful run.
Not a Laravel package. No service provider, no facade, no publishable config. Any tutorial that implies drop-in Laravel usage is wrong. Plan for a real port, not an integration.
YAML cache. Changing workflow YAML without clearing the Symfony cache silently does nothing. This is still the number-one "why isn't my workflow firing" cause — though kaliop:workflows:debug now makes it a 10-second check instead of a guessing game. Always bin/console cache:clear after edits.
Content states must pre-exist. set_state actions don't create state groups or states — those live in the eZ admin. A typo in a state identifier fails quietly or throws deep in the repository layer. Verify state groups exist before writing the workflow.
Trigger loops. An action that writes content can re-fire content/* triggers. Use max_execution guards on any trigger that could be re-entered, and test with a workflow that logs rather than acts until you're confident.
Action ordering is literal. Actions run in YAML order, not by priority. Put state changes before notifications, and never rely on asynchronous email delivery for correctness.
Debugging. Start with kaliop:workflows:debug --show-path to confirm which workflow matched and how it resolved; if it fires but doesn't complete, the failure is inside an action. Wrap custom actions in try/catch that logs $params verbatim, and run the workflow through the CLI with --fail in a staging environment to reproduce outside the editorial UI.
Extension points ranked by safety: custom actions > custom conditions > custom triggers. Custom triggers couple you to repository event internals and break on Ibexa upgrades; custom actions against a stable execute(array $params) contract survive much longer.
How can I help you explore Laravel packages today?