re #224 feat(module): MiddlewareProviderInterface — modules contribute PSR-15 middleware - #225
Merged
Merged
Conversation
added 3 commits
June 6, 2026 10:44
…e PSR-15 middleware Let a module self-register PSR-15 middleware into the HTTP pipeline the same way it already contributes routes, entities, and migrations — closing the one gap that forced a self-contained auth/rate-limit/idempotency module to make the host hand-edit public/index.php. - New contract Altair\Module\Contracts\MiddlewareProviderInterface: each entry pairs a middleware (class-string resolved via the container, or an instance) with an integer priority. Adds psr/http-server-middleware to univeros/module. - Altair\Http\Support\MiddlewarePriority: documented anchors for the framework's own stages — EXCEPTION_HANDLER (0), DISPATCHER (500), ACTION (1000) — with bands so module middleware slot deterministically (pre-routing < DISPATCHER; action-aware between DISPATCHER and ACTION). Documents the bounds: ACTION is terminal on a matched route (priority >= ACTION never runs); a priority below EXCEPTION_HANDLER runs outside the exception handler (outermost wrapper only). - Altair\Http\Support\ModuleMiddleware mirrors ModuleRoutes: collects tagged modules' middleware(), stable-sorts the merged queue by priority (equal priorities keep input order — base first, then registration order), and returns the flat list. entries() exposes the sorted entries for a host to bind a MiddlewareCollection the introspection (middleware:list) reads. - Skeleton public/index.php assembles its base pipeline as prioritised entries, merges module middleware via ModuleMiddleware::collect, and hands the queue to Relay with a ContainerResolver so class-string middleware are autowired. - Tests: merge order, determinism, container resolution, real Relay execution order, introspection listing, and the negative-priority outermost boundary. - Docs: extending.md gains a "Middleware — automatic" section; module.md adds the contract to the capability table and pickup notes.
The new MiddlewarePriority/ModuleMiddleware support classes and the updated univeros/module description change the generated manifests; regenerate so the Determinism gate (#74) stays byte-stable.
…h CI install Adding psr/http-server-middleware to univeros/module changed the CI composer cache key, busting the warm cache and forcing a clean dependency resolution. That unmasked two pre-existing static-analysis errors the warm cache had been hiding (neither reproduces on the local macOS dependency tree; both are real on CI's slim Linux install): - Scaffold\Emitter\HandlerTestEmitter::emit() computed shortNameOf($messageFqcn) and discarded the result (method.resultUnused) — the message short name is never used in the generated test body. Remove the dead statement; the emitted output is byte-identical. - Messaging\Configuration\MessengerConfiguration probes for optional Symfony Messenger transport-bridge factories via class_exists(); a slim CI install resolves some bridges as absent, so PHPStan proves the check false-at-analysis (function.impossibleType) even though the runtime check is intentional. Add a scoped ignoreErrors entry with a reason, per the project's stan policy.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #224.
Goal
Let a module self-register PSR-15 middleware into the HTTP pipeline the same way it already contributes routes, entities, and migrations. Previously the Relay pipeline was hand-assembled in the host's
public/index.php, so any feature needing a guard (auth, rate-limit, tenant resolution, an action-aware idempotency check) forced the host to hand-edit the front controller — defeating the self-registering-module value proposition.What changed
New contract —
Altair\Module\Contracts\MiddlewareProviderInterface(mirrorsRoutesProviderInterface):A module opts in by also implementing it.
univeros/modulegains apsr/http-server-middlewaredependency (a stable PSR interface package).Ordering — integer priority with documented bands.
Altair\Http\Support\MiddlewarePriorityexposes the framework's own stages as anchors so module middleware can slot relative to them:EXCEPTION_HANDLER0DISPATCHER500ACTION1000Pre-routing guards slot below
DISPATCHER; action-aware guards betweenDISPATCHERandACTION. Boundaries are documented:ACTIONis terminal on a matched route (priority>= ACTIONnever runs), and a priority belowEXCEPTION_HANDLERruns outside the exception handler (reserved for a deliberate outermost wrapper).Pickup at bootstrap —
Altair\Http\Support\ModuleMiddlewaremirrorsModuleRoutes: it collectsaltair.module-tagged modules'middleware(), stable-sorts the merged queue by priority (equal priorities keep input order — base entries first, then registration order, so the result is deterministic), and returns the flat list.entries()exposes the sorted entries so a host can bind aMiddlewareCollectionthatbin/altair middleware:listreads. The skeletonpublic/index.phpnow assembles its base pipeline as prioritised entries, merges module middleware, and hands the queue to Relay with aContainerResolver(class-string middleware are autowired).Acceptance criteria
MiddlewareProviderInterfaceunderAltair\Module\Contracts, same doc/style asRoutesProviderInterface.bin/altairmiddleware listing (Introspection commands — container, routes, events, middleware, manifests, config #71) shows module-contributed middleware in resolved order (covered by aPipelineInspectortest over the merged collection).extending.mdgains a "Middleware — automatic" section; a module can register an action-aware guard without the host editingpublic/index.php.Test plan
composer cs— cleancomposer stan(PHPStan level 8, no baseline) — no errorscomposer rector(whole-tree dry-run) — no changesvendor/bin/phpunit tests/Http/Support/ModuleMiddlewareTest.php— 9/9 pass (merge order, sorting, equal-priority determinism, negative-priority outermost boundary, container resolution, real Relay execution order, introspection listing, service-only no-op, entries() ordering)tests/Http tests/Bootstrap tests/Introspection tests/Mcp— 396 passNotes
module:new) is intentionally left unchanged to avoid shipping a sample middleware; the capability is fully documented inextending.md.bin/altair middleware:listreflects module middleware when the host binds the merged collection as itsMiddlewareCollection; introspection is not wired into the default skeleton, so this is documented as the host-side hook.