whoami

Lucas Jenß

cat /etc/motd

The Coding Journal ツ — Notes taken on an epic coding journey. Technical solutions, debugging notes, and practical guides from the trenches of software development.

Close-up of white dominoes with black dots standing on green felt surface, shallow depth of field, focused mood

ls -la ~/languages/

total 8
drwxr-xr-x
▶ PHP
▶ Ruby
▶ Scala
▶ C#
▶ JavaScript
▶ Objective-C
▶ Shell Scripting

ls -la ~/toolchain/

total 7
drwxr-xr-x
▶ Typo3
▶ Akka
▶ Capistrano
▶ Git
▶ MAMP
▶ Adobe Illustrator
▶ NSTrackingArea (Cocoa)

uname -a

platforms
drwxr-xr-x
▶ Mac OS X
▶ Unix

Writing a PHP polyfill for a missing TYPO3 function

An older TYPO3 installation often fails in a deceptively small way: an extension calls a helper that arrived in a later core release while the site still runs the PHP and TYPO3 versions it was built for. Editing vendor code or copying a modern core file into an old tree usually creates another outage. A PHP polyfill gives the missing symbol a controlled userland implementation while the upgrade remains scheduled.

The approach below starts by identifying the exact API, then preserves its observable behaviour without pretending that old and new TYPO3 are identical. It also separates true function polyfills from method wrappers because PHP cannot safely add a method to an existing class. The result is a compatibility layer that can be tested, deployed and removed on a defined schedule.

Trace the failure to the exact TYPO3 API

Begin with the stack trace, not the newest release notes. Record whether the failure concerns a namespaced function, a static method or an instance method, and reproduce it on a clean copy of the deployed environment. In a Composer installation, composer.lock gives the precise core and extension versions; older sites may expose the same information through TYPO3_version and installed extension metadata.

Next, find the first release that introduced the symbol and read that implementation, its callers and its tests. function_exists() applies to functions, while method_exists() is the relevant check for methods. This distinction prevents a common mistake: defining a similarly named function does not satisfy a call to GeneralUtility::getUrl() or another static utility method.

One case involved a Melbourne agency whose TYPO3 7 estate expected a URL helper delivered by a later extension version. A Brisbane retailer had the same extension on a different core release. Recording the failing URL, environment and call stack in my Coding Journal made the shared cause visible without changing either production site.

Define the compatibility contract

Define the contract before coding: accepted input types, return type, exception behaviour, URL or path normalisation, encoding, and whether configuration or filesystem state can change the result. “Works on my machine” is not a contract. Include empty values, missing files, relative paths and the site’s configured base URL as explicit cases.

Compare the upstream implementation with the old API rather than copying it wholesale. A newer TYPO3 method may depend on classes, constants or services unavailable in the legacy release. Port the smallest behaviour needed by the caller, preserve useful error messages, and decide whether an unsupported input should return a fallback value or fail loudly.

Write the contract as a test fixture that can run against both branches. Keep the modern and legacy paths behind one interface, and record any intentional difference. That record becomes valuable during an Australian agency’s next maintenance cycle, when budgets are quoted in Australian dollars and the legacy dependency still has a measurable support cost.

Build the guarded polyfill

A genuine namespaced function can be declared conditionally with function_exists() using its fully qualified name. A missing method is different: userland code cannot add that method to an existing TYPO3 class, and declaring a class with the same fully qualified core name can cause a fatal collision on upgrade. For method-based APIs, the safer polyfill is a project-owned function that delegates to whichever TYPO3 version is installed.

For example, the following compatibility function provides one URL API while supporting the modern GeneralUtility::getUrl() method and the older t3lib_div::getUrl() method:

<?php

namespace Vendor\Project\Compatibility;

use RuntimeException;
use TYPO3\CMS\Core\Utility\GeneralUtility;

if (!function_exists(__NAMESPACE__ . '\\getUrl')) {
    function getUrl($path, $relativePath = false)
    {
        if (
            class_exists(GeneralUtility::class)
            && method_exists(GeneralUtility::class, 'getUrl')
        ) {
            return GeneralUtility::getUrl($path, $relativePath);
        }

        if (
            class_exists('t3lib_div')
            && method_exists('t3lib_div', 'getUrl')
        ) {
            return \t3lib_div::getUrl($path, $relativePath);
        }

        throw new RuntimeException(
            'No compatible TYPO3 URL utility is available.'
        );
    }
}

The guard prevents a redeclaration when Composer loads the file more than once or another package supplies the helper. The two capability checks are deliberately separate: class_exists() confirms that the modern utility can be loaded, and method_exists() confirms the required method is present. If neither branch is usable, throwing an exception is safer than returning a plausible but incorrect URL.

On an estate supporting PHP 5.6, remove scalar type hints and modern syntax until the PHP floor is raised. Keep the same small maintenance principle used on The Loud Library Lady: isolate compatibility code so it is obvious, reviewable and removable rather than spreading conditionals through templates.

