Skip to content

TPropertyValue: a value without a string form passes through coercion, and ensureString() throws for it (#1497) - #1509

Merged
ctrlaltca merged 3 commits into
pradosoft:masterfrom
belisoful:coerce-array-to-union
Nov 9, 2026
Merged

ctrlaltca merged 3 commits into
pradosoft:masterfrom
belisoful:coerce-array-to-union

Conversation

@belisoful

Copy link
Copy Markdown
Member

Part of #1497. Two commits, to be reviewed separately.

1. Unions: a value no member can hold passes through unchanged

TPropertyValue::coerceToType(['a'], int|string) raised TPhpErrorException: Array to string conversion, and an object without __toString() raised PHP's \Error. Step 4 of _coerceUnionType() returned an array only for a union with an array member and otherwise fell through to a string cast.

Such a value is now returned unchanged, the miss style _coerceToClass() already documents, so a typed setter raises PHP's own TypeError ("must be of type string|int, array given") and the property stays unassigned. array|string and iterable|string keep passing an array through, and nullable unions, ICoercible, enums, Stringable objects, and TJavaScriptLiteral behave as before. Two cases that threw now succeed because the setter accepts the value: an object to object|int, and a callable array to callable|string.

2. Single string: ensureString() throws TInvalidDataTypeException

The same defect existed for string and ?string through ensureString(), whose (string) cast raised the warning, or returned 'Array' when warnings were masked.

  • coerceToType() to string or ?string passes an array or an object without __toString() through, as in commit 1.
  • ensureString() called directly throws TInvalidDataTypeException with the new propertyvalue_invalid_string_type, "Value of type '{0}' cannot be converted to a string." Most setters are untyped and store ensureString($value), so pass-through there would store the array. It throws the same way with warnings reported or masked.
  • Everything else ensureString() does is unchanged: bool, NAN, null, numbers, Stringable objects, TJavaScriptLiteral.

Behavior to review:

  • With warnings masked, ensureString($array) returned 'Array'; it now throws.
  • TException::__construct() converts each message argument with ensureString(). new TFooException('code', $array) throws TInvalidDataTypeException; with warnings reported it already threw TPhpErrorException. Rendering such an argument as its type is a possible follow-up.
  • No call in framework/ passes an array or such an object to ensureString() on purpose, and no existing test does. Sites where input can be an array, such as a POST field field[] in TWebTemplate::loadPostData(), now get the clear exception where they got the warning exception.

Regression tests for both commits; they pass on PHP 8.2 through 8.5.

…ough unchanged (pradosoft#1497)

An array given to a union without an array or iterable member, and an object without __toString() that no member accepts, reach the setter unchanged, so a typed setter raises PHP's TypeError.
… value without a string form (pradosoft#1497)

An array or an object without __toString() throws propertyvalue_invalid_string_type, with warnings reported or masked. coerceToType() passes such a value through unchanged for string and ?string, so a typed setter raises PHP's TypeError.
@belisoful belisoful mentioned this pull request Nov 9, 2026
12 of 69 tasks
@ctrlaltca
ctrlaltca merged commit 3ad21fd into pradosoft:master Nov 9, 2026
16 of 17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants