Cover photo for post Scalar type hints in PHP

Scalar type hints in PHP

Ilia recently brought up the topic of scalar type hints again. I would love scalar type hints, but a sensible implementation is not easy. I summarize some approaches in this post and talk about the problems they raise.

Requirements for scalar type hints

Different users of PHP have different requirements regarding type hints. In following I will try to categorize these needs and ideas.

Strongly typed languages

People coming from strongly typed languages (such as Java and friends) would want that a type hint really ensures a given value is of a certain type:

<?php function foo( int $bar ) { return $bar + 1; } // Working, result 24 foo( 23 ); // Error foo( "23" ); // Error foo( "bar" ); ?>

This is the most obvious behaviour, if you come from a a strongly typed language. It is clear, straight forward and intuitive if you are used to strong typing. The drawback here is, that PHP is not strongly typed, by intention. It is completly valid to juggle with types like this:

<?php $foo = "23" + 42 + 23.42; ?>

The result is of type float and has the value 88.42.

The PHP way

The original PHP developer would expect a different behavior. He knows that scalar types can all be cast around and that PHP has somewhat intelligent knowledge about type casting:

<?php function foo( int $bar ) { return $bar + 1; } // Working, result 24 foo( 23 ); // Working, result 24 foo( "23" ); // Working, result 1 foo( "bar" ); ?>

While this would be feasible for PHP, since it reflects the typical behavior of variables, its still against the idea of type hints. You could pass any scalar type to the function above, no matter if the resulting cast is sensible or not.

This basically makes the type hint above useless, you could also just introduce a scalar type hint to ensure that the received value is not an object/array/resource. Also I nice addition, IMO.

Potential solutions

Ilia suggested the first solutions presented in this section, the others are just dumps of my brain about this issue.

The numeric way

Ilia now suggests an addition to the strong way of type hinting, by introducing additional numeric and scalar type hints. As I understood his post, the behavior would then be as follows:

<?php function foo( int $bar ) { return $bar + 1; } function bar( numeric $baz ) { return $baz + 1; } // Working, result 24 foo( 23 ); // Working, result 24 bar( 23 ); // Error foo( "23" ); // Working, result 24 bar( "23" ); // Error foo( "bar" ); // Error bar( "bar" ); // Error foo( 42.23 ); // Working, result 43.23 bar( 42.23 ); ?>

Well, this obviously solves the problems stated in the previous section. However, you still get a problem, if you want to ensure working with an integer value in the bar() function. This one also accepts floats and strings containing floats, because of:

<?php var_dump( is_numeric( 23.42 ), is_numeric( "23.42" ) ); // bool(true) // bool(true) ?>

The casting way

The basic problem with Ilias approach is, that you can not determine receiving an integer value (in whatever format):

<?php function foo ( numeric $bar ) { return $bar + 1; } // Working, result 24 foo( 23 ); // Working, result 24 foo( "23" ); // Working, result 43.23 foo( 42.23 ); // Error foo( "bar" ); ?>

The first calls bevavior is intentionally, as well as the third one. However, the second one breaks the idea of working in a somewhat type safe environment, since foo() may now also return float instead of integer.

One idea here could be to enable casting in type hints:

<?php function foo (scalar (int) $bar ) { return $bar + 1; } // Working, result 24 foo( 23 ); // Working, result 24 foo( "23" ); // Working, result 43 foo( 42.23 ); // Working, result 1 foo( "bar" ); ?>

Instead of returning a float, this method will always return an int. However, this variant has almost the same problem as just requiring scalar, since you can still pass string values to it, that do not contain numbers at all:

<?php var_dump( (int) 42.23, (int) "bar" ); // int(42) // int(0) ?>

The checking way

Another idea could be to add information to the scalar type hint, which type is actually expected. Doing this, PHP could become a bit more intelligent in casting types, to ensure a correct type is received by a funtion:

<?php function foo( scalar(int) $bar ) { return $bar + 1; } // Working, result 24 foo( 23 ); // Working, result 24 foo( "23" ); // Error foo( 42.23 ); // Error foo( "bar" ); ?>

In this case I would expect the type hint to check if the given scalar can safely be interpreted as an int. 42.23 and "bar" are obviously not interpretable as int, while "23" is. The actual value of $bar should be an int in this case. Although keeping the actually passed value would make no difference here, since PHP would cast it on the first use as an int.

Conclusion

