ddtcorex

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 writable storage/ directory, the php artisan schedule:run task scheduler, and first-class queue workers (php artisan queue:work). Its docroot is public/.
  • Symfony centralizes everything behind bin/console, locks dependencies with symfony.lock, caches into var/cache/, and ships async work through the Messenger component (bin/console messenger:consume). Its web root is also public/, but its environment loading differs from Laravel's.
  • WordPress is the outlier: no artisan, no bin/console, just wp-cli and wp-config.php. Plugins live under wp-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 doctor to 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 up when 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

govardlaravelsymfony