[9.x] Do not render exceptions in streamed download #40701
Merged
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.
Problem
When streaming a download using
response()->streamDownload(...), if an exception is thrown in the middle of the stream, the HTML of the error ends up in the downloaded file.To test it, paste this code block into your
routes/web.phpfile:When you visit that route, it'll download the CSV file. Open it, and you'll see the HTML of the message directly in the file:
This is clearly not what anyone wants. We want the error to be logged (AKA reported), but not rendered.
Solution
Wrap the provided callback in our own closure. Within that closure, we can wrap the original callback in a
try/catchblock, and return a custom exception that indicates that it was thrown from within a stream. Then we'll pass the wrapped closure to theStreamedResponseconstructor:Note about mapping inner exceptions
For logging purposes (AKA reporting) we're not interested in the special
StreamedResponseException. We want to log the original exception as is.In C# and in JS, there's an official way to wrap exceptions, and to then get back to the original exception. C# uses the
InnerExceptionproperty, and JS uses thecauseproperty.Alas, PHP does not have a built-in mechanism for this. Instead, what this PR does is add a new convention: just like we already respect
renderandreportmethods directly on the exception class, we'll now also respect agetInnerExceptionmethod, and use that in themapExceptionlogic in theHandler. This way, we can map theStreamedResponseExceptionback to the original exception for logging purposes.