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
Tracing a PHP Memory Limit Crash in an Old TYPO3 Extension
It usually starts with a white screen on a content edit page, followed by a flood of confused messages in Slack from the editorial team. The TYPO3 instance in question is the one that has been quietly running a regional council site in Brisbane since the early 2010s, and the extension doing the damage was last touched by a developer who has long since moved on to other agencies. OutOfMemory errors in PHP look scary in the logs, but they are usually symptoms of a fixable pattern rather than a hardware emergency.
The goal of this walkthrough is to show how I tracked down the offending function in a legacy extension, raised the resource budget just enough to keep the page alive, and refactored the small piece of code that was holding onto objects it never released. The same approach applies whether you are working on a community TYPO3 site for a Sydney arts festival or maintaining an internal tool that nobody has the budget to rewrite.
Reading the warning before it crashes the request
The first signal is almost always a Fatal error: Allowed memory size of 268435456 bytes exhausted in the TYPO3 log. Sometimes the page renders an empty body and the front end shows a generic 500 page, other times the BE login itself refuses to load. On Australian hosting servers that default to 256M, this hits hard because many shared plans on providers such as VentraIP and Digital Pacific cap PHP at exactly that figure. When the error appears right after a content element save, the suspect is the data handler pulling too many rows at once.
A useful habit before touching anything is to copy the exact stack trace into a scratch buffer. TYPO3 is generous with the line numbers, and the trace often points at a single function in a single file. I keep a sticky note on my second monitor during these sessions, partly because Sydney summers make the home office a sweatbox by 2pm and I want to keep my place when the aircon finally catches up.
Why legacy TYPO3 extensions eat RAM
TYPO3 4.x and early 6.x extensions were written in an era when 128M felt generous and nobody worried about iterating over large result sets. The pattern I keep finding is a foreach over a database query that loads every record into memory, builds an array of domain objects, and never calls unset when the loop ends. PHP 7 and 8 do not free that memory back to the OS until the request finishes, so a list view that pulls a few thousand news items will quietly consume hundreds of megabytes.
Another classic is the recursive category walker that builds a tree of objects and stores the whole tree in a static property. The static survives between method calls inside the same request, which is fine for a small catalogue but disastrous when the catalogue has grown over a decade. On a client project for a Melbourne tourism board, the catalogue had grown from four hundred listings to over twelve thousand, and the same recursion that worked in 2014 was now blowing past the limit on every list view.
Narrowing down the culprit with a quick profile
Rather than scattering echo statements through the code, I reach for memory_get_usage with a few strategic prints around the suspected hot spot. A minimal pattern is to log the usage before and after each loop, then watch the deltas climb. On a staging copy that mirrors production traffic, I can reproduce the crash in under a second and the deltas make it obvious which iteration is the troublemaker.
When I want something heavier, I sometimes spin up the request under Xdebug with the memory profile turned on, then dump the output to a file I can grep through on the train back from a SydPHP meetup. For a deeper look at how interactive debugging surfaces memory leaks, the cocoa using nstrackingarea walkthrough on this site shows a similar idea applied to a Cocoa view, and the principles carry over to PHP with only a small change in tooling. The trick is to isolate one request, freeze it at the peak, and read the call graph backwards until you find the variable that never gets cleared.
Choosing where to lift the memory_limit
Once the leak is mapped, the next decision is where to raise the limit. There are four common places: php.ini, .htaccess, the TYPO3 Install Tool, and LocalConfiguration.php via $GLOBALS['TYPO3_CONF_VARS']['SYS']['maxMemory']. Each has tradeoffs, and the right choice depends on how much control you have over the box.
For a managed VPS in Brisbane or a cPanel account on a shared host, the Install Tool is usually the only place you can edit without contacting support. On a dedicated server I prefer php.ini because it applies to every PHP process, including cron and the scheduler. Some Australian hosts charge in AUD per extra 256M of RAM, so I treat every bump as a budget item and keep a spreadsheet of when each client last had a memory raise, lest the CFO ask why hosting costs jumped.
Cutting the actual leak in the extension
Lifting the limit is a band-aid, not a fix. The real work is in the extension itself. In the Melbourne tourism case, the recursive category tree was rebuilt on every list view even when the tree was identical to the one in the previous request. Adding a simple cache key based on the rootline and storing the resulting array in the TYPO3 caching framework reduced the memory pressure by an order of magnitude. The page that previously needed 512M now runs comfortably on 128M, which matters when the client migrates back to a cheaper shared plan during the off-season.
For the foreach pattern, the fix is usually to switch from fetchAll to a generator or a paginated query that releases rows as it iterates. TYPO3's query API supports a setLimit call that does the job, and combining it with a streaming iterator means the peak memory stays flat regardless of how many records exist in the database. A common Australian client habit is to upload an entire year of events in one CSV, so the pagination matters more than the original developer ever imagined.
Confirming the patch on a staging clone
Before pushing any change, I run a representative request through the patched extension on a staging clone and watch the memory graph. The peak should be well under the new limit, ideally under half of it, so there is headroom for the occasional rogue import. I also re-run the original failing scenario to make sure the symptom is gone rather than just delayed.
A second pass with a profiler confirms the deltas now stay flat instead of climbing. If the numbers look healthy, I commit the patch, push it through the usual peer review, and deploy during the AEST quiet window so any rollback can happen before the eastern states wake up. After deployment, I keep an eye on the production logs for a day or two, partly out of habit and partly because nothing teaches you about a TYPO3 instance like reading its log over a flat white at a Carlton cafe.
| Location of the limit | Scope of effect | Typical use in Australia | Tradeoff to weigh |
|---|---|---|---|
| php.ini | All PHP processes on the server | Dedicated VPS or managed box from a local provider | Requires server access, restart needed after edit |
| .htaccess | Current directory and below | Shared hosting on cPanel accounts | Some hosts ignore it, FPM setups may bypass it |
| TYPO3 Install Tool | BE login and tool-driven operations | Sites where only the TYPO3 admin can edit settings | Does not affect CLI, scheduler, or frontend by itself |
| LocalConfiguration.php via SYS.maxMemory | Whole TYPO3 request | When you want a per-install value without touching the OS | Same scope as the Install Tool, useful for scripted deployments |
Habits that keep memory errors from coming back
- Profile one heavy request before and after every refactor, and store the peak number alongside the commit message.
- Replace fetchAll loops with paginated queries or generators whenever a list could grow beyond a few hundred rows.
- Cache the result of any recursive walker with a stable key, and let the TYPO3 framework manage invalidation for you.
- Treat every bump to memory_limit as a temporary reprieve and open a ticket to remove the underlying cause.
- Mirror production traffic on staging at least once per quarter, since Australian clients often grow their content faster than expected during summer campaigns.
If this kind of debugging note is useful, you can read more from the author on the journal's archive page, where I keep older write-ups on TYPO3, PHP runtime quirks, and the occasional detour into other stacks. Subscribe to the feed or drop a comment if you have hit a similar memory wall in a legacy extension and want to compare notes.
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