Skip to content

Composer Portability Audit (Historical)

Snapshot of a portability audit run against framework/ inside the moodle-local_middag legacy codebase, before that code was extracted into middag-io/framework as a standalone Composer package. Preserved as a historical record, not a current-state report — the constraint it measured is now enforced structurally by this repository's own existence as a plugin-independent package. Useful as a template for anyone scoping a similarly-shaped extraction elsewhere in the ecosystem. Backs FW-011.

Portability score at audit time

DimensionScore
Namespace isolation100%
Deptrac boundary90%
Moodle boundary40%
Hard-coded paths30%
require_once discipline85%
Average69%

Named violations at audit time

Host globals read directly, outside the moodle/ adapter layer (4 files):

FileGlobals read directly
shared/util/helper.php$OUTPUT, $PAGE
infrastructure/adapter/inertia/inertia_response.php$PAGE
infrastructure/adapter/inertia/inertia_shared_props.php$USER, $PAGE
infrastructure/logging/file_logger.php$USER

Hard-coded paths (6 files): kernel/router.php, kernel/container_factory.php, kernel/http/plugin_aware_url_generator.php, kernel/http/abstract_controller.php (entry-point/asset paths), infrastructure/adapter/inertia/inertia_response.php (a CSS path), service/extension_service.php (a binary/Pix path).

Accepted exceptions (justified, not counted as violations):

FileJustification
kernel/container_factory.phpReads $DB/$CFG directly — runs at bootstrap, before the DI container exists to inject anything into it. Governed by a separate, older decision (legacy ADR-926).
shared/util/environment.phpReads $CFG for environment detection, same bootstrap-timing rationale.

Blockers named for the extraction, and how they were resolved

#BlockerResolved by, in middag-io/framework
1The Inertia adapter and shared/util/helper read host globals directly instead of receiving them via an injected adapter.The bridge-contract pattern (HostComponentContextInterface, UserContextResolverInterface, etc.) — see architecture.md §5.
2Entry-point URLs, asset paths and binary paths were hard-coded instead of read from injected configuration.ConfigResolverInterface (EnvConfigResolver default).
3kernel/ inferred the host's plugin identity from a global constant instead of accepting it via DI.BootstrapInterface::getProjectRoot()/platform(), fed into ContainerFactory::build() — see bootstrap.md §1.

Whether the exact five-file violation list above was fixed file-by-file before this package was assembled, or the package was written fresh against the bridge-contract pattern from the start, is not established by the historical record. Either way, each blocker maps directly onto a mechanism this framework now ships as a first-class contract, and the 69% score has no live equivalent to compute here — there is no host plugin left to score against.

MIDDAG © 2015-2026