PHP 8.6 is now close enough to release that Drupal teams should be testing against it, but not close enough to treat it as a routine production upgrade. The PHP project has published release candidates, including RC4 on 21 October 2026, and its published schedule identifies another release candidate for 5 November. The PHP documentation also makes clear that pre-release builds are for testing rather than production use. ([php.net](https://www.php.net/pre-release-builds.php?utm_source=openai))
For Drupal developers, this is a useful window for finding compatibility problems in custom modules, themes, Composer dependencies and operational tooling before PHP 8.6 becomes a standard platform choice. Drupal’s current compatibility table lists PHP 8.6 as unsupported for Drupal 11.1 to 11.3, while Drupal 11.4 compatibility is still tracked against the relevant core issue rather than being presented as a supported production combination. ([drupal.org](https://www.drupal.org/docs/getting-started/system-requirements/php-requirements?utm_source=openai))
What PHP 8.6 testing means for a Drupal project
Testing PHP 8.6 does not mean changing the PHP version on a live Drupal site. It means creating a disposable environment that is as close as possible to the production application, running the same dependency installation and test suite, then reviewing every new warning, failure and behavioural difference.
A useful compatibility test should cover four layers:
- PHP itself: language changes, new deprecations, changed engine behaviour and extension differences.
- Drupal core: bootstrap, cache rebuilds, configuration import, routing, rendering and cron.
- Contributed and custom code: modules, themes, Drush commands, event subscribers, plugins and integrations.
- Delivery tooling: Composer, PHPUnit, PHPStan, PHP_CodeSniffer, Drush, deployment scripts and hosting images.
The most valuable result is not simply “the test suite passed”. A useful result tells you which warnings are emitted, where they originate, whether they are caused by your code or a dependency, and whether the problem blocks a future Drupal or PHP upgrade.
Start with a known Drupal baseline
Before testing a new PHP runtime, record the current application state. This avoids confusing an existing dependency or configuration problem with a PHP 8.6 regression.
Run the baseline on the PHP version currently used by development and production:
php --version
composer --version
vendor/bin/drush status
vendor/bin/drush core:status
composer validate --strict
composer check-platform-reqs
vendor/bin/phpunit --version
The exact Drush executable path depends on the project. Some sites expose Drush through vendor/bin/drush; others use an alias or a container command. The important point is to run these checks using the same entry point used by the deployment pipeline.
Capture the dependency graph as an artefact:
composer show --direct
composer show --locked
composer outdated --direct
composer why-not php 8.6
composer why-not php 8.6 is particularly useful, but it should be interpreted carefully. A package may reject PHP 8.6 because its declared constraint has not yet been widened, even when its code works correctly. Conversely, a package may permit PHP 8.6 while still producing runtime warnings or relying on behaviour that has changed.
Keep the project’s existing composer.lock file unchanged for the first compatibility run. Updating every package at the same time makes failures harder to attribute and turns a runtime test into an uncontrolled dependency upgrade.
Understand the current support position
Drupal 11 requires PHP 8.3 or later, but that does not mean every later PHP minor release is automatically supported by every Drupal minor release. Drupal’s system requirements page currently lists PHP 8.3, 8.4 and 8.5 for Drupal 11.3 and 11.4. PHP 8.6 is listed as not supported for Drupal 11.3, while compatibility for Drupal 11.4 remains associated with an active issue. ([drupal.org](https://www.drupal.org/docs/getting-started/system-requirements/php-requirements?utm_source=openai))
That distinction matters for governance:
- Do not advertise PHP 8.6 as a supported production platform for a Drupal release until Drupal’s official requirements state that it is supported.
- Do use PHP 8.6 release candidates in isolated development and CI jobs to discover problems early.
- Do not let a green application test suite override platform support policy, hosting support policy or contributed-module compatibility.
- Record the test runtime as experimental in project documentation and change records.
Drupal’s compatibility policy also depends on upstream dependencies. Even when Drupal core itself runs successfully, a module or Symfony component may have a separate PHP constraint. The result is why compatibility testing must include Composer resolution and not only HTTP smoke tests.
Build an isolated PHP 8.6 test environment
Use a disposable environment that cannot accidentally receive production traffic or write to production services. A useful test environment should have:
- a copy of the codebase and a sanitised database;
- the same Composer lock file as production;
- the same PHP extensions where practical;
- the same database family and major version;
- separate Redis, Solr, object storage and external API credentials;
- outbound network restrictions where integrations are not required;
- debug logging enabled and error reporting captured.
Do not point a PHP 8.6 test container at the production database. A compatibility run may execute update hooks, queue workers, cron tasks or custom maintenance commands. Even read-only assumptions can be invalidated by a module, an integration or an overlooked command-line operation.
When the PHP 8.6 image or package is available in your chosen development tool, verify the runtime rather than trusting the environment label:
php -v
php -m | sort
php -i | grep -E 'memory_limit|opcache.enable|error_reporting'
vendor/bin/drush status --fields=drupal-version,php,db-driver,db-version
If your local tool does not yet provide PHP 8.6, use an official PHP pre-release build or a controlled build supplied by your infrastructure team. Avoid downloading an unverified third-party binary simply to make a local test pass. PHP’s pre-release page provides the published source and Windows builds for the release candidates. ([php.net](https://www.php.net/pre-release-builds.php?utm_source=openai))
Run Composer checks before bootstrapping Drupal
Composer is the first useful gate because it identifies packages whose declared platform constraints do not include PHP 8.6. Run the installation with the lock file and fail on platform requirements:
rm -rf vendor
composer install \
--no-interaction \
--prefer-dist \
--no-progress
composer check-platform-reqs
composer validate --strict
For a CI compatibility job, avoid silently ignoring platform requirements. Options such as --ignore-platform-reqs can be useful for diagnosing a deliberately isolated dependency-resolution problem, but they should not be used to claim that an application is compatible.
If the install fails, classify the result rather than immediately editing constraints:
- Is the package genuinely incompatible with PHP 8.6?
- Is the package’s PHP constraint simply stale?
- Does a newer package release contain the compatibility change?
- Does Drupal core or another dependency impose the constraint?
- Is the package abandoned and therefore a migration candidate?
Use Composer’s explanation commands to find the source of a restriction:
composer why-not php 8.6
composer prohibits php 8.6
composer why vendor/package
composer show vendor/package --all
Do not solve every failed constraint by adding a broad root-level override. A forced platform version can hide a real incompatibility and may leave the lock file describing a dependency graph that cannot be safely installed elsewhere.
Turn PHP deprecations into actionable test failures
PHP 8.6 introduces deprecations affecting several areas that are relevant to long-lived Drupal applications. The migration guide documents changes involving identifiers such as namespace, readonly, let and is, return statements in finally blocks, legacy type aliases, selected object-to-array conversions and parts of the mbstring, Reflection, Session and SPL APIs. ([php.net](https://www.php.net/manual/en/migration86.php?utm_source=openai))
Some of these patterns may be present in custom code, old contributed modules or integration libraries. Others may appear only in rarely used paths such as error handling, session backends, import commands or test helpers.
Make deprecations visible in the test environment. For example, a controlled test bootstrap can configure reporting explicitly:
<?php
error_reporting(E_ALL);
ini_set('display_errors', '1');
ini_set('log_errors', '1');
Do not blindly copy this configuration into production. Displaying PHP errors to visitors can disclose paths, service names, SQL fragments and other sensitive information. In production, send errors to a protected log or observability platform instead.
Pay particular attention to code like this:
<?php
function loadValue(): array
{
try {
return loadFromStorage();
} finally {
return [];
}
}
A return in finally can suppress an exception or replace a return value. PHP 8.6 deprecates this behaviour because it makes failures disappear. The safer form is to perform cleanup in finally without returning from it:
<?php
function loadValue(): array
{
try {
return loadFromStorage();
} finally {
releaseTemporaryResources();
}
}
In Drupal code, this sort of issue can be hidden inside a service, queue worker or migration process. A page request may appear healthy while a failed import quietly returns an empty result.
Audit the patterns most likely to affect Drupal code
Legacy identifiers
Search custom modules and themes for identifiers that are now reserved or deprecated. Search results require review rather than mechanical replacement because a word may appear in a string, translation, configuration value or external API payload.
grep -RInE '\b(readonly|namespace|let|is)\b' \
web/modules/custom \
web/themes/custom \
--include='*.php'
Also search for function and method names that PHP has identified as deprecated, including aliases such as is_long(), is_integer(), is_double() and doubleval(). Replace them with the canonical equivalents where the code is under your control.
Object-to-array conversions
PHP 8.6 deprecates several cases where objects are silently treated as arrays. Drupal applications commonly pass objects through normalised data, form handlers, API clients and serialisation layers, so this is worth checking in custom integration code.
Prefer an explicit conversion with a documented intent:
<?php
$values = get_object_vars($object);
$query = http_build_query($values);
For domain objects, an explicit method such as toArray() is often better than exposing every visible property. It makes the API contract clear and prevents internal state from being sent to an external service.
Custom session handlers
PHP 8.6 changes the expectations around custom session handlers. The migration documentation notes that handlers need the session ID creation and validation methods required for strict session mode, and that incomplete implementations now generate deprecations. ([php.net](https://www.php.net/manual/en/migration86.deprecated.php?utm_source=openai))
Drupal core does not mean every project is unaffected. Custom authentication integrations, legacy SSO modules and bespoke session storage can implement PHP session interfaces directly. Search for implementations in custom and contributed code:
grep -RInE 'implements[[:space:]]+.*SessionHandlerInterface|session_set_save_handler' \
web/modules/custom \
web/modules/contrib \
--include='*.php'
Review any result against the PHP 8.6 migration documentation and the module’s current issue queue before changing it. Session code is security-sensitive; a change that removes a warning but weakens session validation is not an acceptable compatibility fix.
Exercise Drupal-specific paths
A successful unit-test run is not enough. Drupal behaviour is spread across container compilation, plugin discovery, configuration, render caching, routing, access checking, queues and command-line operations.
At minimum, run the following on the isolated site:
vendor/bin/drush status
vendor/bin/drush cr
vendor/bin/drush config:status
vendor/bin/drush config:import --preview
vendor/bin/drush cron
vendor/bin/drush queue:run --time-limit=30
vendor/bin/drush watchdog:show --count=50
Use the commands appropriate to the project and review their output. A queue worker may be destructive, so run it only against test data and test integrations. If the site uses custom Drush commands, migration plugins or scheduled jobs, include them explicitly rather than assuming that cron covers every code path.
For HTTP testing, exercise more than the homepage:
- anonymous and authenticated page requests;
- editorial forms and media uploads;
- multilingual routes;
- JSON:API or custom REST endpoints;
- search and filtered listings;
- cache misses and cache hits;
- file downloads and image derivatives;
- login, logout and password-reset flows;
- webhooks and payment or identity integrations in sandbox mode.
Record both HTTP failures and PHP logs. A response may remain a 200 while PHP emits deprecations, a service catches an exception, or a fallback path returns incomplete data.
Add PHP 8.6 as a non-blocking CI job first
The first CI job should be informational. Its purpose is to expose compatibility problems without blocking every pull request while Drupal and the project’s dependencies are still establishing official support.
A generic pipeline shape looks like this:
php-compatibility:
stage: compatibility
allow_failure: true
script:
- php --version
- composer validate --strict
- composer install --no-interaction --prefer-dist --no-progress
- composer check-platform-reqs
- vendor/bin/drush status
- vendor/bin/phpunit --testdox
- vendor/bin/drush cr
- vendor/bin/drush config:status
The exact syntax varies between GitLab CI, GitHub Actions, Jenkins and other systems. Treat the example as a pipeline outline, not a drop-in configuration. The runner must provide a verified PHP 8.6 release-candidate runtime and the required extensions.
Once the job is stable, split failures into categories:
- Infrastructure: missing extension, unsupported runner image or broken service container.
- Dependency resolution: Composer constraint or package metadata problem.
- Deprecation: code still runs but requires remediation.
- Functional regression: a test or request produces the wrong result.
- Security regression: authentication, authorisation, escaping or session behaviour changes.
Do not suppress deprecation output simply to make the job green. If the current test harness cannot distinguish a known vendor warning from a new application warning, improve the reporting first. A temporary baseline may be reasonable, but it should be version-controlled, reviewed and accompanied by an issue for removing it.
Use static analysis to find problems before runtime
Runtime tests find executed problems. Static analysis helps identify code paths that are not covered by the test suite.
Run the project’s existing tools under the PHP 8.6 environment:
vendor/bin/phpcs --standard=Drupal,DrupalPractice web/modules/custom web/themes/custom
vendor/bin/phpstan analyse web/modules/custom
vendor/bin/phpunit --coverage-text
Not every project uses PHPStan, and Drupal coding standards may be configured through a project-specific ruleset. Use the versions already declared by the project before upgrading analysis tools as part of the runtime test. Otherwise, you risk mixing a PHP compatibility issue with a static-analysis rules change.
Where static analysis reports a PHP 8.6 deprecation, confirm the finding against the PHP migration documentation and then decide whether to fix, suppress with justification or report it upstream. Suppression should be narrow and temporary.
Do not forget the deployment layer
Changing PHP affects more than the web process. Check every execution context:
- PHP-FPM or Apache module;
- CLI PHP used by Composer and Drush;
- cron containers and queue workers;
- worker processes managed by Supervisor or systemd;
- scheduled CI jobs;
- image-processing and document-generation services;
- monitoring checks and health endpoints.
A common failure mode is running the web application on one PHP version while deployment commands use another. Confirm the binary in each context:
command -v php
php -r 'echo PHP_VERSION, PHP_EOL;'
vendor/bin/drush php:eval 'echo PHP_VERSION, PHP_EOL;'
Also inspect the enabled extensions. A PHP 8.6 runtime with a different set of extensions can produce misleading results, particularly for XML, GD, Intl, mbstring, cURL, OpenSSL, PDO drivers and image handling.
Security and governance considerations
Pre-release runtimes should never be treated as an excuse to weaken security controls. Keep dependency verification enabled, use trusted package sources, protect test credentials and sanitise databases before copying them into disposable environments.
PHP’s own release information explicitly warns that release candidates are not for production. Drupal’s requirements documentation should remain the authority for whether a particular Drupal release is supported on PHP 8.6. ([php.net](https://www.php.net/pre-release-builds.php?utm_source=openai))
For regulated or public-sector projects, record:
- the PHP release-candidate build and checksum;
- the Drupal core version and Composer lock-file hash;
- the extensions enabled during testing;
- the test data classification;
- the warnings and failures found;
- the upstream issues or patches referenced;
- the approval required before production adoption.
That evidence is more useful than a simple statement that “PHP 8.6 was tested”. It allows another developer to reproduce the result after the final PHP release or after Drupal publishes an updated compatibility position.
A sensible adoption sequence
For most Drupal teams, the safest sequence is:
- Run the existing test suite on the current supported PHP version and establish a clean baseline.
- Add PHP 8.6 to CI as an informational compatibility job.
- Keep the production Composer lock file unchanged during the first run.
- Fix custom-code deprecations and report genuine contributed-module issues upstream.
- Repeat the test with the final PHP 8.6 release when it becomes available.
- Wait for Drupal and key contributed dependencies to publish their compatibility position.
- Test a representative staging deployment, including cron, queues, cache rebuilds and integrations.
- Roll out gradually with a rollback path and enhanced log monitoring.
Do not combine the PHP change with a major Drupal upgrade, a database migration and a large dependency refresh unless there is a compelling reason. Smaller changes make incident response and rollback much easier.
Conclusion
PHP 8.6 is close enough to justify serious Drupal compatibility testing, but early testing should be treated as preparation rather than permission to upgrade production. The most valuable work now is finding deprecations and dependency constraints while there is still time to fix them upstream.
For a Drupal 11 project, the practical target is a reproducible compatibility pipeline: the same Composer lock file, a verified PHP 8.6 test runtime, explicit extension checks, deprecation visibility, Drupal-specific smoke tests and a recorded decision for every failure. That approach gives the team useful evidence without confusing an experimental runtime with an officially supported platform.