By Greg Nowak. Updated 21 September 2026.
A Drush command that ends in a Symfony or PHP fatal error can look like a deep Drupal failure. Quite often, it is a toolchain problem: the shell starts one Drush installation, that process loads dependencies from somewhere else, and a different PHP runtime completes the mismatch.
That distinction matters when Drush runs database updates, cache rebuilds, cron jobs or deployment hooks. The aim is not merely to make one command pass. It is to restore a predictable environment that developers, CI and production all run the same way.
What an EventDispatcher error actually tells you
An error involving Symfony\Component\EventDispatcher\EventDispatcher::dispatch() points to an incompatible caller and component. Symfony changed the method’s argument order during the Symfony 4.3 transition, with the newer signature becoming the standard in Symfony 5. That history explains why old Drush releases, contributed commands or custom code can fail against newer Symfony packages.
It does not prove that Symfony itself should be downgraded. Start with the file paths in the stack trace. A mixture of global Composer directories and the project’s vendor directory strongly suggests that the wrong Drush executable was selected. If every path is inside the project, investigate its resolved dependencies and custom command code instead.
Diagnose the runtime before changing it
Work from the directory containing composer.json. These checks are non-destructive:
pwd
command -v php
command -v composer
command -v drush
php -v
php --ini
composer --version
composer config --global home
composer show drush/drush
vendor/bin/drush version
vendor/bin/drush status
composer why symfony/event-dispatcher --tree
composer check-platform-reqscommand -v reveals what the shell will execute. Calling vendor/bin/drush bypasses a global Drush installation and tests the copy resolved with the project. Composer’s why command shows which packages introduced EventDispatcher. check-platform-reqs checks the real PHP version and extensions, ignoring any simulated config.platform value.
| Result | Likely cause | Best next move |
|---|---|---|
vendor/bin/drush works, plain drush fails |
Global binary or shell-path mismatch | Change scripts and runbooks to use the project binary |
| Both Drush commands fail | Project dependency, extension or PHP mismatch | Inspect Composer’s dependency tree and platform checks |
| Only production fails | Environment or release drift | Compare PHP, extensions, lock file and install flags |
| All failing paths are project-local | Unsupported package or custom command | Identify the incompatible caller before changing Symfony |
If the project Drush works, fix the invocation
Replace unqualified drush calls in deployment scripts, scheduled jobs, CI configuration and operating notes with vendor/bin/drush. A convenient developer alias is fine, but production automation should not depend on whichever executable appears first in a user’s PATH.
Also confirm that cron and deployment users run the intended PHP binary. An interactive shell may use PHP 8.4 while a scheduled task still resolves PHP 8.2, or may load a different php.ini. That is why recording php -v and php --ini in diagnostic output is useful.
If the project Drush fails, let Composer expose the constraint
Do not install an individual Symfony package globally or delete composer.lock as a first response. Those actions replace evidence with a new, unreviewed dependency set.
Use Composer to test the intended Drush major and show its blockers:
composer prohibits drush/drush ^13 --tree
composer prohibits drush/drush ^14 --tree
composer update drush/drush --with-all-dependencies --dry-runThe dry run previews a solution without writing it. A blocker may be Drupal core, the active PHP version, a contributed package or a project constraint. Resolve that specific relationship instead of forcing installation with --ignore-platform-reqs.
If Drush is missing, add it on a branch or in a controlled build environment. Use a normal requirement when production jobs need Drush; use --dev only when production installations deliberately omit development packages and never call it:
composer require drush/drush:^13
# Or, for a compatible Drupal 11.3+ or Drupal 12 project:
composer require drush/drush:^14
vendor/bin/drush status
composer validate
git diff -- composer.json composer.lockChoose a currently supported combination
The current Drush compatibility table recommends Drush 13 for Drupal 10.2 and later and for Drupal 11. Drush 14 is recommended for Drupal 11.3 and later and for Drupal 12. Both Drush majors require PHP 8.3 or newer. Drush 12 can still be compatible with Drupal 10, but it is no longer the supported line.
Check Drupal’s PHP matrix as well as Drush’s. In September 2026, Drupal 10.4–10.6 support PHP 8.1 through 8.4; Drupal 11.1–11.4 support PHP 8.3 and 8.4, while PHP 8.5 support begins with Drupal 11.3. Drupal 12 requires PHP 8.5. A PHP release being newer does not automatically make it valid for an older Drupal minor.
Deploy the fix as a controlled change
Commit both composer.json and composer.lock, build from the lock file, and test the commands your release actually uses: status, cache rebuild, database updates, configuration import and any project-specific command files. Run composer check-platform-reqs in the target environment before allowing deployment to continue.
For a legacy site that cannot yet move to a supported combination, pin the last compatible project-local Drush release, isolate its runtime and document the limitation. Treat that as containment with an upgrade owner and date—not as a permanent platform decision.
Make the repair last
- Keep Drush and its exact resolved version under project control.
- Use the project binary consistently in local, CI, staging and production workflows.
- Compare CLI PHP versions and loaded configuration across environments.
- Test Drupal, Drush and PHP compatibility as one upgrade decision.
- Remove undocumented global dependencies from operational procedures.
The immediate code change may be small. The valuable outcome is a delivery process that produces the same command-line environment every time. If an inherited Drupal platform has accumulated fragile scripts or unclear dependency ownership, Greg can help turn it into a documented, supportable setup.
Related on GrN.dk
- Drupal 9: A Practical Upgrade Path for Legacy Sites
- Drupal Automatic Updates After the Legacy API Shutdown: A Practical Fix Plan
- Cloudflare Changed DoH JSON. What Else Is Parsing DNS as Text?
Need help with this kind of work?
Talk to Greg about your Drupal delivery setup Get in touch with Greg.