19 Aug 2026 · 6 min read
Laravel, Symfony & WordPress on Govard
Three PHP frameworks, three sets of conventions, one orchestrator — how Govard detects and wires Laravel, Symfony, and WordPress without a bespoke stack per project.
Govard is a Go-native orchestrator for local PHP and web development. The first posts in this series covered how it brings an environment up and how it discovers a framework automatically. This one zooms in on the three most common members of the PHP family it supports today: Laravel, Symfony, and WordPress. They share a language but almost nothing else in their conventions — and that is the friction Govard is built to absorb. The extension contract is documented in the adding a framework guide, and the full command surface lives in the CLI reference.
The PHP-family drift problem (Laravel vs Symfony vs WordPress conventions)
On paper, Laravel, Symfony, and WordPress are all "PHP projects". In practice, each carries its own opinionated world, and a developer who maintains one of each spends more time relearning infrastructure than writing code.
- Laravel standardizes on
artisan, a.env-driven config, a writablestorage/directory, thephp artisan schedule:runtask scheduler, and first-class queue workers (php artisan queue:work). Its docroot ispublic/. - Symfony centralizes everything behind
bin/console, locks dependencies withsymfony.lock, caches intovar/cache/, and ships async work through the Messenger component (bin/console messenger:consume). Its web root is alsopublic/, but its environment loading differs from Laravel's. - WordPress is the outlier: no
artisan, nobin/console, justwp-cliandwp-config.php. Plugins live underwp-content/, scheduled work rides on WP-Cron (or Action Scheduler), and there is no built-in queue abstraction. The docroot is the project root itself.
Left to yourself, you end up maintaining three divergent Docker/Compose setups, each with its own nginx vhost, PHP-FPM pool, and bootstrap ritual. A new developer clones a Laravel repo and a WordPress repo and has to learn two different local stacks. Govard removes that drift at the source: it detects the framework from filesystem signatures and provisions the services that that framework expects — so the only thing you relearn is the application, not the runtime around it.
Tip: after cloning any of the three, run
govard doctorto confirm Govard detected the framework you expected before you boot the stack. A silent mismatch here is far cheaper to fix than a container that fails halfway up.
Per-framework services & wiring (php-fpm variants, scheduler, queue workers)
Once a framework is detected, Govard consults its FrameworkDefinition — a
self-contained description of the services, volumes, and defaults that project
needs. The engine stays framework-agnostic; it never hard-codes if framework == "laravel". It provisions the services the definition asks for from a shared set
of primitives, so the three frameworks feel consistent outside while staying
faithful to their internals.
PHP-FPM variants. Each framework gets the right FPM pool and PHP version for
its dependency set, with the docroot pointed at the correct directory — public/
for Laravel and Symfony, the project root for WordPress — without hand-editing an
nginx.conf.
Scheduler. Laravel's php artisan schedule:run and Symfony's scheduled tasks
both need a cron-like driver. Govard runs a scheduler service that periodically
invokes the framework's scheduler command, so time-based jobs fire locally
without a nohup process in a terminal. WordPress, which relies on WP-Cron, gets
the same treatment through a lightweight recurring trigger instead of depending on
visitor traffic to advance scheduled hooks.
Queue workers. Async work is where the three diverge most. Laravel's
php artisan queue:work, Symfony's bin/console messenger:consume, and
WordPress's Action Scheduler each need a long-lived process. Govard provisions a
queue-worker service per framework so jobs run in the background the moment the
environment is up.
# bring the whole stack up — FPM, scheduler, and queue workers included
govard env up
# confirm the running services and their container names
govard env ps
# follow every service's output, including the scheduler and queue worker
govard env logs -f
The practical payoff is uniformity: the list of services differs per framework,
but the way you operate them does not. Restart a stuck queue worker with
govard env restart queue regardless of whether the underlying command is
queue:work or messenger:consume — Govard already knows which one your
framework uses. The extension contract for declaring these services is the
adding a framework guide.
One CLI, every project (govard list, govard env up regardless of framework)
The unifying story is that the commands never change. Whether the directory holds a Laravel API, a Symfony monolith, or a WordPress site, the same verbs apply.
# see every project on this machine and the framework Govard detected for each
govard list
# boot the correct stack for whichever framework the directory contains
govard env up
# the rest of the lifecycle is identical across all three
govard env ps
govard env logs -f
govard env restart
govard env down
govard list is the map: it reports each project and the framework Govard
resolved it to, so you never wonder which conventions a given directory follows.
From there, govard env up does the framework-specific wiring described above
without any flag telling it which framework to expect — a Laravel developer and a
WordPress developer on the same team share the exact same muscle memory.
For editor integration, govard vscode setup --global scaffolds the debug
configuration once, and Xdebug attaches to whichever project's FPM pool is
running — Laravel, Symfony, or WordPress. The
CLI reference lists every
subcommand, including govard doctor for a post-clone sanity check and
govard config profile switch when a project needs a different configuration
profile.
Tip: switch profiles before
govard env upwhen a project overrides defaults (database version, PHP version, cache backend). Detected framework and active profile are independent layers, and fixing the profile after boot means a rebuild.
Adding a new framework when yours is missing (wizard -> FrameworkDefinition)
Laravel, Symfony, and WordPress already ship as first-class frameworks, but the real point of the model is that your framework can join too. If Govard does not yet recognize the stack you are on, you are not stuck hand-maintaining a Compose file.
Start with the custom framework wizard, which assembles a definition from the service primitives Govard already understands — web server (nginx/Apache), database, cache, search, queue, and an edge cache such as Varnish. Each choice maps to a service the engine already knows how to provision, so the result is a composed definition rather than a from-scratch build.
# walk through the wizard to assemble a definition for a bespoke PHP stack
govard framework add --wizard
When the composed framework proves reusable, promote it into a proper
FrameworkDefinition under internal/frameworks/<name>/ following the
adding a framework guide.
A definition declares its detection signatures, bootstrap, blueprint assets, and
service defaults; once registered, the existing pipeline boots it with the same
govard env up flow as Laravel or Symfony. Shared assets belong in
internal/blueprints/, while framework-specific logic stays in its own package.
That is the whole arc: detect the framework, wire its services, operate it with one CLI, and extend the model when something new appears. Laravel, Symfony, and WordPress are simply the definitions that ship today; the contract around them keeps the PHP family from drifting back into three separate local stacks.
Next in this series: Symfony specifics on Govard —
/blog/govard-symfony