
Branch support differs from patch freshness
The official support table reviewed on 25 October 2026 lists the end of PHP 8.2 security support as 31 December 2026. An approaching support deadline is a reason to plan migration. It is separate from patch freshness: an installation can belong to a supported branch while missing current fixes. Review both questions independently rather than turning a branch number into a complete statement about the security of an application.
Start by recording the actual runtime and loaded extensions. The command-line interpreter is not necessarily the same version used by the web service. A host may run different interpreters for CLI tasks, web workers and individual sites. The inventory example reports only its own execution environment. Do not publish a full runtime-information page on the public Internet to obtain this inventory because detailed settings can expose information that should remain private.
Inventory the runtime and use a dated policy
The example policy records upstream branch support dates and includes a short review deadline. Results distinguish active support, security-only support and conditions requiring further review. Unknown branches and prerelease version strings are not silently approved. For distribution packages with backported fixes, also inspect the vendor's support and security information. An upstream calendar cannot by itself describe a separate distribution-maintenance agreement or prove which fixes a vendor has applied to a particular package.
Run php php_inventory.php > php-inventory.json in staging, then python check_support.py php-inventory.json. The checker evaluates branch status and installs nothing. Run python -m unittest test_support.py to exercise support boundaries, policy expiry and unrecognized versions. When policy expires, recheck the official table and update the reviewed data. Changing the system clock or extending a deadline without reviewing the source is not an acceptable way to obtain a passing result.
Before migration, test plugins, themes, application libraries and database connectivity against the intended runtime in isolation. Deprecated behavior, type differences and logged errors may appear only on less frequently used paths. Include administrator login, uploads, Persian text processing and scheduled tasks in the acceptance checklist. Read the migration guides for intermediate branches when crossing several releases so that changes introduced between the starting version and the final target are not missed.
Complete files to save
Save all four files together. The article explains execution and verification; no code is replaced with ellipses.
1. Inventory runtime and extensions
File: php_inventory.php. Save every line under this filename.
<?php
// CLI inventory; compare separately with the actual FPM/Apache service.
$extensions = get_loaded_extensions();
sort($extensions);
echo json_encode([
'version' => PHP_VERSION,
'sapi' => PHP_SAPI,
'integer_bytes' => PHP_INT_SIZE,
'extensions' => $extensions,
'timezone' => date_default_timezone_get(),
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), PHP_EOL;This file belongs to the complete example above; execution order, prerequisites and limits are explained in the article.
2. A support calendar with a review deadline
File: support_policy.json. Save every line under this filename.
{
"reviewed": "2026-10-25",
"reviewBy": "2026-11-08",
"source": "https://www.php.net/supported-versions.php",
"branches": {
"8.2": {"activeUntil": "2024-12-31", "securityUntil": "2026-12-31"},
"8.3": {"activeUntil": "2025-12-31", "securityUntil": "2027-12-31"},
"8.4": {"activeUntil": "2026-12-31", "securityUntil": "2028-12-31"},
"8.5": {"activeUntil": "2027-12-31", "securityUntil": "2029-12-31"}
}
}This file belongs to the complete example above; execution order, prerequisites and limits are explained in the article.
3. Assess the branch without changing it
File: check_support.py. Save every line under this filename.
import argparse
from datetime import date
import json
from pathlib import Path
import re
def assess(version, policy, today):
if not date.fromisoformat(policy['reviewed']) <= today <= date.fromisoformat(policy['reviewBy']):
return 'policy-needs-review'
match = re.fullmatch(r'(\d+\.\d+)\.\d+(?:-[A-Za-z0-9.+~]+)?', version)
if not match:
return 'unknown-version'
branch = policy['branches'].get(match[1])
if branch is None:
return 'unlisted-branch-review-official-table'
if today > date.fromisoformat(branch['securityUntil']):
return 'end-of-life'
if today > date.fromisoformat(branch['activeUntil']):
return 'security-only-plan-migration'
return 'active-branch-check-latest-patch'
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('inventory', type=Path)
args = parser.parse_args()
policy = json.loads(Path(__file__).with_name('support_policy.json').read_text())
inventory = json.loads(args.inventory.read_text(encoding='utf-8'))
result = assess(inventory['version'], policy, date.today())
print(json.dumps({'branchAssessment': result, 'sapi': inventory.get('sapi')}))
raise SystemExit(0 if result.startswith('active-branch') else 1)This file belongs to the complete example above; execution order, prerequisites and limits are explained in the article.
4. Test calendar boundaries and unknown versions
File: test_support.py. Save every line under this filename.
from datetime import date
import json
from pathlib import Path
import unittest
from check_support import assess
class SupportTests(unittest.TestCase):
def setUp(self):
self.policy = json.loads(Path(__file__).with_name('support_policy.json').read_text())
self.today = date(2026, 10, 25)
def test_branch_states(self):
self.assertEqual(assess('8.2.34', self.policy, self.today), 'security-only-plan-migration')
self.assertEqual(assess('8.5.11', self.policy, self.today), 'active-branch-check-latest-patch')
self.assertIn('unlisted', assess('7.4.33', self.policy, self.today))
def test_unknown_and_stale(self):
self.assertEqual(assess('8.6.0RC3', self.policy, self.today), 'unknown-version')
self.assertEqual(assess('8.5.11', self.policy, date(2026, 11, 9)), 'policy-needs-review')
def test_exact_security_end_boundary(self):
policy = {**self.policy, 'reviewBy': '2027-01-02'}
self.assertEqual(assess('8.2.34', policy, date(2026, 12, 31)), 'security-only-plan-migration')
self.assertEqual(assess('8.2.34', policy, date(2027, 1, 1)), 'end-of-life')
if __name__ == '__main__':
unittest.main()This file belongs to the complete example above; execution order, prerequisites and limits are explained in the article.
Plan migration with extension and web-service tests
For release, record the web runtime, worker configuration and required extensions alongside test evidence. Prepare a return path and a coordinated data backup; schema or data-format changes can make reverting just the interpreter insufficient. Recheck the actual web-service version and application errors after deployment. A useful upgrade plan combines the support calendar, current patch information and application compatibility, rather than assuming that any larger version number is automatically a safe operational choice.
Implementation checklist
- Inspect CLI and web runtime separately.
- Check support status and patch freshness independently.
- Review expired policy against official data.
- Test extensions, data compatibility and recovery.
References
Editorial guidance: Liyan Knowledge.