PHP really has a paradigm conflict here: On the one hand, we are usually open for most things users want to do (look at goto) and add them where needed. This would mean to basically add all variants above to let people do things the way they want. On the other hand, adding strong type hints and scalar (with and without additional info) and numeric and casts results in endless confusion and violates the KISS principle PHP has always been based on.

This basically means that there is no optimal solution that satisfies everyones needs, as so often.

My personal preference would be to go with Ilias solution. I generally favor that people check if values are correct before they submit them to my code, so I can pretty much live with strong type hints. Although I want int to be valid is float and any scalar type to be a valid string. ;) Having numeric is a nice addition and I have been looking for scalar for a long time now, to just ensure neither an object nor an array is passed.

One big problem I still have with type hints is the error handling. Type hints currently raise an E_RECOVERABLE_ERROR which is almost a fatal. There is no "nice" solution to catch these in your code. This should not make any problems if you only deal with your own code, but when using library code, its hard to catch such errors coming from it. The hackish way of switching from error to exception looks like this:

<?php set_error_handler( function ( $errno, $errstr /* ... */ ) { throw new Exception( $errstr ); }, E_RECOVERABLE_ERROR ); function foo( array $bar ) { return $bar[] = true; } foo( "bar" ); ?>

Note that this is just a basic sketch and a real implementation should be worked out some more. This basically works, but is not nice. So, my wish for PHP 6: Please make type hints throw an exception. Thanks.

Comments

A quick word about that part: "The original PHP developer would expect a different behavior."

I've been using PHP since some 3.0.x (at a time where mysql_query('... WHERE user_id = ' . $HTTP_GET_VARS['user_id']) seemed perfectly normal to me) and I don't think I've ever used a strongly-typed language, but only the "strict" type-hints make sense to me. If I declare a function as "function foo(int $bar)" I really expect an int, not a string. If it's not an int, then something must be wrong somewhere.

The usual argument is that "123" should be interpreted as 123, but the real question is where did that string come from? mysqlnd (the new default mysql lib if I'm not wrong?) casts numeric values as integers or floats, and so does filter_input(), so if a function receives a string in place of an int I guess something went wrong so I want to stop the execution and log that error (or throw an exception, as you suggested.)

Chris D at 2009-07-02

How about this one: http://www.maxhorvath.com/2009/05/type-hints-for-scalar-values-phptypesafe-now-compatible-with-current-php-53.html

Foo at 2009-07-02

Strict type hints only.

The numeric hint is just a strange half-way behaviour for people who want to exclude some erroneous inputs, but don't dare to be really strict...

Just use the old syntax (no type hint) if you want the old behaviour. KISS.

Maarten at 2009-07-02

I believe the only sensible way to have type hints is to have strict ones.

If I say function foo(int $bar) i want $bar to be an int, not a string that looks like an int. If type hints is implemented otherwise I don't think it will have that much effect at all...

Christer at 2009-07-02

If someone doesn't want strict type hints then they could just not use them. Someone who wants the flexibility of a weakly typed language surely won't be interested in type hinting so why make type hinting that they will like, as long as type hinting remains optional then have string hinting for those who want it and none for those who want to use weak typing.

Miller at 2009-07-02

What about objects that can be casted to integers using __toString()? Would they go through function(int $a) {}?

Christian Weiske at 2009-07-02

@Christian: No it wouldn't work since __toString() will and and should always return a value of type string. If you need to cast an object to an int just add a toInt() method.

David at 2009-07-02

I would really like type hints for primitive data types, but when then as strict as in any static typed language. In some special cases you can always skip hints for those methods that require PHP's dynamic behavior.

But I can also abandon on this feature, because a strict developer can already develop type save with PHP.

Manuel Pichler at 2009-07-02

Ok, it doesn't seem so complicated to me. The kind of type hints we currently have (for classes) in fact is a type enforcement. So if we extend it to scalars it would be only logical to do it in the same way.

I can't see why any more users would expect scalar type hints to be non-strict when the only experience they could have had so far with hints was strict.

Who exactly would expect type hints not to be strict, when the only type hints they could have used so far were strict?

Boris Erdmann at 2009-07-03

I think having float hinting accept integers as well is all that's required. No need for a separate 'numeric' type hint IMO. Just 'float' and 'int'.

What I really want is return type hinting too. Then PHP reflection can be powerful without the need for docblocks. Handy for exposing classes as web services (AMF,SOAP,JSON-RPC etc.)

I'd take these features over namespaces anyday.

Stephen Beattie at 2009-07-04

i agree with you stephen

ambreen tariq at 2009-09-05