Skip to content

[2.x] fix: return an empty collection when there is no installed.json to read - #4946

Open
karl-bullock wants to merge 1 commit into
flarum:2.xfrom
karl-bullock:fix-extension-manager-null-collection
Open

[2.x] fix: return an empty collection when there is no installed.json to read#4946
karl-bullock wants to merge 1 commit into
flarum:2.xfrom
karl-bullock:fix-extension-manager-null-collection

Conversation

@karl-bullock

@karl-bullock karl-bullock commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Affected versions: reproduced on 2.0.0-rc.5. The code is unchanged on 2.x at ff36902 (Application::VERSION = 2.0.0-rc.6), so it still applies. This PR is based on that commit.

ExtensionManager::getExtensions() is declared to return a Collection, but only assigns one when vendor/composer/installed.json exists. When it does not, the property stays null and the method returns null, so PHP raises:

TypeError: Flarum\Extension\ExtensionManager::getExtensions():
Return value must be of type Illuminate\Support\Collection, null returned

The missing-file case is not hypothetical. It is the state a forum is in partway through a composer operation, which is also the moment the admin panel is most needed to recover. A fatal there turns a recoverable situation into a white screen, and the people most likely to hit it are the ones without shell access to put it right.

This returns an empty collection instead, which is what the neighbouring getInstalledPackageNames() already does for the same missing file. It is deliberately not cached, so the next call picks the extensions up once composer has written the manifest.

Reproducing

On a stock 2.0.0-rc.5 forum:

mv vendor/composer/installed.json vendor/composer/installed.json.bak

Every request then returns 500 with the TypeError above. With this change the forum serves 200, the API answers, and restoring the file brings all extensions back with no restart.

I ran that in both directions on a throwaway forum: stock core with the manifest hidden gave 500 on every request; the same forum with only this change applied, manifest still hidden, gave 200; restoring the manifest listed all 17 extensions again.

One question for reviewers

This is a judgement call rather than a correctness one, so I would rather raise it than leave it implicit: you could argue a missing installed.json should fail loudly, and that quietly reporting zero extensions hides a broken install.

My reasoning for the empty collection is that the forum boots and stays reachable, which is what lets somebody fix the install, and that the neighbouring method already treats the same missing file that way. If you would prefer an explicit exception with a clear message, I am happy to change it.

Testing

framework/core's own suites pass with the change in place (409 and 861 tests, no failures). PHPStan is clean.

One correction to my commit message, which I cannot amend now that the branch is published: it says this was found by running the extension manager's integration suite. That is where I hit it, but that suite does not currently run on a clean checkout. extensions/package-manager is absent from monorepo_tests, its tests/integration/setup.php still requires ../../vendor/autoload.php rather than the monorepo bootstrap, and composer test:setup fails because the fixture in SetupComposer.php pins flarum/core to 1.0.0, which current Composer declines to install over published security advisories. So please do not take that line as a reproduction recipe. The mv installed.json steps above are the reliable one.

ExtensionManager::getExtensions() is declared to return a Collection, but
only assigns one when vendor/composer/installed.json exists. When it does
not, the property stays null and the method returns null, so PHP raises

  TypeError: getExtensions(): Return value must be of type
  Illuminate\Support\Collection, null returned

on every call. The missing-file case is not hypothetical: it is exactly
the state a forum is in partway through a composer operation, which is
also when the admin panel is most needed to recover. A fatal there turns a
recoverable moment into a white screen.

Returns an empty collection instead, which is what getInstalledPackageNames()
directly below already does for the same file. It is deliberately not
cached, so the next call picks the extensions up once composer has written
the manifest.

Found by running the extension manager's integration suite, where it
accounted for all 28 errors.
@karl-bullock
karl-bullock requested a review from a team as a code owner August 17, 2026 01:38
@karl-bullock karl-bullock changed the title Return an empty collection when there is no installed.json to read [2.x] fix: return an empty collection when there is no installed.json to read Aug 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant