phpmentors/pageflower-bundle
A Symfony bundle from PHP Mentors that integrates Pageflower into Symfony applications. It supplies bundle configuration and service wiring for Pageflower's page-oriented features, helping developers add consistent page handling and navigation to PHP projects.
Pageflower is a Spring-Web-Flow-style pageflow engine: you declare flows (pages + transitions + guards) in config, and the engine controls which page a request is allowed to reach and where it redirects. phpmentors/pageflower-bundle is a thin Symfony integration layer over the phpmentors/pageflower core, so expect to read two READMEs: this bundle's, then the core library's.
Minimal path:
composer require phpmentors/pageflower-bundle --dry-run
composer why-not phpmentors/pageflower-bundle
config/bundles.php; older skeletons use AppKernel):
PhpMentors\PageflowerBundle\PageflowerBundle::class => ['all' => true],
# config/packages/phpmentors_pageflower.yaml
phpmentors_pageflower:
flows:
checkout:
definition: '%kernel.project_dir%/config/pageflows/checkout.yaml'
# config/pageflows/checkout.yaml
checkout:
pages:
cart:
transitions:
next: { to: shipping, guard: cart_is_not_empty }
shipping:
transitions:
back: { to: cart }
next: { to: confirm }
Where to look first, in order: README.md of the bundle (it is short and mostly install + one example), then the core phpmentors/pageflower repo's tests/ — for a 5-star package, the tests are the actual documentation for guard/transition semantics.
Pages are controller actions, not entities. Each page in the flow maps to a route/controller. The engine's job is to evaluate the incoming transition, run guards, and either dispatch the page or redirect back to the last valid page. Keep controllers thin; the flow owns sequencing, controllers own rendering.
Guards as services. A guard is a named service whose method returns whether a transition may fire. Pattern:
final class CartIsNotEmpty implements GuardInterface
{
public function __construct(private Cart $cart) {}
public function evaluate(): bool
{
return !$this->cart->isEmpty();
}
}
Register with the tag the bundle documents (verify the exact tag name with bin/console debug:container --tag=<tag> — this is where the 2020-era docs are thinnest).
Flow state lives in session scope. The engine tracks the current page per flow instance. Treat the session as the source of truth for navigation position only; never put domain data there. On logout or "start over," explicitly reset the flow rather than relying on garbage collection.
Redirect-after-POST is the engine's default posture. Do not try to render a page directly on a POST; let the transition fire and redirect. If you fight this you will get double-submit bugs that look like engine bugs.
Integration tip — version your flows. Because in-flight sessions reference page names, renaming a page while users are mid-flow strands them. Either namespace flow names (checkout_v2) and migrate, or add a guard on entry that resets unknown state.
Testing pattern. Two layers: unit-test guards in isolation (they are plain services), and write one functional test per flow that walks happy path + one guard-rejection path. Assert on redirect targets, not rendered HTML, because the engine's observable behavior is the redirect.
Porting note for Laravel developers. There is no Laravel analog packaged here, and PageflowerBundle is Symfony DI + HttpKernel coupled. If you are evaluating it for a Laravel codebase, you are evaluating the concept (declarative flows + guards) and will be reimplementing it on top of middleware, not installing it. The core phpmentors/pageflower library is framework-agnostic in principle; the bundle is the part you would leave behind.
src/ or a VCS repository entry if install constraints block you.require that resolves on Symfony 5.4 may still explode on Symfony 6/7 attribute and return-type changes. Run the test suite of a two-page flow before committing to it anywhere real.bin/console debug:container | grep -i pageflow and debug:router to find what the bundle actually registered. Do not copy class names from blog posts; they drift between the core library and the bundle.How can I help you explore Laravel packages today?