Skip to content

Refactor WebAppAdapter and web helpers to remove normal stop() usage #472

Description

@armanist

Summary

Remove stop() from normal web response flow and make WebAppAdapter fully return-driven.

Goals

  • WebAppAdapter::start(): ?int remains unchanged
  • WebAppAdapter::start() always returns 0
  • normal web flow sends a returned Response
  • web helper paths stop relying on StopExecutionException

In scope

  • src/App/Adapters/WebAppAdapter.php
  • src/App/Traits/WebAppTrait.php
  • src/Http/Helpers/http.php
  • src/ResourceCache/ViewCache.php
  • web-specific short-circuit helpers such as 404 / redirect flow

Target start() shape

public function start(): ?int
{
    if (request()->isMethod('OPTIONS')) {
        $response = response()->setStatusCode(StatusCode::NO_CONTENT);
    } else {
        $this->loadModules();

        $matchedRoute = $this->resolveRoute();

        $this->loadLanguage();

        $this->logDebugInfo();

        $response = $this->setupViewCache()
            ->getCachedResponse(request()->getUri() ?? '');

        if ($response === null) {
            $response = (new MiddlewareManager($matchedRoute))
                ->applyMiddlewares(request(), response());

            $response = (new RouteDispatcher())
                ->dispatch($matchedRoute, request(), $response);
        }
    }

    $this->sendResponse($response);

    return 0;
}

Required helper shape

private function sendResponse(Response $response): void
{
    $this->handleCors($response);
    $response->send();
}

Cached view contract

Current style is mutation plus boolean:

public function serveCachedView(string $uri, Response $response): bool

Target style should be:

public function getCachedResponse(string $uri): ?Response

Short-circuit paths to replace

  • OPTIONS
  • page_not_found()
  • redirect helper flow
  • cache-hit flow

Acceptance criteria

  • normal web flow has no try/catch StopExecutionException
  • WebAppAdapter::start() returns 0
  • normal web paths do not rely on stop()
  • cached-view lookup returns ?Response directly

Validation

  • WebAppAdapter tests
  • web helper tests
  • full web path regression checks

Activity

  1. armanist commented on Apr 28, 2026

    @armanist
    CollaboratorAuthor

    Architecture proposal update for #472 (based on deeper review):

    I believe we should avoid passing Response through middleware/dispatcher as a threaded mutable object. It creates ownership ambiguity and mixes two models (explicit pipeline vs DI-owned response).

    Recommended direction

    Adopt a stricter PSR-15-style middleware shape and explicit adapter control flow:

    • QtMiddleware::apply(Request $request, Closure $next): Response
    • MiddlewareManager::applyMiddlewares(Request $request): ?Response
      • Response => middleware short-circuited (send it)
      • null => continue to route dispatch
    • RouteDispatcher::dispatch(...): Response remains strict
    • WebAppAdapter decides branch explicitly (cache hit / middleware short-circuit / dispatch), then sends exactly one final response.

    Why

    • Removes response overwrite/confusion from the current draft shape.
    • Keeps control flow explicit and testable.
    • Preserves a single response ownership model (DI/request scope) without pretending to pass ownership through layers.
    • Cleanly supports middleware short-circuit semantics.

    Suggested start() flow (high-level)

    1. Handle OPTIONS by creating/sending response, return 0
    2. Resolve route and setup runtime
    3. If cache returns response => send + return 0
    4. middlewareResponse = applyMiddlewares(request())
    5. If middlewareResponse !== null => send + return 0
    6. Else dispatch route => send returned response + return 0

    If agreed, I can follow with a concrete implementation checklist (file-by-file) for safe rollout.

  2. armanist commented on Apr 28, 2026

    @armanist
    CollaboratorAuthor

    Follow-up proposal update for #472 (clarifying terminal handler model):

    After review, I recommend replacing the earlier ?Response manager idea with a stricter and cleaner contract:

    • QtMiddleware::apply(Request $request, Closure $next): Response
    • MiddlewareManager::applyMiddlewares(Request $request, Closure $terminal): Response

    What is $terminal?

    $terminal is the final handler executed after middleware chain completes.
    For #472, $terminal is route dispatch (RouteDispatcher::dispatch(...)).

    Why this is better

    • avoids fallback hacks like ?? response()
    • avoids ambiguous response ownership/overwriting
    • keeps middleware short-circuit behavior natural (return response()->...)
    • keeps control flow explicit while preserving current developer ergonomics

    Adapter shape (high-level)

    1. OPTIONS branch -> response -> send -> return 0
    2. Cache hit branch -> response -> send -> return 0
    3. Otherwise run middleware manager with terminal dispatch callback
    4. Send returned response and return 0

    If this direction is accepted, implementation can proceed in phases with minimal breakage.

  3. added 6 commits that reference this issue on Apr 28, 2026
  4. added this to the 3.0.0 milestone on Apr 28, 2026
  5. added 11 commits that reference this issue on Apr 28, 2026
  6. added a commit that references this issue on Apr 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions