This is an old revision of the document!

Request for Comments: Scalar Type Hinting With Casts


Currently, PHP has no way to provide type hinting for function parameters which are not classes or arrays. This is on often requested feature that has been discussed on the internals list many many times. This RFC discusses a new implementation of this feature that attempts to stay close to php's type shifting roots, and attempts to mirror zend_parse_parameters as much as possible.


This RFC discusses a method of adding scalar type hints to PHP while attempting to embrace the dynamic nature of PHP variables. This means that passing a type that does not exactly match the hinted type will cause a cast to happen. This cast will only succeed if the argument can be cleanly converted to the requested type. If it cannot be converted without significant data-loss, an E_RECOVERABLE_ERROR will be raised.

For consistency, this patch attempts to largely follow zend_parse_parameters() for the validation rules, but disallows lossy conversion from float to int (1.5 -> int generates an error) and non-well-formed numeric values for float or int ('123abc' is an error).


Engine Changes

The current implementation introduces four new reserved words: int, float, bool and string. These were not previously reserved, because casting is a special case in the lexer.

If this causes a problem, it would be possible to revert to the previous implementation, where the parser still detects the type hints as object type hints and the compiler (zend_compile.c) then detects the exact value for the type hint, changing the stored hint from IS_OBJECT to the proper type (freeing the string).


Four new type hints are introduced with this patch:

  • int - Matching integers only
  • float - Matching floating point numbers
  • bool - Matching boolean parameters only
  • string - Matching strings only

Conversion Rules

Conversion is allowed only if data-loss does not happen. There are a few exceptions (objects using __toString, strings containing leading numerics, etc). Here's a table of examples.

  • fail indicates an E_RECOVERABLE_ERROR
  • pass indicates no error and a conversion
  • notice indicates an E_NOTICE and a conversion
value string float int boolean‡ array
true (boolean) pass pass pass pass fail
false (boolean) pass pass pass pass fail
0 (integer) pass pass pass pass fail
1 (integer) pass pass pass pass fail
12 (integer) pass pass pass pass fail
12 (double) pass pass pass pass fail
12.34 (double) pass pass fail pass fail
'true' (string) pass fail fail pass fail
'false' (string) pass fail fail pass fail
'0' (string) pass pass pass pass fail
'1' (string) pass pass pass pass fail
'12' (string) pass pass pass pass fail
'12abc' (string) pass fail fail pass fail
'12.0' (string) pass pass pass pass fail
'12.34' (string) pass pass fail pass fail
'foo' (string) pass fail fail pass fail
array () (array) fail fail fail fail pass
array (0 => 12) (array) fail fail fail fail pass
NULL (NULL) pass pass pass pass fail
'' (string) pass fail fail pass fail
1 (resource) fail fail fail fail fail
StdClass fail fail* fail* fail† fail
implementing __toString pass fail* fail* fail† fail

*actually notice in patch as it stands due to behaviour of default object casting handler

†actually pass in patch as it stands due to behaviour of default object casting handler

‡likely subject to change, see Booleans section below

It's important to note that passing `12.5` as a float or string to a int type hint will presently fail, since data-loss would occur (this diverges from zend_parse_parameters which would truncate the value).


If a provided hint does not match at all (“foo” passed to an int hint), an E_RECOVERABLE_ERROR is raised. This includes non-well-formed numerics passed to an int or float hinted parameter, unlike zend_parse_parameters which would simply raise an E_NOTICE.


Any value can be entered as a default. Presently even array() is allowable for an int type hint. The default is converted at run-time when it is accessed.

This can lead to odd bugs, so in the future it would be good to validate the default in zend_compile.c (casting it where appropriate, checking for a valid cast).

NULL defaults (nullable hints)

Normally, scalar typehints are non-nullable and NULL is casted like any other value. However, if the default value for a scalar typehint is NULL, the typehint is considered nullable. This means that while values of other types would be casted, NULL will not, e.g.:

function foo(int $a) { var_dump($a); }
function bar(int $a = NULL) { var_dump($a); }
foo(NULL); // int(0)
bar(NULL); // NULL


The current implementation treats references like any other value. If it casts, the referenced value is casted.

New APIs

This current proposal adds a series of conversion functions to the core:

  • int convert_to_{type}_safe(zval **ptr) - Convert the zval to {type}. Return value indicates if conversion was “clean”. (FAILURE indicates unclean conversion)
  • int convert_to_{type}_safe_ex(zval **ptr) - Separate zval if not a reference, and convert to {type}. Return indicates clean conversion (FAILURE indicates unclean conversion).

These functions pairs exist for long, double, string and boolean.

New Methods

For consistency, the following new methods have been added to ReflectionParameter

  • isInt() - boolean to determine if parameter is type-hinted as an integer.
  • isFloat() - boolean to determine if parameter is type-hinted as a float.
  • isBool() - boolean to determine if parameter is type-hinted as a boolean.
  • isString() - boolean to determine if parameter is type-hinted as a string.


The modifications necessary to implement this feature exist on the scalar_type_hints branch of Andrea's GitHub fork (forked from the branch on ircmaxell's GitHub fork). It is still a work-in-progress, and should be considered unstable at this time.

Possible Changes

Float to Int Casting Rules

At present, the cast from float to int results in an error if the int doesn't exactly represent the float (satisfying a double cast: val = (double) (long) val). And a cast from an int to a float follows the same semantics (as on 64 bit platforms PHP_INT_MAX is not exactly representable by a float).

This could be relaxed for semi-representable values. So 1.5 could be allowed for an int parameter (casted to 1). But float(99999999999999999999) would not, because it would lose a lot of information in the transfer (would be casted to PHP_INT_MAX).

I believe the current behavior (error on non-exactly-representable) is the correct one. However, this could be changed to an E_NOTICE instead indicating that partial data was lost.

Warning On Data Loss

We could also change the E_RECOVERABLE_ERROR on data-loss to an E_WARNING. That would allow data-loss to continue. The value passed in would still be cast according to the normal casting rules. So passing “foo” to an int parameter would result in int(1) and an E_WARNING.

Handling of StdClass and other objects

While an E_RECOVERABLE_ERROR result when passing a StdClass (and other objects with the default object handlers) to parameters hinted as int, float or bool would be desirable, the patch as it stands does not do this. Instead, for the int and float cases, an E_NOTICE is emitted and the result back is 1, and in the bool case, no error at all happens and the result back is true. To make this yield E_RECOVERABLE_ERROR would require detecting the default object handler (an ugly hack which also wouldn't make the behaviour sensible for non-defaults), or changing the behaviour and/or semantics of the object handler for casting, neither of which are particularly desirable. By keeping the current behaviour in the patch, we are consistent with casting and zend_parse_parameters. Furthermore, it could be argued that since objects are truthy, casting to bool without complaint here might not be a bad thing.


Given that StdClass casts without error to bool in the patch as it stands, and there's no nice way of changing that (see previous section), should we just make all truthy values (including array(non-empty) and resource) cast to true without error, and all falsey values (including array() and empty string) cast to false without error?

While int, float and string only allow lossless casting (with the exception of objects), bool’s behaviour at the moment is quite different. The current behaviour leaves much to be desired, and there are some other options available.

One option is simply to forget about being lossless and make the bool type hint accept any value, meaning any truthy value or any falsey value would yield what is expected without error. This would ensure that if someone has passed in a non-boolean truthy/falsey value to your function, it’ll be handled correctly. It would mean all your bit hacks ($foo & FLAG etc.) would work and anything you got from $_GET (e.g. ?foobar=1). However, this is unlikely to catch bugs in code, because literally any PHP value would work. For that reason, this may not be the way forward.

Another option is go completely strict and allow only boolean values, failing everything else. This would be unlike the int, float and string hints, which are flexible and cast, but would be more helpful for catching bugs. However, not casting at all isn’t very “PHP-like”, and forcing people to manually cast with (bool) might not be ideal. If we were to go for this one, we could also accept objects casting to bool (which the default handler does), because otherwise we'd be stopping extension developers from making bool-like objects if they so pleased.

The final option this section considers is a limited set of values. TRUE, FALSE and NULL would be accepted, along with the integer and float values 1 and 0 (which are the int/float values TRUE and FALSE cast to, respectively), ‘1’ and the empty string (which are the string values TRUE and FALSE cast to), and ‘0’ (which (string)(int)FALSE would give you), along with objects casting to boolean. This is something of a compromise between the first two proposals.

Both the author of this RFC (Anthony) and the current maintainer (Andrea) are yet to settle on one specific option.

Handling of "123abc" for int and float

This has been changed to E_RECOVERABLE_ERROR, but should it perhaps be something softer, like E_NOTICE or E_WARNING?

With it as E_RECOVERABLE_ERROR, it can be considered to “fail” the typehint and hence the int and float typehints are lossless.


Note that these reflect the intended output and not what the patch at present actually does. For that, see the patch's own test cases or the table above.

Integer Hints

function foo(int $a) {
foo(1); // int(1)
foo("1"); // int(1)
foo(1.0); // int(1)
foo(999999999999999999999999999999999999); // E_RECOVERABLE_ERROR (since it's not exactly representable by an int)
foo(array()); // E_RECOVERABLE_ERROR
foo(new StdClass); // E_RECOVERABLE_ERROR

Float Hints

function foo(float $a) {
foo(1); // float(1)
foo("1"); // float(1)
foo(1.0); // float(1)
foo(1.5); // float(1.5)
foo(array()); // E_RECOVERABLE_ERROR
foo(new StdClass); // E_RECOVERABLE_ERROR

String Hints

function foo(string $a) {
foo(1); // string "1"
foo("1"); // string "1"
foo(1.0); // string "1"
foo("1a"); // string "1a"
foo("a"); // string "a"
foo(1.5); // string "1.5"
foo(array()); // E_RECOVERABLE_ERROR
foo(new StdClass); // E_RECOVERABLE_ERROR

Boolean Hints

function foo(bool $a) {
foo(1); // bool(true)
foo("1"); // bool(true)
foo(1.0); // bool(true)
foo(0); // bool(false)
foo("0"); // bool(false)
foo("1a"); // bool(true)
foo("a"); // bool(true)
foo(1.5); // bool(true)
foo(array()); // E_RECOVERABLE_ERROR
foo(new StdClass); // E_RECOVERABLE_ERROR

More Information

Prior RFCs


  • 0.1 - Initial Draft
  • 0.1.1 - Takeover by Andrea; notes on StdClass behaviour
  • 0.1.2 - Renamed boolean to bool, noted reserved words
  • 0.1.3 - E_RECOVERABLE_ERROR for “1a” as int/float
  • 0.1.4 - Removed resource typehint
  • 0.1.5 - Note on NULL default values
rfc/scalar_type_hinting_with_cast.1405351124.txt.gz · Last modified: 2017/09/22 13:28 (external edit)