Skip to content

Dependency rot in 20 PHP applications, and in 15 fresh installs

Every Monday lockrot 0.10.0 reads two kinds of composer.lock and reports the packages in them that stopped being maintained: the lock the newest stable release of 20 open-source PHP applications ships, and the lock 15 new projects get when they are created that morning with composer create-project. This page is the last run, 2026-09-21. Nothing is installed, no script or plugin from any package is run, and no project is contacted: the run reads lock files, then asks Packagist and the repository host about the packages in them.

What these applications ship

Project Release Packages abandoned silent pinned left-behind old-promise stale Report
coollabsio/coolify v4.3.23 · e2e2d40 161 0 2 0 0 2 5 open
appwrite/appwrite 2.2.0 · 5105525 91 2 1 0 0 2 0 open
bagisto/bagisto v2.4.11 · 9beff85 167 0 0 0 2 2 5 open
monicahq/monica v4.1.2 · 32028ce 181 7 2 1 16 21 4 open
firefly-iii/firefly-iii v6.7.3 · 99f9b74 132 0 1 1 0 4 5 open
matomo-org/matomo 5.13.0 · 04c5286 55 1 4 2 6 11 0 open
BookStackApp/BookStack v26.05.5 · 771a2d0 113 0 0 0 0 1 3 open
grokability/snipe-it v8.7.2 · f3f1dd7 166 1 5 1 5 7 4 open
wallabag/wallabag 2.6.14 · 74cbfd9 210 27 10 1 54 37 3 open
magento/magento2 2.4.9 · 755e34d 148 4 0 0 1 6 3 open
mautic/mautic 7.2.0 · 504af18 193 2 2 1 10 14 3 open
akaunting/akaunting 3.2.4 · 3940b76 183 16 0 0 7 9 8 open
invoiceninja/invoiceninja v5.13.43 · 3820200 245 0 0 2 7 7 8 open
PrestaShop/PrestaShop 9.1.5 · aee413e 227 4 3 0 8 28 6 open
moodle/moodle v5.2.3 · 344232c 48 0 1 0 0 4 0 open
phpmyadmin/phpmyadmin RELEASE_5_2_3 · 962857e 58 2 1 0 9 13 0 open
phpbb/phpbb release-3.3.17 · 3508484 40 4 1 0 16 5 1 open
joomla/joomla-cms 6.1.3 · 0f2d4de 89 2 0 2 2 4 1 open
drupal/drupal 11.4.7 · e0e7bfb 66 0 1 0 0 1 0 open
TYPO3/typo3 v14.3.7 · 07f4dc8 77 0 0 0 0 0 1 open
20 projects 2650 72 34 11 143 178 60

Each row names the release it read and the commit that release points at, so any number here can be checked against the same two files lockrot read. A release rather than a branch head on purpose: a release is what people install, and it is the only version of a project that two weeks of this page can be compared across — the tip of a development branch moves for reasons that have nothing to do with dependency rot.

What a new project gets today

A project started this morning has no history to rot in, and that is exactly why it is worth measuring: its dependency tree is whatever the current constraints resolve to, and the answer changes weekly without anyone touching the project. This half of the run creates each one from scratch — composer create-project, with the starter package pinned to its newest stable version, nothing installed and no script or plugin executed — and reads the lock file that falls out.

