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.
ls -la ~/languages/
- drwxr-xr-x
- ▶ PHP
- ▶ Ruby
- ▶ Scala
- ▶ C#
- ▶ JavaScript
- ▶ Objective-C
- ▶ Shell Scripting
ls -la ~/toolchain/
- drwxr-xr-x
- ▶ Typo3
- ▶ Akka
- ▶ Capistrano
- ▶ Git
- ▶ MAMP
- ▶ Adobe Illustrator
- ▶ NSTrackingArea (Cocoa)
uname -a
- 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
| Key | Value |
|---|---|
| editor | Terminal-first workflow |
| os | Mac OS X / Unix |
| vcs | Git, distributed version control |
| deploy | Capistrano, cron automation |
| graphics | SVG, Adobe Illustrator troubleshooting |
| networking | IP validation, SSH, VPN |
git log --oneline --reverse
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.
Solving NDK build issues on OS X
Troubleshooting native development kit compilation problems on Mac OS X.
Programmatically adding PHP generated TypoScript to the backend configuration
Integrating dynamically generated TypoScript into Typo3 backend setups using PHP.
ArgumentError: Could not parse PKey: no start line
Debugging an SSH key parsing error encountered during deployment.
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.
Cocoa: Using NSTrackingArea
A short tutorial on using Cocoa's NSTrackingArea to capture mouseEntered and mouseExited events.
cat ~/contact.txt