Skip to content

Shared-Namespace Dependencies ​

Reference for the Composer dependencies of middag-io/moodle whose PHP namespaces Moodle core also bundles globally, the "shadow risk" this creates, and the checks to run before changing them. Verified on 2026-11-04 against middag-php-moodle main@6aae04d (composer.json, src/) and a Moodle 5.0.9 checkout (Build 20260810, lib/classes/component.php).

The risk ​

Moodle core does not load its own lib/ third-party code through Composer. It vendors a fixed set of libraries under lib/<name>/ and registers their namespaces globally, for every request, in core_component::$psr4namespaces. Moodle's autoloader resolves them independently of, and usually before, any plugin's vendor/autoload.php.

When this adapter requires a package that lands in one of those namespaces, PHP loads whichever registration wins the race. Which one wins depends on bootstrap order (does the consumer plugin require its vendor/autoload.php before or after Moodle's lib/setup.php registers the core map?), and that varies by hosting layout: a bundled plugin, a Composer-managed site, or a project-root vendor in a container. A single package's composer.json cannot pin it. It is not guaranteed to be this adapter's own vendor/ copy. The same holds for Middag\* classes themselves: a consumer's autoloader cannot unload a class that another autoloader already resolved earlier in the request.

Namespaces Moodle bundles (5.0.9) ​

NamespaceMoodle-bundled pathBundled version
Firebase\JWTlib/php-jwt/src6.11.0
Psr\Loglib/psr/log/srcPSR interfaces only
Psr\Containerlib/psr/container/srcPSR interfaces only
Psr\SimpleCachelib/psr/simple-cache/srcPSR interfaces only
Psr\Clocklib/psr/clock/srcPSR interfaces only
Psr\EventDispatcherlib/psr/event-dispatcher/srcPSR interfaces only
Psr\Http\Messagelib/psr/http-factory/src, lib/psr/http-message/srcPSR interfaces only
Psr\Http\Clientlib/psr/http-client/srcPSR interfaces only
Psr\Http\Serverlib/psr/http-server-handler/src, lib/psr/http-server-middleware/srcPSR interfaces only
GuzzleHttp, GuzzleHttp\Psr7, GuzzleHttp\Promiselib/guzzlehttp/*Bundled Guzzle stack
PHPMailer\PHPMailerlib/phpmailer/srcBundled PHPMailer

The list is not exhaustive; it covers the namespaces relevant to this adapter's dependency tree.

This adapter's shared-namespace packages ​

PackageHow the adapter gets itShared namespace
firebase/php-jwt ^7.0Direct requirementFirebase\JWT
psr/container ^2.0Direct requirementPsr\Container
psr/log ^3.0Direct requirementPsr\Log
psr/simple-cache ^3.0Direct requirementPsr\SimpleCache
psr/event-dispatcherThrough symfony/event-dispatcher-contractsPsr\EventDispatcher
psr/clock, psr/http-message, psr/http-factory, psr/http-client, psr/http-server-handler, psr/http-server-middlewareThrough middag-io/framework (and nyholm/psr7, symfony/http-client)Psr\Clock, Psr\Http\*

nyholm/psr7 and symfony/http-client themselves do not collide: Moodle bundles Guzzle's PSR-7 implementation (GuzzleHttp\Psr7), a different vendor namespace from Nyholm\Psr7. Only the shared PSR interface namespaces overlap.

The known case: firebase/php-jwt ​

Moodle 5.0.9 bundles Firebase\JWT 6.11.0; this adapter requires ^7.0, and v6 to v7 changed the JWT / Key API. Nothing in src/ uses Firebase\JWT\*, so the dependency is declared but dead, and the collision is dormant rather than triggered. The moment any layer calls use Firebase\JWT\JWT;, which copy loads is a matter of bootstrap order.

Rules ​

  • The adapter declares its dependencies normally: no namespace rewriting (scoping), no delegating to Moodle's bundled copy.
  • Before raising a shared-namespace package to a new major, or raising the adapter's minimum Moodle version, check the new major against what the target Moodle versions bundle for that namespace.
  • An unused shared-namespace dependency is removed rather than kept "just in case": it is a standing collision risk with no benefit. firebase/php-jwt is the pending case; the removal is its own composer.json change.
  • The autoloader race itself is a property of how the consumer bootstraps. The adapter documents the risk surface; it cannot resolve the race from its own code.

Enforcement state ​

ClauseState
This table matches composer.json and Moodle's core_component mapplanned — no CI check diffs the adapter's namespaces against a Moodle checkout
Major bumps of shared-namespace packages are checked against Moodle's copyplanned — review
firebase/php-jwt removed while unusedplanned — still required on main@6aae04d

Decision record: MDL-020 in middag-php-moodle/docs/decisions.

MIDDAG © 2015-2026