Fix chained exceptions serializing as empty JSON objects#164
Fix chained exceptions serializing as empty JSON objects#164jamisonbryant wants to merge 3 commits intomixerapi:masterfrom
Conversation
The exception chain collector stored raw Throwable objects in the
viewVars. Since Exception properties are protected and the class
does not implement JsonSerializable, json_encode produced {} for
each entry. Extract class, message, code (and file/line in debug
mode) into plain arrays so the chain is visible in JSON responses.
|
@jamisonbryant the PR description is bigger than the code change (not counting test coverage) ....Can you you give me the tweet version? |
|
Hard for me to tell reading the AI description in here exactly how breaking of a change this is... @jamisonbryant Is your AI agent saying this is only breaking with debug enabled? |
Tweet version (no AI): MixerAPI's exception renderer passes raw Throwable objects to JsonView for chained exceptions, but PHP's Exception class doesn't implement JsonSerializable so they all serialize as |
This comment was marked as outdated.
This comment was marked as outdated.
|
| $exceptions[] = $exceptionData; | ||
| $current = $current->getPrevious(); |
There was a problem hiding this comment.
I'm just brainstorming here and this may be one of or both of a misuse and overkill, but what if a class was created that implements Throwable and JsonSerializable. Wouldn't this become backwards compatible?
There was a problem hiding this comment.
PHP won't let you implement Throwable:
Fatal error: Class Foo cannot implement interface Throwable, extend Exception or Error instead
I got around this by doing exactly that: extending Exception.
There was a problem hiding this comment.
Interesting. I had never tried myself either. Good work. This looks promising I will hopefully have time this week to pull it down and validate things myself.
There was a problem hiding this comment.
Thanks Chris! Any chance you can give me minimal privileges to approve workflows (GitHub Actions) on your repos? So I can at least verify pipelines are green?
I run the tests as I write code but sometimes CI behaves funny so it would be nice to know :)
There was a problem hiding this comment.
I think once you are contributor (i.e. this PR is merged) it will auto-run them. It's just restrictive for first-time contributors.
Replace plain array serialization with a SerializableException class that extends Exception and implements JsonSerializable. This preserves the Throwable contract for event listeners on beforeRender while producing structured JSON output. Also caps exception chain traversal at 10 iterations.
|
I can clean up those style errors lickety split in just a bit. |
- Avoid count() in loop condition (use depth counter) - Add doc comments for __construct and jsonSerialize - Remove space after (int) cast
Closes #163
Summary
The exception chain collector in
MixerApiExceptionRendererstored rawThrowableobjects in theexceptionsviewVar. Since PHP'sExceptionclass has only protected properties and does not implementJsonSerializable,json_encodeproduced{}for each entry. This wraps each exception in aSerializableExceptionthat extendsExceptionand implementsJsonSerializable, preserving theThrowablecontract for event listeners while producing structured JSON output.Major Changes
SerializableExceptionclass that extendsExceptionand implementsJsonSerializable, wrapping each exception in the chain to produce structured JSON while remaining aThrowablefileandlinewhendebugis enabled, consistent with how the renderer already handles trace/origin data for the primary exceptionMinor Changes
CakeExceptionimport fromMixerApiExceptionRenderTest!==) in the chain-walking loopBackwards Compatibility Notes(superseded)This changes the type of theexceptionsviewVar fromThrowable[]toarray[](array of associative arrays). Two considerations:JSON output: Changes from[{}, {}]to[{"class": "...", "message": "...", "code": ...}, ...]. The previous output was a serialization bug (empty objects), so no consumer could have been meaningfully depending on it.Event listeners: Any listener onMixerApi.ExceptionRender.beforeRenderthat accesses$viewVars['exceptions']and calls Throwable methods on the entries (e.g.,$e->getMessage()) would need to update to array access (e.g.,$e['message']). TheErrorDecoratoritself is a passive container and is unaffected.Production mode:debugViewVars()unsets theexceptionskey entirely whendebug = false, so production output is unchanged.Backwards Compatibility Notes (updated)
The
exceptionsviewVar entries are nowSerializableExceptioninstances instead of rawThrowableobjects.SerializableExceptionextendsException(and therefore implementsThrowable), so:MixerApi.ExceptionRender.beforeRenderthat callsThrowablemethods (getMessage(),getCode(),getFile(),getLine()) on chain entries continues to work unchanged. These methods return the original exception's values.[{}, {}]to structured objects withclass,message,code(plusfileandlinein debug mode). The previous output was a serialization bug --json_encodeonThrowableproduces empty objects becauseExceptionproperties are protected.instanceofchecks:$e instanceof \Throwableand$e instanceof \Exceptionstill pass. Checks for specific exception types (e.g.,$e instanceof RuntimeException) would not match, since entries are nowSerializableExceptionwrappers. This is unlikely to affect real consumers.debugViewVars()unsetsexceptionsentirely whendebug = false, so production output is unchanged.Work Remaining
None.
Test Plan
vendor/bin/phpunit plugins/exception-render/tests/-- all 21 tests passvendor/bin/phpunit-- all 102 tests passvendor/bin/phpstan analyse plugins/exception-render/src/-- no errors