Translatable error message templates
An error's MESSAGE can now contain {marker} placeholders, so a client can translate the source text and then fill in the values. Every error body documents both:
class PlanLimitError extends ApiError
{
public const CODE = 'plan_limit_reached';
public const MESSAGE = 'Your plan allows up to {limit} projects.';
public const STATUS = 402;
public function __construct(
#[Example(5)]
public int $limit,
) {}
}"error": {
"message": "Your plan allows up to 5 projects.",
"code": "plan_limit_reached",
"template": "Your plan allows up to {limit} projects.",
"params": { "limit": 5 },
"details": { "limit": 5 }
}templateisMESSAGEverbatim and is present on every error body.paramshas one property per marker, documented from the same-named public property: its type,#[Description]and#[Example]. It is required whenMESSAGEhas markers and omitted when it has none.- The
messageexample fills in each marker that has an#[Example]value. - A marker must name a public property of the class. Generation fails otherwise, naming the class and the marker.
- Rename or omit either field with
errors.error_fields.templateanderrors.error_fields.params;nullomits it.
Fix: implied errors for annotations on helper methods
When an @OA annotation sits on a helper method rather than the route's action, implied-error rules, the Data-parameter validation check and model-binding detection now inspect the matched route's real action. #[Throws] is still read from both methods.
Behavior changes for apps using error documentation
- Every error body now includes a required
templatefield. - A
{marker}inMESSAGEthat names no public property now fails generation. templateandparamsare reserved error-object names, so an#[EnvelopeField]with either name now fails generation.
No effect otherwise — apps without errors.base_class configured are unaffected.
See the README for full details.