New project Created from Packages Advisories abandoned silent pinned left-behind old-promise stale Report
Laravel laravel/laravel v13.10.1 76 0 0 0 0 0 0 1 open
Symfony (skeleton) symfony/skeleton v8.1.99 1 0 0 0 0 0 0 0 open
Symfony (--webapp) symfony/skeleton v8.1.99 133 0 0 0 0 0 0 2 open
API Platform symfony/skeleton v8.1.99 47 0 0 0 0 0 1 0 open
Drupal drupal/recommended-project 11.4.7 68 0 0 1 0 0 1 0 open
WordPress (Bedrock) roots/bedrock 1.31.5 15 0 0 0 0 0 1 0 open
Shopware shopware/production v6.7.14.1 173 0 0 0 0 0 5 3 open
Sylius sylius/sylius-standard v2.2.4 195 0 3 0 0 1 5 1 open
TYPO3 typo3/cms-base-distribution v14.3.0 106 0 0 0 0 0 0 1 open
Craft CMS craftcms/craft 5.8.0 111 0 0 2 0 1 7 3 open
Statamic statamic/statamic v6.5.1 123 0 0 1 0 0 2 4 open
Pimcore pimcore/skeleton v2026.2.0 187 0 1 1 0 0 6 4 open
Slim slim/slim-skeleton 4.5.0 16 0 0 1 0 1 0 0 open
CakePHP cakephp/app 5.4.0 20 0 0 0 0 0 0 0 open
Yii 2 yiisoft/yii2-app-basic 2.0.55 25 0 0 0 0 1 1 0 open
15 starters 1296 0 4 6 0 4 29 19

The advisories column counts packages with a security advisory against the installed version, from the same feed composer audit reads. It is here and not in the table above because these reports are made with --all: every package is in the document, so an advisory on an otherwise healthy one is visible. The applications are read without --all, where a healthy package is not a finding and never reaches the page.

Several projects in this table cannot be in the one above, and for one reason: Shopware, Sylius, TYPO3, Craft CMS, Statamic and WordPress-through-Bedrock do not commit a lock file to their repository. Their lock is born at create-project time, which is the only place it can be read — and is the version their users actually run.

A verdict is an observation, not a judgement, and the two that carry most of the table say different things. abandoned is the Packagist marker or an archived repository — someone said so. silent is no stable release and no push to any branch for five years, which is lockrot's default threshold and not a law of nature: a package that encodes base64url does not need a release in 2035 either. That is what the allowlist is for, and a project that has looked at one of these and decided it is fine can say so in its own composer.json in one line.

old-promise counts against PHP 8.4 for every project here, whatever each one actually targets, because a column has to mean the same thing in every row. On a real project it is --target-php that decides it.

Why these projects

The 20 are applications people deploy, chosen on two public signals and one hard constraint. The signals: how widely a thing is actually run, where someone else measures it — W3Techs, 21 September 2026, puts Joomla on 1.1% of all websites, Drupal on 0.6% and Adobe's Magento on 0.6% — and GitHub stars, which is what puts Coolify, Appwrite and Bagisto at the top of any list of PHP applications today.

The constraint decided more of the first table than either signal: a project has to commit composer.json and composer.lock at a tagged release. Of the 300 most-starred PHP repositories and a hand list of the widely-deployed ones — 109 looked at — 61 came through that filter, and these 20 are chosen from those 61 for spread across what the applications actually do: CMS, shop, forum, LMS, analytics, marketing, accounting, asset management, the rest.

The second table is where the rest of them turn up. WordPress, Shopware, Sylius, TYPO3, Craft CMS, Statamic, Pimcore, Dolibarr, Roundcube, osTicket, SilverStripe, Contao, October, MODX, ProcessWire and Vanilla commit no lock file at all; their dependency tree exists only once someone installs them, so that is where it is read. The starters are the ways people actually begin a PHP project — the framework skeletons and the vendor-recommended distributions — each pinned to its newest stable version so the row names something checkable. The list, the reasoning and the near misses are in data/watch/projects.json.

What moves these numbers

Three things, and they are worth telling apart. A project cuts a release, and the row moves because the project moved — the release column says when that happened. A maintainer marks a package abandoned on Packagist, and the row moves although the release did not change at all, which is the gap this post is about and the reason composer audit does not see most of what is here. Or lockrot learns to measure something it skipped before, and the row moves because the tool moved; every row in history.csv carries the release, the commit and the lockrot version that produced it for exactly that reason.

Running it on your own lock

$ composer require --dev somework/lockrot
$ composer lockrot --target-php=8.4 --fail-on=silent

The PHAR does the same without touching the lock (download and verify), and the GitHub Action is what this page runs. A project with a long list starts from a baseline and fails on what arrives next, rather than on what is already there.