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

Building a custom TYPO3 scheduler task for automated database cleanup

TYPO3 installations running for years grow a thick carpet of stale data. sys_log fills with thousands of automated entries, cache_hash balloons after extension updates, and fe_sessions linger well past their use-by date. An interactive cleanup works for a quick tidy-up, but production sites, especially those serving Australian e-commerce brands across several states, need something hands-off. A registered scheduler task earns its keep in that exact spot.

This walkthrough shows how to wire up a reusable task that drops into any TYPO3 v11 or v12 instance. We will look at the task class, the additional fields the backend exposes, and a couple of safety nets I picked up maintaining sites for clients in Melbourne and Perth. A useful reference on design framework choices helped me shape the additional fields.

Approach Setup effort Runs without admin Best for
Manual backend button None No One-off cleanup
Cron + standalone PHP script Medium Yes Bulk production jobs
TYPO3 Scheduler task Medium Yes Integrated logging and BE control

Why the scheduler beats ad-hoc cleanup

The Scheduler ships with TYPO3 core, so there is nothing extra to install beyond enabling it through the Extension Manager. Unlike a plain cron entry, every run shows up in the backend with a timestamp, duration, and error message. For an agency hosting client sites from a Sydney data centre, that history is gold when something goes sideways at 3 am AEST.

Another reason I lean on the Scheduler is human-readable configuration. Each registered task exposes its own form fields, so editors see a tidy panel labelled "Retention days" rather than a cryptic command-line flag. The task also benefits from TYPO3's logging framework, so warnings and notices flow into the standard log table and any PSR-3 handler your stack uses. A standalone cron script would need its own logging plumbing.

There is a real workflow benefit. Because the Scheduler runs as the CLI user, you can throttle jobs during business hours and let heavy cleanup fire overnight when visitors in Adelaide and Brisbane have logged off. The UI exposes frequency in plain language, so handing the site over to a junior dev does not require explaining crontab syntax.

Designing the task class

The class extends \TYPO3\CMS\Scheduler\Task\AbstractTask and implements the single execute() method that returns a boolean. A boolean return tells the Scheduler whether to mark the run as successful or to flag it as failed. Returning true on partial success is a common mistake; I prefer to throw a \RuntimeException whenever cleanup cannot finish, so the failure shows up loudly in the BE module.

Inside execute(), inject the database connection through dependency injection rather than reaching for the old $GLOBALS['TYPO3_DB'] static call. In TYPO3 v12, type-hint Doctrine\DBAL\Connection in the constructor and the framework will resolve it automatically. The task should fetch the retention threshold from the backend, fall back to a sensible default if nothing is configured, then build deletion queries against the tables you want to clean. Common targets are sys_log, cache_hash, session tables, and any custom log table a third-party extension writes to.

A subtle point: never call TRUNCATE on a TYPO3 table that other workers might be reading. Stick to batched DELETE statements with a LIMIT clause so the database does not lock the table for half a second while a frontend visitor in Hobart is loading a product page.

Registering the task with additional fields

Registering a Scheduler task happens in two places. First, the class is registered through AdditionalFieldProviderInterface, which tells TYPO3 which form fields to render when an editor creates a new task. Second, the task is exposed to the Scheduler through ext_localconf.php, where a single registration call wires the FQCN into the registry.

The additional fields provider returns an array of TCA-style field definitions. A typical setup exposes a numeric input for retention days, a checkbox for "dry run mode", and a multi-select for which tables to include. The provider also implements validateAdditionalFields() so you can sanity-check values before they hit the database. If you prefer configuration in LocalConfiguration.php, the same provider can pull defaults from there.

When the editor saves a new task in the BE, TYPO3 stores the field values alongside the task class FQCN. Each subsequent run re-reads those values through getter methods that the abstract base class makes available through $this->getOption(). I always set explicit defaults in the constructor so a brand-new task behaves sensibly even if someone forgets to fill in the form.

Testing, logging, and runtime safety

A cleanup task is the wrong place to discover that your WHERE clause is too greedy. I always run a dry-run flag during development and let the task log the SQL it would execute without committing anything. Once the dry run looks healthy, I switch to live mode and watch a single execution through the Scheduler's "Run task" button before relying on the cron-style frequency.

Memory is the silent killer for any task that loops over millions of rows. A medium-sized TYPO3 instance for a Sydney retailer can accumulate half a million sys_log rows per year, and loading them all into a single array will blow the PHP memory_limit before the loop finishes. For that reason I keep deletion loops batched and explicitly call gc_collect_cycles() between iterations. If you have ever hit a white page after enabling a new cleanup task, the post on debugging-a-php-memory-limit-error-in-a-legacy-typo3-extension walks through the kind of trace that points back at this loop.

Logging through TYPO3's LogManager keeps the output consistent with the rest of the system. Each batch writes an info entry with the number of rows affected, and any SQL exception writes a warning with the offending query. The Scheduler history view surfaces those messages directly, which is much easier than tailing a log file on a remote server.

Pitfalls to plan for

Three things tend to bite people the first time they ship a Scheduler task, and they are worth naming up front so you can guard against them.

  • Lock wait timeouts when the cleanup tries to delete from a table an active frontend user is reading. Batch your deletes and add a short SLEEP() between iterations if your database engine complains.
  • Index fragmentation after repeated bulk deletes. Schedule an OPTIMIZE TABLE for MySQL or VACUUM (ANALYZE) for Postgres on a separate, less frequent task.
  • Forgetting the Scheduler cron itself. The Scheduler only runs tasks when the scheduler CLI command fires regularly. Most Australian hosts running cPanel on a Brisbane or Perth node already include a sample cron line in the install tool, but double-check it is present.

Putting it to work across your sites

Putting a Scheduler task together does not require a huge investment, but the pattern pays back many times over once you have a fleet of TYPO3 sites that all need the same housekeeping. Start by mapping the tables you actually want to clean, write a dry-run mode first, and only enable live deletion after watching one full execution.

Habits that pay off

A few practical habits make the work smoother:

  • Keep the task class in its own extension so you can version it and reuse it across projects.
  • Document the retention defaults in the extension's README so clients in different states know what to expect.
  • Add a short description and a sensible default frequency when you register the task, so the BE module lists something meaningful rather than a blank row.

One last trap is timezone confusion. TYPO3 stores timestamps in UTC by default, so when you compare them against a retention threshold you need both sides in the same zone. I compute the cutoff in UTC inside the task and let the editor see the value in their preferred AEST or AWST view through the backend form rendering. If you want to see how a similar housekeeping task fits into a wider TYPO3 workflow, or if you are new to the site, you can read more on the about page and the rest of the notes published here. Drop your own Scheduler tips in the comments if you have a trick that has saved your bacon on a long-running install.


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