Skip to content

Feature/490 errorhandler refactor - #491

Merged
armanist merged 5 commits into
quantum-php:masterfrom
armanist:feature/490-errorhandler-refactor
May 1, 2026
Merged

armanist merged 5 commits into
quantum-php:masterfrom
armanist:feature/490-errorhandler-refactor

Conversation

@armanist

@armanist armanist commented May 1, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #490

Summary by CodeRabbit

  • New Features

    • Improved exception rendering pipeline with dedicated web renderer and formatted stack traces in debug mode
    • Explicit HTTP 500 responses for server errors
  • Improvements

    • CLI: concise error message in production, full diagnostics in debug
    • More accurate mapping of exceptions to log severity levels
    • Cleaner, secure error output in production
  • Tests

    • Expanded unit tests covering rendering, formatting, and severity resolution

@coderabbitai

coderabbitai Bot commented May 1, 2026 •

Copy link
Copy Markdown

Warning

Rate limit exceeded

@armanist has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 42 minutes and 36 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a49f57d4-5559-48be-a3a9-41fc16c4bd70

📥 Commits

Reviewing files that changed from the base of the PR and between b12c13b and 3786a21.

📒 Files selected for processing (3)
  • src/Tracer/ErrorHandler.php
  • tests/Unit/Tracer/ErrorHandlerTest.php
  • tests/Unit/Tracer/StackTraceFormatterTest.php
📝 Walkthrough

Walkthrough

ErrorHandler is refactored into an orchestrator that delegates severity resolution, stack-trace formatting, and web rendering to new collaborators: ExceptionSeverityResolver, StackTraceFormatter, and WebExceptionRenderer; CLI/web handling and logging behavior adjusted, and rendering fallbacks added.

Changes

Cohort / File(s) Summary
ErrorHandler orchestrator
src/Tracer/ErrorHandler.php
Rewrote as an orchestrator: constructor accepts optional ExceptionSeverityResolver, StackTraceFormatter, WebExceptionRenderer. Delegates error-type resolution, stack trace composition, and web rendering. Removes inline stack/source helpers; adjusts CLI output (message-only in prod, verbose in debug), uses explicit StatusCode::INTERNAL_SERVER_ERROR, and preserves non-debug logging.
Severity resolver
src/Tracer/ExceptionSeverityResolver.php
New final class mapping Throwable → PSR‑3 log level (ErrorException via mapped codes, ParseError→critical, ReflectionException→warning, default→error).
Stack trace formatter
src/Tracer/StackTraceFormatter.php
New final class composing structured stack traces and source excerpts, skips ErrorHandler frames, reads source via filesystem adapter, and returns HTML snippets with highlighted lines.
Web renderer
src/Tracer/WebExceptionRenderer.php
New class that renders web error content: in debug uses errors/trace with composed trace, otherwise returns errors/500. Injects StackTraceFormatter; may throw and let caller handle fallback.
Tests and test helpers
tests/Unit/Tracer/ErrorHandlerTest.php, tests/Unit/Tracer/ExceptionSeverityResolverTest.php, tests/Unit/Tracer/StackTraceFormatterTest.php, tests/Unit/Tracer/WebExceptionRendererTest.php, tests/Unit/AppTestCase.php
Updated ErrorHandler tests to focus on orchestration and fallback; added unit tests for ExceptionSeverityResolver, StackTraceFormatter, and WebExceptionRenderer; moved private-method reflection helper into shared AppTestCase. Removed tests tied to internal helper implementations.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant ErrorHandler
    participant ExceptionSeverityResolver
    participant StackTraceFormatter
    participant WebExceptionRenderer
    participant FileSystem
    participant ViewFactory

    Client->>ErrorHandler: Exception occurs
    ErrorHandler->>ExceptionSeverityResolver: resolve(throwable)
    ExceptionSeverityResolver-->>ErrorHandler: severity

    alt CLI Mode
        ErrorHandler-->>Client: print message (or verbose diagnostics if debug)
    else Web Mode
        ErrorHandler->>StackTraceFormatter: compose(throwable)
        StackTraceFormatter->>FileSystem: read source lines
        FileSystem-->>StackTraceFormatter: source snippet
        StackTraceFormatter-->>ErrorHandler: formatted trace
        ErrorHandler->>WebExceptionRenderer: render(throwable, severity)
        WebExceptionRenderer->>ViewFactory: renderPartial(trace or 500)
        ViewFactory-->>WebExceptionRenderer: HTML
        alt render success
            WebExceptionRenderer-->>ErrorHandler: rendered HTML
        else render throws
            WebExceptionRenderer-->>ErrorHandler: exception
            ErrorHandler-->>ErrorHandler: catch & produce fallback 500 response
        end
        ErrorHandler->>ErrorHandler: log(severity) if non-debug
        ErrorHandler-->>Client: send HTTP 500 with body
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

