Skip to content

Issue/537 structured runtime pipelines for web and console - #574

Merged
armanist merged 21 commits into
quantum-php:masterfrom
armanist:issue/537-runtime-pipelines
Oct 13, 2026
Merged

armanist merged 21 commits into
quantum-php:masterfrom
armanist:issue/537-runtime-pipelines

Conversation

@armanist

@armanist armanist commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #537

What changed

  • WebAppAdapter::start() now runs a RequestPipeline of six stages with a RequestContext: preflight, route resolution, not found, prepare (language, debug info, view cache), dispatch (cached view or route, wrapped by middleware), and send. Stages that must be skipped check RequestContext::isStopped(); the send stage always runs and still clears the request state in a finally, as before.
  • ConsoleAppAdapter::start() now runs a ConsolePipeline of four stages with a ConsoleContext: register core commands, register app commands, validate, and run. The constructor, the core:env reduced boot and the public createApplication() are unchanged.
  • Folder layout: the web adapter, context and pipeline are in App/Adapters/Web, the console ones in App/Adapters/Console, and the stages are grouped in App/Stages/Boot, App/Stages/Request and App/Stages/Console. Stage interfaces are in App/Contracts.
  • Removed the AppAdapter base class and the WebAppTrait and ConsoleAppTrait traits. Both adapters implement AppInterface directly.

Why

The boot flow was already a staged pipeline, while the runtime flows were flat call sequences. Giving them stages and context objects is the structure #538 needs to attach lifecycle events. This PR is a pure refactor: behavior is unchanged, and no runtime events are dispatched yet.

Breaking changes

The namespaces of the two adapters, the contexts, the pipelines and the boot stages changed, and AppAdapter, WebAppTrait and ConsoleAppTrait are removed. Code that goes through AppFactory is unaffected; code that references these classes directly must update its imports. Documented in CHANGELOG.md.

How behavior was proven unchanged

  • The tests were written first against the old code, then re-run unchanged against the new code: web (OPTIONS preflight, JSON and HTML 404, matched route, view-cache hit, a failing controller leaves the request uncleaned) and console (missing command, core and app commands registered and executed, core:env reduced boot, core commands registered before app commands).
  • Compared with master, the existing adapter tests lost no assertion: only the four tests that reached private trait methods by reflection were removed, and each has a stage-level equivalent.
  • Mutation checks (leaving out or reordering stages) were run in a throwaway copy and each was caught by a test. They found two gaps, now covered: a 404 must not load the language, and core commands must register before app commands.

Validation

  • Full vendor/bin/phpunit: 1642 tests, 4013 assertions, 4 skipped (optional Twig not installed)
  • composer cs:check and composer phpstan passed
  • Smoke test in quantum-php-project: php qt list, core:version and route:list; /, /en and /api/posts respond; a 404 in HTML and JSON; OPTIONS returns 204 with the CORS headers; the debugbar renders

Notes for reviewers

  • Pre-existing and unchanged: an unknown or missing console command prints the error but exits with code 0.
  • composer rector:check reports 22 findings, none introduced here.
  • Stages still read the global request() and response() helpers, as the rest of the framework does.

Summary by CodeRabbit

  • New Features
    • Web and console applications now process requests and commands through ordered stages.
    • Console startup registers built-in and application commands; missing or unrecognized commands are rejected before execution.
    • Web requests handle preflight checks, unmatched routes, middleware, cached responses, and response sending.
  • Breaking Changes
    • Direct imports of moved adapter, context, pipeline, and boot-stage classes must be updated. Applications using AppFactory are unaffected.
    • The shared adapter base class and runtime traits have been removed.

armanist and others added 15 commits October 9, 2026 13:06
Add WebAppAdapter tests, driven only through start(), that record how the
web flow behaves today so the pipeline refactor can be proven not to
change it: OPTIONS preflight, JSON and HTML 404 responses, the matched
route response, a view cache hit, and a dispatch exception that leaves
the request uncleaned.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…factor

Add ConsoleAppAdapter tests that record how the console flow behaves
today so the pipeline refactor can be proven not to change it: a missing
command name fails, core and app commands are registered and really
executed through a non-exiting Symfony Application, and the core:env
command boots with the reduced stages while regular commands boot the
environment, error handler and app config.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Introduce RequestStageInterface, RequestContext and RequestPipeline as
the foundation for running the web request as stages. RequestContext
carries the matched route, response, view cache and a stopped flag for
one request, while AppContext stays unchanged. The pipeline runs stages
in order, rejects anything that is not a request stage, and leaves
skipping to the stages. Nothing uses these classes yet.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Add HandlePreflightStage, which answers an OPTIONS request with an empty
204 response and stops the remaining stages, and SendResponseStage, which
applies the CORS headers, sends the response, and always clears the
request state afterwards, even when sending fails. The stages are not
wired into WebAppAdapter yet.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Add ResolveRouteStage, which finds the route for the current request and
stores it on the request context and the request, and
HandleRouteNotFoundStage, which answers a request with no matched route
with the page not found response and stops the remaining stages. Both
skip when the context is already stopped. The stages are not wired into
WebAppAdapter yet.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…tion test

The view cache hit test creates cache/views and cache/views/test through
ViewCache::setup() and used to leave them behind. Record whether the
folders existed before the test and remove only the ones it created.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Add PrepareRequestStage, which loads the language, logs the registered
event listeners to the debugbar when debug is on, and sets up the view
cache and stores it on the request context, in the same order as the
former WebAppAdapter flow. It skips when the context is stopped, so an
OPTIONS or not found request does none of this work. The stage is not
wired into WebAppAdapter yet.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Add DispatchRequestStage, which produces the response of a matched
request: the cached view if there is one, otherwise the dispatched
route, wrapped by the module and framework middlewares, and stores it on
the request context. It skips when the context is stopped and fails
clearly when the matched route or the view cache was not set. The stage
is not wired into WebAppAdapter yet.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
WebAppAdapter::start() now builds a RequestContext and runs a
RequestPipeline of the preflight, route, not found, prepare, dispatch
and send stages instead of the flat sequence of trait methods, and
WebAppTrait is removed. Behavior is unchanged: the characterization
tests written before the refactor pass without edits. Only the four
tests that reached private trait methods by reflection are removed, as
the stage tests now cover them. Two tests are added to pin that
preflight and not found requests do not load the language.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Introduce ConsoleStageInterface, ConsoleContext and ConsolePipeline as
the foundation for running a console command as stages. ConsoleContext
carries the Symfony application, input and output and the exit code of
one run, using the input and output interfaces so stages can be tested
with array input and buffered output. The pipeline runs stages in
order and rejects anything that is not a console stage. Nothing uses
these classes yet.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Add the stages that run a console command: RegisterCoreCommandsStage and
RegisterAppCommandsStage, which share a base that discovers the commands
of a folder and adds them to the application, ValidateCommandStage, which
fails for a missing or unknown command, and RunCommandStage, which runs
the application with the context input and output and stores the exit
code. Stage tests use a real non-exiting Symfony application, array
input and buffered output. The stages are not wired into
ConsoleAppAdapter yet.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
ConsoleAppAdapter::start() now builds a ConsoleContext and runs a
ConsolePipeline of the register core commands, register app commands,
validate and run stages instead of the flat sequence of trait methods.
createApplication() moves into the adapter and stays overridable, and
ConsoleAppTrait is removed because it only held that one method.
Behavior is unchanged: the characterization tests written before the
refactor pass without edits. One test is added to pin that core commands
are registered before app commands.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
AppAdapter only held the AppContext property and its constructor, and
nothing referred to it except the two adapters, which are used through
AppInterface. WebAppAdapter and ConsoleAppAdapter now implement
AppInterface directly and keep their own context.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ges into folders

Group each flow in its own folder. The adapter, context and pipeline of
the web flow move to App/Adapters/Web and those of the console flow to
App/Adapters/Console, and the boot stages move to App/Stages/Boot next
to the existing Stages/Request and Stages/Console. Namespaces and imports
are updated, the tests move to mirror the source, and LoadHelpersStage
now resolves the source root one level higher. Behavior is unchanged:
the moved characterization and stage tests keep their assertions and
only their namespace and import lines differ.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-09T15:00:30.078870Z 8f75262 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 1a8e67bd-7ed8-46b6-a132-f85dc72c6fcc
📥 Commits

Reviewing files that changed from the base of the PR and between 8f75262 and 3343a05.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • src/App/Adapters/Console/ConsoleAppAdapter.php
  • src/App/Stages/Console/RegisterAppCommandsStage.php
  • src/App/Stages/Console/RegisterCoreCommandsStage.php
  • src/App/Stages/Console/RunCommandStage.php
  • src/Console/CommandRegistrar.php
  • tests/Unit/App/Adapters/Console/ConsoleAppAdapterTest.php
  • tests/Unit/App/Stages/Console/RunCommandStageTest.php
  • tests/Unit/Console/CommandRegistrarTest.php
💤 Files with no reviewable changes (1)
  • src/App/Adapters/Console/ConsoleAppAdapter.php

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

📝 Walkthrough

The web and console adapters now run staged runtime pipelines with dedicated contexts. The change adds request and console stages, moves boot stages into a dedicated namespace, and updates command registration, imports, and tests.

Changes: Runtime Pipeline Refactor

