Skip to content

API Coverage & Version Gates ​

Reference for how middag-io/moodle covers Moodle's APIs: the runtime version gates that let one codebase serve every supported Moodle version, the coverage registry (tiers A, B and C), and the @api/@internal classification of the adapter's host surface. Verified against middag-php-moodle main@6aae04d on 2026-11-04.

One codebase, runtime gates ​

There is no branch or package per Moodle version. The adapter's support floor is Moodle 4.5; an API that only exists on newer (or older) versions is gated at runtime, either by version range or by detecting the symbol, and degrades to a defined behavior when absent.

MechanismAPI
Version rangeVersionSupport::supports($feature, $matrix) with ['since' => 'x.y', 'until' => 'x.y'] rules; VersionSupport::atLeast(), between(), assertMin()
Symbol detectionVersionSupport::symbolExists($fqcn) (classes, interfaces and traits); method_exists() for single methods
Per-definition gatemin_moodle / max_moodle on every Definition/* class, checked by isCompatible($moodleVersion) before a static file entry is emitted

Version gates in the code ​

APIGateBehavior when unavailable
Hooks APIHookDefinition defaults to min_moodle: '4.3'Hook entry not emitted into db/hooks.php
DI bridge (PSR-11 \core\di)DiBridgeSupport::isAvailable(): supports('moodle_di_hook', ['since' => '4.4']) and symbolExists('core\hook\di_configuration')Nothing exported to Moodle's container; the framework container is unaffected. See DI Bridge
Native routerRouterBridgeSupport::isAvailable(): supports('moodle_router', ['since' => '5.1']) and symbolExists('core\router\route_loader_interface')Proxy inactive; routing only through the consumer's own entry points. See Routing Bridge
Bootstrap 5 navbar markupNavbarService: VersionSupport::atLeast('5.0')Bootstrap 4 attributes (data-toggle, dropdown-menu-right) instead of data-bs-toggle, dropdown-menu-end
Read-replica table pinningDbSupport::markTablesForPrimary(): method_exists($DB, 'mark_tables_for_primary') (Moodle 5.2+)No-op
Check APINo runtime gate: the API predates the 4.5 floorn/a

Coverage registry ​

Every Moodle API the boundary might touch is classified into one of three tiers, each with standard justification language:

TierNamePatternWhenJustification
AFull integration*Support, entities, DTOs, enums, definitions, port implementations, contractsAPI consumed by 2+ extensions, or a cross-cutting concern"Essential to the framework: {reason}"
BCompatibility interfaceMinimal contract or light wrapperAPI consumed by specific extensions; absorbs breaking changes"Protection interface: {reason}. Priority: {high|medium|low}"
COut of scopeNo artifactDoes not apply to the framework's purpose, or covered by its own mechanism"Out of scope: {reason}"

Admission to tier A requires 2+ real call sites, a wrapper that follows the support pattern, entities and DTOs that follow the host domain layout, an updated boundary allowlist, and, when the integration is non-trivial (DI, routing), its own decision record. A tier B interface is promoted to A once it has 3+ distinct operations or 2+ consuming extensions. The registry is reviewed by event (a new Moodle major version, a new extension, a product-discovery gap), not on a calendar.

Tier A — 45 APIs ​

Access/Capability, Authentication, Backup/Restore, Cache, Config, Context, Course/Category/Cohort, DML, Enrolment, Events, External Services/Web Services, File, Form, Grade, Groups, Hooks, HTML Writer, Logging, Message, Navigation, Output/Rendering, Page, Plugin, Privacy (GDPR), Role, Session, Settings (Admin), String/i18n, Task, Upgrade, URL, User, Version, Preference, Calendar, Lock, Custom Fields, Check, Notification, Time, Competency, Routing (5.1+), DI (PSR-11, 4.4+).

All are available on the 4.5 floor except the gated rows in the table above. The registry was reconstructed when the legacy plugin had 38 wrappers; the adapter now has 42 (see Support Layer).

Tier C — out of scope ​

#APIJustification
C01DDL (data definition)Persistence goes through the framework's repositories and schema adapter; tables live in install.xml
C02DeprecationThe framework has its own deprecation model, independent of Moodle's debugging()
C03Search (global search)Global-search indexing is the extension's responsibility, through \core_search\base directly

@api / @internal classification ​

Every class in src/ carries an explicit stability tag. The default for the host domain surface is @api: extensions and external plugins consume the typed wrappers directly, so treating them as internal would be fiction. A class opts out with an explicit @internal tag.

AreaTagNotes
Domain/* entities, DTOs, enums and Contract/ interfaces; Domain/AbstractMoodleEntity@apiIncludes CalendarEventDto and NotificationDto
Infrastructure-only DTOs@internalDomain/Task/AdhocTaskDto, RunningTaskDto, ScheduledTaskDto, TaskSummaryDto, Domain/Platform/PluginDto
Domain/* service implementations@internalEnrolmentService, FileService, GroupService, MessageService, PlatformService, AdhocService, ScheduledService, UserService; consume them through their Contract/ interface
Definition/, Settings/ (DSL, SettingsResolver, SettingsNamingPolicy), Privacy/Contract/, Domain/File/Contract/FileAreaHandlerInterface, Domain/Backup/Contract/BackupStepsProviderInterface@apiSafe to import directly
Support/*Support, Support/Moodle@apiExcept DiBridgeSupport, RouterBridgeSupport, TaskDefinitionBuilder, CrudConventionResolver (@internal)
Runtime/Kernel, Bus/MoodleHostEventBridge@apiThe consumer's composition root drives them
Port implementations wired by the container (MoodleConfigResolver, MoodleConnectionAdapter, MoodleSqlDialect, TransactionManager, MoodleBootstrap, MoodleMaintenanceGate), Http/ controllers, kernel, routing and Inertia bridge, Form/, Security/ implementations, Output/NavbarService, Output/Widget, Output/AbstractBlock@internalBind or consume them through the framework port or the local Contract/ interface, never by importing the concrete class

An @api class enters the API evolution and deprecation cycle of the family's 1.x policy (API-STABILITY.md in each repository): a breaking change goes through deprecation, never a silent rename.

Enforcement state ​

CheckState
Tier A admission (call-site count, promotion criteria)planned — no automated check
@internal classes are not imported from outside a facade or DI seamplanned — no static-analysis rule restricts imports across the tags
Entities and DTOs default to @api unless tagged @internalplanned — no automated check; the tag is present on every class today

Decision records: MDL-002 and MDL-005 in middag-php-moodle/docs/decisions.

MIDDAG © 2015-2026