testing

Suggested reviewers

  • andrey-smaelov

Poem

🐰 With twitchy nose and nimble paw, I split the burden clean,
Three helpers mind the faults I had, so errors stay routine.
I dig out traces, name the harm, and stitch a calm fallback—
A carrot for the coder who keeps this rabbit on the track! 🥕

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.24% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Feature/490 errorhandler refactor' is vague and generic, using a ticket reference and single word 'refactor' without describing the specific change or main point of the refactoring. Revise the title to be more specific about what was refactored, such as 'Split ErrorHandler into focused collaborators' or 'Extract exception severity resolver and stack trace formatter from ErrorHandler'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The pull request successfully implements all core objectives from issue #490: extracts ExceptionSeverityResolver, StackTraceFormatter, and WebExceptionRenderer as focused collaborators; keeps ErrorHandler as orchestrator; adds robust fallback behavior; and preserves CLI/web error outputs with new unit tests.
Out of Scope Changes check ✅ Passed All changes align with issue #490 scope: refactors internal error handling logic, adds new collaborator classes, updates related tests, and adds a test helper method to AppTestCase; no unrelated routing, middleware, adapter-level, or template-content changes are present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Review rate limit: 0/1 reviews remaining, refill in 42 minutes and 36 seconds.

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

@codecov

codecov Bot commented May 1, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.73%. Comparing base (632f788) to head (3786a21).
⚠️ Report is 6 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff              @@
##             master     #491      +/-   ##
============================================
+ Coverage     90.71%   90.73%   +0.01%     
- Complexity     2905     2912       +7     
============================================
  Files           249      252       +3     
  Lines          7655     7670      +15     
============================================
+ Hits           6944     6959      +15     
  Misses          711      711              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@armanist armanist added enhancement New feature or request refactoring labels May 1, 2026
@armanist armanist added this to the 3.0.0 milestone May 1, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Tracer/ErrorHandler.php (1)

115-126: ⚠️ Potential issue | 🟠 Major

Explicitly set HTTP 500 status in the fallback path.

Line 125 calls response()->html($errorPage) without setting a status code, which defaults to 200 OK. The emergency fallback should explicitly return HTTP 500.

Pass the status code as the second parameter: response()->html($errorPage, StatusCode::INTERNAL_SERVER_ERROR) or chain response()->setStatusCode(500)->html($errorPage).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Tracer/ErrorHandler.php` around lines 115 - 126, The fallback path
currently renders the error page with response()->html($errorPage) which leaves
the status as 200; update the emergency catch/fallback in
Tracer/ErrorHandler.php so the response uses HTTP 500—e.g. call
response()->html($errorPage, StatusCode::INTERNAL_SERVER_ERROR) or chain
response()->setStatusCode(500)->html($errorPage) before response()->send(); keep
the existing log via $this->logError($throwable, $errorType) and is_debug_mode()
logic intact.
🧹 Nitpick comments (1)
tests/Unit/Tracer/ErrorHandlerTest.php (1)

159-175: ⚡ Quick win

Strengthen fallback test to assert HTTP contract, not only body text.

This test currently validates content only. Please also assert the fallback is returned as a 500 plain-text response so the acceptance criterion is regression-proof.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/Unit/Tracer/ErrorHandlerTest.php` around lines 159 - 175, Update
testHandleWebExceptionFallsBackWhenRendererThrows to assert the full HTTP
fallback contract: after invoking ErrorHandler::handleWebException (via the
test's invokePrivateMethodOn on $handler), in addition to asserting
response()->getContent() equals 'Internal Server Error', also assert
response()->getStatusCode() returns 500 and
response()->headers->get('Content-Type') (or
response()->headers->get('content-type')) equals a plain-text MIME like
'text/plain' (or starts with 'text/plain') so the test verifies status, body and
content-type when WebExceptionRenderer::render throws.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/Tracer/ErrorHandler.php`:
- Around line 115-126: The fallback path currently renders the error page with
response()->html($errorPage) which leaves the status as 200; update the
emergency catch/fallback in Tracer/ErrorHandler.php so the response uses HTTP
500—e.g. call response()->html($errorPage, StatusCode::INTERNAL_SERVER_ERROR) or
chain response()->setStatusCode(500)->html($errorPage) before
response()->send(); keep the existing log via $this->logError($throwable,
$errorType) and is_debug_mode() logic intact.

---

Nitpick comments:
In `@tests/Unit/Tracer/ErrorHandlerTest.php`:
- Around line 159-175: Update testHandleWebExceptionFallsBackWhenRendererThrows
to assert the full HTTP fallback contract: after invoking
ErrorHandler::handleWebException (via the test's invokePrivateMethodOn on
$handler), in addition to asserting response()->getContent() equals 'Internal
Server Error', also assert response()->getStatusCode() returns 500 and
response()->headers->get('Content-Type') (or
response()->headers->get('content-type')) equals a plain-text MIME like
'text/plain' (or starts with 'text/plain') so the test verifies status, body and
content-type when WebExceptionRenderer::render throws.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6bf79040-3b9c-4f92-88e5-3a24a70b162f

📥 Commits

Reviewing files that changed from the base of the PR and between 632f788 and d0d1bb1.

📒 Files selected for processing (8)
  • src/Tracer/ErrorHandler.php
  • src/Tracer/ExceptionSeverityResolver.php
  • src/Tracer/StackTraceFormatter.php
  • src/Tracer/WebExceptionRenderer.php
  • tests/Unit/Tracer/ErrorHandlerTest.php
  • tests/Unit/Tracer/ExceptionSeverityResolverTest.php
  • tests/Unit/Tracer/StackTraceFormatterTest.php
  • tests/Unit/Tracer/WebExceptionRendererTest.php

Comment thread tests/Unit/Tracer/ErrorHandlerTest.php Outdated
Comment thread tests/Unit/Tracer/StackTraceFormatterTest.php Outdated
@armanist
armanist requested a review from andrey-smaelov May 1, 2026 13:42

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Tracer/ErrorHandler.php`:
- Around line 112-123: handleWebException currently calls
$this->webExceptionRenderer->render($throwable) and response()->send() directly,
which can themselves throw and prevent a guaranteed error response; wrap the
render + response()->html(...) and response()->send() calls in a
try/catch(Throwable $e) block inside handleWebException so that if rendering
fails you log the rendering failure (via $this->logError or similar) and then
emit a plain-text 500 response (response()->text("Internal Server Error",
StatusCode::INTERNAL_SERVER_ERROR) or equivalent) and call response()->send();
keep the existing logging of the original $throwable (the existing
$this->logError($throwable, $errorType) call) intact and only suppress
additional debug rendering when is_debug_mode() is false.

In `@tests/Unit/Tracer/ErrorHandlerTest.php`:
- Around line 158-177: The test currently expects the WebExceptionRenderer's
thrown Exception to propagate, but the ErrorHandler::handleWebException must
instead fall back to emitting a minimal plain-text 500 response; update
testHandleWebExceptionPropagatesRendererFailure to remove the
expectException/expectExceptionMessage calls and instead call
$this->invokePrivateMethod($handler, 'handleWebException', [new
Exception('boom')]) inside the ob_start/try block, capture the output via $body
= ob_get_clean() and assert that no exception was thrown and that $body equals
(or contains) the minimal plain-text fallback message and that the handler
produced a 500 response outcome (i.e. assert fallback content and 500 status
semantics) referencing the test method name and ErrorHandler::handleWebException
and WebExceptionRenderer::render to locate the code to change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c7eb88f5-8e22-415a-9f33-ac694f873b4d

📥 Commits

Reviewing files that changed from the base of the PR and between d0d1bb1 and b12c13b.

📒 Files selected for processing (5)
  • src/Tracer/ErrorHandler.php
  • tests/Unit/AppTestCase.php
  • tests/Unit/Tracer/ErrorHandlerTest.php
  • tests/Unit/Tracer/StackTraceFormatterTest.php
  • tests/Unit/Tracer/WebExceptionRendererTest.php
✅ Files skipped from review due to trivial changes (1)
  • tests/Unit/Tracer/WebExceptionRendererTest.php

Comment thread src/Tracer/ErrorHandler.php Outdated
Comment thread tests/Unit/Tracer/ErrorHandlerTest.php Outdated
@armanist
armanist merged commit 35b67fa into quantum-php:master May 1, 2026
5 checks passed
@armanist
armanist deleted the feature/490-errorhandler-refactor branch May 1, 2026 14:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor Tracer\ErrorHandler for separation of concerns and safer fallback behavior

2 participants