Load it safely in legacy TYPO3

Functions are not PSR-4 discoverable like classes, so placement and bootstrap order matter. For a Composer-managed TYPO3 site, put the polyfill in a small compatibility package and list it under autoload.files; regenerate the autoloader and confirm the file loads after Composer’s loader is available. A dedicated package also gives the code its own tests and version.

On a non-Composer TYPO3 installation, load the file once from an extension bootstrap such as ext_localconf.php, before extension code calls the helper. Use require_once, keep the file free of output and side effects, and avoid hiding fatal errors. Do not patch typo3/sysext or vendor/, because the next deployment will overwrite those changes.

A Perth-based colleague found a second defect while checking this path: the helper ran, but a cache still held paths generated by the new branch. Record cache names with the release, and invalidate only the affected entries. It is a useful reminder that a polyfill can be correct while stale compiled output remains.

Verify old and new behaviour

Automate the contract with PHPUnit. Run the same assertions against the oldest supported TYPO3 and PHP combination and the current production combination, ideally in isolated containers. Include the expected URL, relative and absolute forms, encoded characters, invalid input and the exception path. Do not let the test rely on live site configuration when a small fixture can control it.

Add a regression test for the original failure before deploying the fix. Then exercise the full request, not only the helper: TYPO3 bootstrap, extension registration, cache generation and response output can reveal ordering mistakes that a unit test misses. Capture the PHP error log and generated HTML as build artefacts when the defect is difficult to reproduce.

Legacy URLs need the same attention. An existing e-wallet casino deposit page still depended on the old path handling and had to keep its published address during the TYPO3 change, so redirect and canonical-link tests became part of the release.

Deploy first to staging, then to a small production slice during a Sydney client’s agreed AEST or AEDT window. Watch 24-hour timestamps in logs so a daylight-saving transition cannot hide an early failure. Keep the previous package and cached artefacts ready for rollback, and have the person on call confirm both the redirect and the generated page.

Compare the migration choices

A compatibility layer is a decision, not a permanent virtue. Compare the options against the expected life of the old release, the number of affected sites and the cost of an eventual core upgrade. A wrapper is usually the best transitional choice because it contains version checks and leaves the TYPO3 API untouched.

Approach Older TYPO3 Newer TYPO3 Main trade-off
Upgrade TYPO3 first Unsupported until deployment Uses the native API Highest immediate effort, but removes the shim
Project-owned wrapper Delegates to the legacy API Delegates to the modern API Safe, testable and easy to remove
Core-named class shim Can satisfy old call sites Risks colliding with the real class Unsuitable without a carefully tested loading strategy
Modify vendor or core files Requires a local patch Patch may disappear on deployment Fragile and difficult to maintain

The core-named class shim is the risky option: it may satisfy an old call site but collide with the real class after an upgrade. Patching vendor code is even more fragile because the change disappears on deployment. For an Australian consultancy, the financial comparison should include GST and the hours needed to retest every inherited site, not just the initial development cost.

Ship the fix and document the decision

Publish the smallest durable version of the fix: attach the failing test, the polyfill or wrapper, the supported version matrix and a removal date to the project issue. That record turns a temporary compatibility measure into controlled technical debt rather than mystery code in a bootstrap file.

Run the old-version and current-version suites before merging, deploy in the agreed release window, monitor the first cached requests and keep the rollback ready. Schedule the TYPO3 upgrade that removes the shim, then publish the operational notes with the project so every affected Australian estate can reuse the tested fix.


cat ~/interests.json

KeyValue
editorTerminal-first workflow
osMac OS X / Unix
vcsGit, distributed version control
deployCapistrano, cron automation
graphicsSVG, Adobe Illustrator troubleshooting
networkingIP validation, SSH, VPN

git log --oneline --reverse

2013-10-30

Solving SVG import issues in Adobe Illustrator CS6 and CC

When importing an SVG into Illustrator, the operation fails with an unknown error [CANT]. A workaround for this Adobe-side bug.

2013

Solving NDK build issues on OS X

Troubleshooting native development kit compilation problems on Mac OS X.

2013

Programmatically adding PHP generated TypoScript to the backend configuration

Integrating dynamically generated TypoScript into Typo3 backend setups using PHP.

2013

ArgumentError: Could not parse PKey: no start line

Debugging an SSH key parsing error encountered during deployment.

2011-08-04

Validating IP-Addresses in PHP

Using PHP filter functions with flags like FILTER_FLAG_IPV4 and FILTER_FLAG_IPV6, and understanding how filter_var handles reserved IP addresses.

2011-07-09

Cocoa: Using NSTrackingArea

A short tutorial on using Cocoa's NSTrackingArea to capture mouseEntered and mouseExited events.


cat ~/contact.txt

github: github.com/x3ro
stackoverflow: x3ro
coderwall: coderwall.com/x3ro
twitter: @x3rames