Give CommonProblemDetailsExceptionTrait typed properties with safe defaults#82
Open
iloveyouinc wants to merge 1 commit into
Open
Conversation
…faults
An exception composing the trait but constructed without setting every
property (e.g. a plain `new CustomException(...)` in a consumer's own
constructor, never routed through a ::create() static factory) currently
holds null in the untyped private props, so the typed getters explode:
// TypeError: Mezzio\ProblemDetails\Exception\...::getTitle():
// Return value must be of type string, null returned
(new class extends Exception implements ProblemDetailsExceptionInterface {
use CommonProblemDetailsExceptionTrait;
})->getTitle();
In practice this turns an intended 4xx problem-details response into an
unhandled 500 inside ProblemDetailsResponseFactory.
Typing the properties with safe defaults (500 / '' / []) makes any composed
instance well-formed: '' for title/type is exactly the sentinel
ProblemDetailsResponseFactory already treats as 'derive from status', and
the properties are private to the trait, so only trait code and composing
classes write them — no BC surface beyond the previously-fatal path now
returning defaults. Includes a regression test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Member
This is a library that ships a trait. All private properties of the trait are effectively part of the public API, so this is a BC break for anyone that uses it: https://3v4l.org/Zr2aL |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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
CommonProblemDetailsExceptionTraitdeclares its private properties untyped with no defaults, while exposing typed getters (getTitle(): string,getType(): string, …). The trait's docblock says composing classes are required to set the properties — but when a composing class constructs an instance without setting all of them (a very easy thing to do in a consumer's own__construct, never routed through a::create()-style factory), the contract violation surfaces as a confusingTypeErrordeep inside response generation:In practice,
ProblemDetailsResponseFactory::createResponseFromThrowable()calls these getters, so an exception that was meant to become a clean RFC 7807 4xx response instead becomes an unhandled 500 — the failure appears in the response pipeline, far from the actual mistake.Fix
Type the properties and give them safe defaults:
''fortitle/typeis exactly the sentinelProblemDetailsResponseFactoryalready treats as "derive from status" (createResponse()fills the title from the status map and builds thehttpstatus.estype URI), so a partially-initialized exception now renders as a well-formed problem-details response instead of fataling. The trait docblock is updated to document the defaults.BC considerations
privateto the trait — only trait code and composing classes write them, so adding native types cannot break external writers.TypeErrornow returns the documented defaults.::create()users) are byte-for-byte unaffected.Testing
Adds a regression test constructing a composing class that sets nothing and asserting all getters +
toArray()return the defaults.composer test(82 tests / 451 assertions),composer cs-check, andcomposer static-analysisall pass locally on PHP 8.4.Found while maintaining a downstream fork where ~200
new SomeProblemException($detail)call sites each needed a hand-written constructor purely to avoid thisTypeError; this change makes the trait fail-safe by construction.🤖 Generated with Claude Code