Skip to content

fix(errors-advanced): report warnings as non-fatal instead of rendering a page - #682

Open
etienne-scherrer wants to merge 1 commit into
marko-php:developfrom
etienne-scherrer:fix/errors-advanced-nonfatal-warnings
Open

etienne-scherrer wants to merge 1 commit into
marko-php:developfrom
etienne-scherrer:fix/errors-advanced-nonfatal-warnings

Conversation

@etienne-scherrer

Copy link
Copy Markdown
Contributor

Problem

With errors-advanced, a PHP warning renders a full error page, and then the code that warned keeps running. AdvancedErrorHandler::handleError() sends E_WARNING down the fatal path: handleException() clears the output buffers, sets status 500 and Content-Type: text/html, echoes the page, and handleError() returns true, so PHP resumes.

When the controller's response is sent afterwards, Response::sendHeaders() skips every header because headers are already out, but Response::send() still writes the body. The response's own status, Content-Type, security headers, Location and cookies are lost, and its body is appended to the HTML error page, so a JSON or plain text body that reflects request input is then served as HTML. In PHP 8 a missing array key is a warning, so a request can trigger this on purpose.

A controller that checks a warning-prone call and redirects shows it:

$image = imagecreatefromstring('not an image'); // E_WARNING, returns false

if ($image === false) {
    return Response::redirect('/upload?invalid=1');
}

The visitor gets a 500 page, the redirect's Location and cookies (the session cookie included) are silently dropped, and the controller ran to the end anyway.

errors-simple does not have this problem: since b6bd408 it treats Severity::Warning as non-fatal and reports it through error_log in the web SAPI, as errors-simple.md documents.

Change

  • AdvancedErrorHandler::handleError() treats Severity::Warning as non-fatal, like SimpleErrorHandler.
  • handleNonFatal() writes [Warning] message in file:line to error_log in the web SAPI instead of returning silently, through a writeToErrorLog() hook as in errors-simple. The CLI keeps writing to STDERR.
  • errors-advanced.md states the non-fatal policy, matching errors-simple.md.

Warnings stay loud, in the log instead of the response. Fatal errors and uncaught exceptions still render the page.

Tests

  • A warning in the CLI is reported as non-fatal, keeps prior output, clears no buffers and sets no status. Replaces "does not swallow warnings", which pinned the page rendering.
  • A warning in the web SAPI goes to error_log with no output, no status and no headers (failed before: the page was rendered).
  • The module boot test now expects the booted handler to report E_USER_WARNING as non-fatal.

Each of the three fails when warnings are made fatal again.

pest --parallel in php:8.5-cli with the suite's extensions: 14173 passed, 449 skipped, 15 failed; the 15 (GdImageProcessorTest, GdImageProcessorLimitsTest, IntegrationVerificationTest, RepoManagementScriptsTest) fail the same way on a clean develop in that container. phpcs, php-cs-fixer --dry-run, docs:lint and phpstan: clean.

Not in this PR: Response::json() could add JSON_HEX_TAG | JSON_HEX_AMP, so a JSON body can never be read as markup even if headers are lost some other way. Happy to send that separately.

@github-actions github-actions Bot added the bug Something isn't working label Nov 3, 2026
@etienne-scherrer
etienne-scherrer marked this pull request as ready for review November 3, 2026 22:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant