DEV Community

Cover image for Running Legacy PHP on PHP 8
Ivijan-Stefan Stipić
Ivijan-Stefan Stipić

Posted on

Running Legacy PHP on PHP 8

Upgrading a modern PHP application is usually a controlled process. You update dependencies, run tests, fix deprecations, and deploy the new runtime.

Legacy PHP applications are rarely that simple.

They may contain abandoned packages, custom business logic, old WordPress plugins, legacy framework code, and functionality written long before Composer became standard. When the server moves to PHP 8, the application can stop immediately because it still calls functions such as create_function(), each(), ereg(), or split().

That is why I built PHP Legacy Compat, an open-source PHP 8 compatibility layer for applications originally written for PHP 5.x and PHP 7.x.

The project does not make legacy code modern. It provides enough stability to keep an application operational while the underlying code is upgraded properly.

The migration gap

The correct long-term solution for removed PHP functionality is to rewrite the affected code. In production, however, a full rewrite may not be possible before an infrastructure deadline.

An internal CRM may contain undocumented business rules. A WordPress website may depend on an abandoned theme. A Magento 1 integration may still process orders. Keeping an unsupported PHP runtime online is risky, but replacing the entire application immediately may be equally unrealistic.

PHP Legacy Compat is intended for the space between those two problems.

It provides polyfills, documented approximations, and safer helpers for common migration failures, including:

  • create_function()
  • each()
  • ereg(), eregi() and related POSIX regex functions
  • split() and spliti()
  • legacy session registration functions
  • utf8_encode() and utf8_decode()
  • deprecated strftime() behavior
  • unsafe count() calls
  • the old reversed argument order for implode()

For example, old code may still contain:

$callback = create_function('$value', 'return trim($value);');
Enter fullscreen mode Exit fullscreen mode

Or:

while (list($key, $value) = each($data)) {
    process_item($key, $value);
}
Enter fullscreen mode Exit fullscreen mode

On PHP 8, both examples fail because those functions no longer exist. The compatibility layer restores practical behavior so the application can continue running while these calls are replaced with closures, foreach, and other modern alternatives.

Compatibility must be honest

The easiest way to hide migration errors would be to define every missing function and return a harmless-looking value. That approach is dangerous because it can silently break business logic or corrupt data.

PHP Legacy Compat follows a simple rule:

Compatibility must be honest.

The project distinguishes between true polyfills, partial polyfills, safe fallbacks, and compatibility approximations.

Some behavior, such as each(), can be reproduced closely. Other behavior depended on engine internals that no longer exist. The old POSIX regex functions, for example, must be approximated using PCRE, so their limitations are documented rather than hidden.

The project does not attempt to recreate removed extension stacks such as ext/mysql or ext/mcrypt. It does not provide fake cryptography, pretend that magic quotes still exist, or return fake success values just to prevent an error.

If behavior cannot be restored safely or meaningfully in userland, the library does not pretend otherwise.

Universal and Strict modes

Different migrations require different levels of compatibility, so the project provides two mutually exclusive files.

Universal mode

Universal mode offers broader compatibility for difficult migrations where keeping the application operational is the immediate priority.

require_once __DIR__ . '/php-legacy-compat-universal.php';
Enter fullscreen mode Exit fullscreen mode

It includes carefully documented approximations where they remain useful. This mode is usually the practical starting point for an old application with many runtime failures.

Strict mode

Strict mode provides a narrower and more conservative compatibility surface.

require_once __DIR__ . '/php-legacy-compat-strict.php';
Enter fullscreen mode Exit fullscreen mode

It excludes looser behavior where the risk of semantic differences is higher. It is useful when an application is already partly modernized or when a team wants fewer approximations.

Only one mode should be loaded during a request. The two files represent different migration policies and are intentionally not loaded automatically by Composer.

Installation

The package is available through Composer:

composer require infinitumform/php-legacy-compat
Enter fullscreen mode Exit fullscreen mode

Then include one compatibility file as early as possible in the application bootstrap:

require_once __DIR__ . '/vendor/infinitumform/php-legacy-compat/php-legacy-compat-universal.php';
Enter fullscreen mode Exit fullscreen mode

Depending on the application, this can be loaded from a front controller, bootstrap file, framework entry point, or WordPress must-use plugin.

For WordPress, a small loader can run before an old theme or normal plugin:

<?php
/**
 * Plugin Name: Legacy PHP 8 Compatibility Loader
 */

require_once WPMU_PLUGIN_DIR . '/php-legacy-compat-universal.php';
Enter fullscreen mode Exit fullscreen mode

The project is not limited to WordPress. It can also help with legacy Laravel, Symfony, CodeIgniter, Magento, custom frameworks, and internal PHP systems.

Migration helpers and diagnostics

Some PHP 8 problems cannot be solved by redefining a removed function. count() still exists, but it no longer accepts every loose value that legacy code may pass.

For those cases, the project provides explicit helpers:

if (legacy_safe_count($items)) {
    process_items($items);
}
Enter fullscreen mode Exit fullscreen mode

The historical reversed argument order for implode() can be handled during migration with:

echo legacy_safe_implode($items, ',');
Enter fullscreen mode Exit fullscreen mode

A diagnostic report is also available:

$report = legacy_compat_get_report();
print_r($report);
Enter fullscreen mode Exit fullscreen mode

This reports the active mode, available polyfills, helpers, and notable limitations. An optional soft error handler is included for controlled staging, but it is disabled by default and should not replace proper error handling or real fixes.

A bridge, not a permanent fix

My recommended migration path is straightforward:

  1. Load Universal mode to get a difficult legacy application running on PHP 8.
  2. Test critical workflows and identify remaining incompatibilities.
  3. Replace restored legacy calls with modern PHP code.
  4. Move to Strict mode to expose dependencies on looser behavior.
  5. Remove the compatibility layer when the application no longer needs it.

That final removal is a successful outcome. The project is designed to make itself unnecessary over time.

No userland library can reproduce every detail of an old PHP runtime. It cannot repair unrelated security problems, replace framework upgrades, or guarantee that an application behaves correctly without testing. The current project scope explicitly targets PHP 8.0 through PHP 8.3.

What it can do is prevent removed functions from blocking the entire migration, clearly document unavoidable differences, and provide developers with enough time to modernize a codebase in controlled steps.

PHP Legacy Compat is open source and released under the MIT License:

github.com/InfinitumForm/php-legacy-compat

Legacy software does not always need to be preserved forever, but it often needs a safe path forward. This project is intended to provide that path.

Top comments (0)