Layer / File(s) Summary
Runtime contracts and boot namespace migration
src/App/Adapters/*/*Context.php, src/App/Adapters/*/*Pipeline.php, src/App/Contracts/*StageInterface.php, src/App/Stages/Boot/*, src/App/Factories/AppFactory.php, tests/Unit/App/Adapters/*/*ContextTest.php, tests/Unit/App/Adapters/*/*PipelineTest.php, tests/Unit/App/Stages/Boot/*, CHANGELOG.md
Adds typed runtime contexts, stage contracts, and ordered pipelines. Boot stages move to the Boot namespace, LoadHelpersStage changes its component-helper source directory, and the shared AppAdapter class is removed. Related imports, tests, and changelog entries are updated.
Console command pipeline
src/App/Adapters/Console/ConsoleAppAdapter.php, src/App/Stages/Console/*, src/Console/CommandRegistrar.php, src/App/Traits/ConsoleAppTrait.php, tests/Unit/App/Adapters/Console/*, tests/Unit/App/Stages/Console/*, tests/Unit/Console/CommandRegistrarTest.php
Console startup runs core-command registration, app-command registration, and command execution as stages. CommandRegistrar discovers and adds command instances. The adapter returns the context exit code, and the former console trait is removed.
Web request pipeline
src/App/Adapters/Web/*, src/App/Stages/Request/*, src/App/Traits/WebAppTrait.php, src/App/Adapters/WebAppAdapter.php, tests/Unit/App/Adapters/Web/*, tests/Unit/App/Stages/Request/*
Web startup runs request stages for preflight handling, route resolution, not-found handling, preparation, dispatch, and response sending. Response sending applies CORS headers and clears request state. The former web adapter and trait are removed, and tests cover the staged request flow.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Refactor

Sequence Diagram(s)

sequenceDiagram
  participant ConsoleAppAdapter
  participant ConsolePipeline
  participant RegisterCoreCommandsStage
  participant RegisterAppCommandsStage
  participant CommandRegistrar
  participant RunCommandStage
  participant Application
  ConsoleAppAdapter->>ConsolePipeline: run(ConsoleContext)
  ConsolePipeline->>RegisterCoreCommandsStage: process(context)
  RegisterCoreCommandsStage->>CommandRegistrar: register core commands
  CommandRegistrar->>Application: add discovered commands
  ConsolePipeline->>RegisterAppCommandsStage: process(context)
  RegisterAppCommandsStage->>CommandRegistrar: register app commands
  CommandRegistrar->>Application: add discovered commands
  ConsolePipeline->>RunCommandStage: process(context)
  RunCommandStage->>Application: run(input, output)
Loading
sequenceDiagram
  participant WebAppAdapter
  participant RequestPipeline
  participant HandlePreflightStage
  participant ResolveRouteStage
  participant HandleRouteNotFoundStage
  participant PrepareRequestStage
  participant DispatchRequestStage
  participant SendResponseStage
  WebAppAdapter->>RequestPipeline: run(RequestContext)
  RequestPipeline->>HandlePreflightStage: process(context)
  RequestPipeline->>ResolveRouteStage: process(context)
  RequestPipeline->>HandleRouteNotFoundStage: process(context)
  RequestPipeline->>PrepareRequestStage: process(context)
  RequestPipeline->>DispatchRequestStage: process(context)
  RequestPipeline->>SendResponseStage: process(context)
  SendResponseStage->>WebAppAdapter: send response and clear request state
Loading

Suggested reviewers: andrey-smaelov

Merge Risk: ⚪ Minimal · up to 3343a

The investigated console command behavior is unchanged, and no actionable merge-blocking risk remains after normal checks.

Pre-merge checks · Passed 4 · Failed 1

Failed Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 15.30% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 183 functions across 52 files. (1 skipped… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks (4 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly identifies the issue and the main change: structured runtime pipelines for web and console execution.
Linked Issues check Passed Issue [#537] requires structured web and console runtime pipelines, dedicated contexts, preserved runtime responsibilities, future hook boundaries, and tests. WebAppAdapter::start() constructs a `Re…
Out of Scope Changes check Passed The changes stay within issue [#537]. The namespace moves, direct AppInterface implementations, and removal of the shared adapter and runtime traits support the requested runtime restructuring. `Com…
Full details: Docstring Coverage

Explanation

Docstring coverage is 15.30% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 183 functions across 52 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Comment thread src/App/Stages/Console/RegisterAppCommandsStage.php Outdated
Comment thread src/App/Stages/Console/RegisterCoreCommandsStage.php Outdated
Comment thread src/App/Stages/Console/ValidateCommandStage.php Outdated
armanist and others added 6 commits October 13, 2026 17:11
Add characterization tests for the command registration done by the console
adapter: an app command overrides a core command with the same name, a missing
app commands directory is tolerated, and only instantiable commands are
registered. They pass on the current code and guard the upcoming rework of the
register stages.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Add a CommandRegistrar in the Console package that discovers the commands of a
directory and adds them to the console application. It is the single place
that connects the native command discovery to the Symfony application. Nothing
uses it yet; the register stages will call it in the next commit.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…gistrar

Give RegisterCoreCommandsStage and RegisterAppCommandsStage their own
process() that calls the CommandRegistrar with their directory and namespace,
and remove the abstract RegisterCommandsStage they used to inherit it from.
Behavior is unchanged: core commands are still registered before app commands.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Move the directory and the namespace of the core and app register stages into
private helpers, so process() only reads as the call to the CommandRegistrar.
Behavior is unchanged.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Reject a missing or unknown command at the start of RunCommandStage and remove
ValidateCommandStage, which was a guard clause and not a stage of its own. The
exception and its message are unchanged, and nothing runs before the check.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The console pipeline no longer has a validate stage: the run stage rejects a
missing or unknown command, and the commands are registered through the new
CommandRegistrar.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@armanist
armanist merged commit 75127e9 into quantum-php:master Oct 13, 2026
6 checks passed
@armanist
armanist deleted the issue/537-runtime-pipelines branch October 13, 2026 13:44
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.

Refactor WebAppAdapter::start() and ConsoleAppAdapter::start() into structured runtime pipelines

3 participants