PHP 5 Type Hinting
PHP 5 introduces Type Hinting. Functions are now able to force parameters to be objects (by specifying the name of the class in the function prototype) or arrays (since PHP 5.1). However, if NULL is used as the default parameter value, it will be allowed as an argument for any later call.
The following excerpt from the above:
if NULL is used as the default parameter value, it will be allowed as an argument for any later call.
Does the above mean:
if default parameters are to used use with type hinting, it can have only have NULL as the default value.
i.e. the code in code1 is wrong and results in:
Fatal error: Default value for parameters with a class type hint can only be NULL
code1:
function setName ( string $name = "happ") {
...
}
Where as code in code2 is right:
code2:
function setName ( string $name = NULL) {
...
}
Why is this constraint assigned in php?
You can't typehint strings, you can only typehint objects and arrays, so this is incorrect:
function setName ( string $name = "happ") {
(The reason you don't get a compile-time error here is because PHP is interpreting "string" as the name of a class.)
The wording in the docs means that if you do this:
function foo(Foo $arg) {
Then the argument passed to foo() must be an instance of object Foo. But if you do this:
function foo(Foo $arg = null) {
Then the argument passed to foo() can either be an instance of object Foo, or null. Note also that if you do this:
function foo(array $foo = array(1, 2, 3))
Then you can't call foo(null). If you want this functionality, you can do something like this:
function foo(array $foo = null) {
if ($foo === null) {
$foo = array(1, 2, 3);
}
[Edit 1] As of PHP 5.4, you can typehint callable:
function foo(callable $callback) {
call_user_func($callback);
}
[Edit 2] As of PHP 7.0, you can typehint bool, float, int, and string. This makes the code in the question valid syntax. As of PHP 7.1, you can typehint iterable.
Type declarations (also known as type hints in PHP 5) of a string type are supported in PHP 7.
The valid types are:
Class/interface name (>=PHP 5.0.0);
self (>=PHP 5.0.0);
array (>=PHP 5.1.0);
callable (>=PHP 5.4.0);
bool, float, int, string (>=PHP 7.0.0);
iterable - either an array or an instanceof Traversable (>=PHP 7.1.0).
This is a matter of compilation time versus run time values. At compilation only literal values (strings, numbers, booleans and NULL) are allowed. The PHP processor can't know about all the possible classes at this time and so you can't create an instance of an object in the function arguments.
What I'm expecting from the excerpt is that, while normally passing NULL into a type hinted function will throw an Exception/Error. If you set a default as NULL then it won't throw this exception if NULL is passed. I haven't tested it, just what I would expect.
Related
I've been trying to use type hinting more in PHP. Today I was writing a function that takes a boolean with a default parameter and I noticed that a function of the form
function foo(boolean $bar = false) {
var_dump($bar);
}
actually throws a fatal error:
Default value for parameters with a class type hint can only be NULL
While a function of the similar form
function foo(bool $bar = false) {
var_dump($bar);
}
does not. However, both
var_dump((bool) $bar);
var_dump((boolean) $bar);
give the exact same output
:boolean false
Why is this? Is this similar to the wrapper classes in Java?
http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration
Warning
Aliases for the above scalar types are not supported. Instead, they are treated as class or interface names. For example, using boolean as a parameter or return type will require an argument or return value that is an instanceof the class or interface boolean, rather than of type bool:
<?php
function test(boolean $param) {}
test(true);
?>
The above example will output:
Fatal error: Uncaught TypeError: Argument 1 passed to test() must be an instance of boolean, boolean given
So in a nutshell, boolean is an alias for bool, and aliases don't work in type hints.
Use the "real" name: bool
There are no similarity between Type Hinting and Type Casting.
Type hinting is something like that you are telling your function which type should be accepted.
Type casting is to "switching" between types.
The casts allowed are:
(int), (integer) - cast to integer
(bool), (boolean) - cast to boolean
(float), (double), (real) - cast to float
(string) - cast to string
(array) - cast to array
(object) - cast to object
(unset) - cast to NULL (PHP 5)
In php type casting both (bool) and (boolean) are the same.
Given this explanation
Nullable types: Type declarations for parameters and return values can now be marked as nullable by prefixing the type name with a question mark. This signifies that as well as the specified type, NULL can be passed as an argument, or returned as a value, respectively.
https://www.php.net/manual/en/migration71.new-features.php
The following code :
public function test(?int $var) {
}
Means that test() can be called with $var either as int or as null.
And the following code :
public function test(int $var = null) {
}
Means that test() can be called with $var either as int or as null as well.
What are the differences between those two methods ? Is any of these more performant than the other?
It is important to distinguish between the two language features being discussed here, that is, type declarations and default argument values.
The first function is only using type declarations, this means that the input argument has to be of the type int or NULL.
The second function is using both type declarations and default argument values, this means that the argument has to be of the type int or NULL but if omitted it will default to NULL.
Take your first function, if you simply called test() without passing anything to it, you'd get:
PHP Fatal error: Uncaught ArgumentCountError: Too few arguments to function test() [...]
which is correct as the function expects either int or NULL but got neither whereas for the second, as you have defined the argument with a default value, it would run without errors.
Code
function test(?int $var) {
var_dump($var);
}
function test2(int $var = null) {
var_dump($var);
}
test(1); // fine
test(); // error
test2(1); // fine
test2(); // fine
As far as performance goes, the difference is probably negligible, nothing significant enough that would be a cause for concern.
Live Example
Repl
If the language were designed today, int $var = null would probably be an error, and should really be written ?int $var = null. The two parts mean different things:
The ? indicates that null is a valid value for that parameter.
The = null indicates that null is the default if the parameter is not passed.
However, before the ?type syntax was introduced, there was a special case in the language: if null is given as the default for a parameter, then it is legal to pass null to that parameter, even if the type declaration would otherwise prevent it.
The difference is how you can call the function:
// public function test(?int $var)
$foo->test("x"); // does not work (Argument 1 passed to Foo::test() must be of the type int or null, string given)
$foo->test(123); // works
$foo->test(null); // works
$foo->test(); // does not work (Too few arguments to function Foo::test(), 0 passed)
// public function test(int $var = null)
$foo->test("x"); // does not work (Argument 1 passed to Foo::test() must be of the type int or null, string given)
$foo->test(123); // works
$foo->test(null); // works
$foo->test(); // works
The difference is that you cannot call the function as ->test() using the first syntax.
As of PHP 7.0, the scalar type hints int, float, string, and bool can be included in method signatures. By default, these type declarations operate in weak/coercive mode (or "type juggling" mode). According to the PHP manual:
PHP will coerce values of the wrong type into the expected scalar type if possible. For example, a function that is given an integer for a parameter that expects a string will get a variable of type string.
But even though it is possible to coerce NULL into the integer 0, a method with an int typehint will refuse to coerce an inbound value of NULL to the integer 0.
<?php
class MyClass
{
public function test(int $arg)
{
echo $arg;
}
}
$obj = new MyClass();
$obj->test('123'); // 123
$obj->test(false); // 0
$obj->test(null); // TypeError: Argument 1 passed to MyClass::test()
// must be of the type integer, null given
And similarly, even though it is possible to coerce NULL into the boolean false, a method with a bool typehint will refuse to coerce an inbound value of NULL to the boolean false. The same goes for the float and string type hints as well.
This behavior seems to contradict the documentation on php.net. What's going on here?
There is currently no way to allow a method with a scalar type hint to automatically type juggle inbound NULL values to the declared type.
Per the RFC responsible for introducing this feature into PHP 7:
The weak type checking rules for the new scalar type declarations are mostly (emphasis added) the same as those of extension and built-in PHP functions. The only exception to this is the handling of NULL: in order to be consistent with our existing type declarations for classes, callables and arrays, NULL is not accepted by default, unless it is a parameter and is explicitly given a default value of NULL.
However, NULL values can be accepted as NULLs in the following scenarios:
<?php
class MyClass
{
// PHP 7.0+
public function testA(int $arg = null)
{
if (null === $arg) {
echo 'The argument is NULL!';
}
}
// PHP 7.1+
// https://wiki.php.net/rfc/nullable_types
public function testB(?int $arg)
{
if (null === $arg) {
echo 'The argument is NULL!';
}
}
}
$obj = new MyClass();
$obj->testA(null); // The argument is NULL!
$obj->testB(null); // The argument is NULL!
PHP 5 Type Hinting
PHP 5 introduces Type Hinting. Functions are now able to force parameters to be objects (by specifying the name of the class in the function prototype) or arrays (since PHP 5.1). However, if NULL is used as the default parameter value, it will be allowed as an argument for any later call.
The following excerpt from the above:
if NULL is used as the default parameter value, it will be allowed as an argument for any later call.
Does the above mean:
if default parameters are to used use with type hinting, it can have only have NULL as the default value.
i.e. the code in code1 is wrong and results in:
Fatal error: Default value for parameters with a class type hint can only be NULL
code1:
function setName ( string $name = "happ") {
...
}
Where as code in code2 is right:
code2:
function setName ( string $name = NULL) {
...
}
Why is this constraint assigned in php?
You can't typehint strings, you can only typehint objects and arrays, so this is incorrect:
function setName ( string $name = "happ") {
(The reason you don't get a compile-time error here is because PHP is interpreting "string" as the name of a class.)
The wording in the docs means that if you do this:
function foo(Foo $arg) {
Then the argument passed to foo() must be an instance of object Foo. But if you do this:
function foo(Foo $arg = null) {
Then the argument passed to foo() can either be an instance of object Foo, or null. Note also that if you do this:
function foo(array $foo = array(1, 2, 3))
Then you can't call foo(null). If you want this functionality, you can do something like this:
function foo(array $foo = null) {
if ($foo === null) {
$foo = array(1, 2, 3);
}
[Edit 1] As of PHP 5.4, you can typehint callable:
function foo(callable $callback) {
call_user_func($callback);
}
[Edit 2] As of PHP 7.0, you can typehint bool, float, int, and string. This makes the code in the question valid syntax. As of PHP 7.1, you can typehint iterable.
Type declarations (also known as type hints in PHP 5) of a string type are supported in PHP 7.
The valid types are:
Class/interface name (>=PHP 5.0.0);
self (>=PHP 5.0.0);
array (>=PHP 5.1.0);
callable (>=PHP 5.4.0);
bool, float, int, string (>=PHP 7.0.0);
iterable - either an array or an instanceof Traversable (>=PHP 7.1.0).
This is a matter of compilation time versus run time values. At compilation only literal values (strings, numbers, booleans and NULL) are allowed. The PHP processor can't know about all the possible classes at this time and so you can't create an instance of an object in the function arguments.
What I'm expecting from the excerpt is that, while normally passing NULL into a type hinted function will throw an Exception/Error. If you set a default as NULL then it won't throw this exception if NULL is passed. I haven't tested it, just what I would expect.
In the PHP manual, to show the syntax for functions with optional parameters, they use brackets around each set of dependent optional parameter. For example, for the date() function, the manual reads:
string date ( string $format [, int $timestamp = time() ] )
Where $timestamp is an optional parameter, and when left blank it defaults to the time() function's return value.
How do you go about creating optional parameters like this when defining a custom function in PHP?
Much like the manual, use an equals (=) sign in your definition of the parameters:
function dosomething($var1, $var2, $var3 = 'somevalue'){
// Rest of function here...
}
The default value of the argument must be a constant expression. It can't be a variable or a function call.
If you need this functionality however:
function foo($foo, $bar = false)
{
if(!$bar)
{
$bar = $foo;
}
}
Assuming $bar isn't expected to be a boolean of course.
Some notes that I also found useful:
Keep your default values on the right side.
function whatever($var1, $var2, $var3="constant", $var4="another")
The default value of the argument must be a constant expression. It can't be a variable or a function call.
Give the optional argument a default value.
function date ($format, $timestamp='') {
}
The date function would be defined something like this:
function date($format, $timestamp = null)
{
if ($timestamp === null) {
$timestamp = time();
}
// Format the timestamp according to $format
}
Usually, you would put the default value like this:
function foo($required, $optional = 42)
{
// This function can be passed one or more arguments
}
However, only literals are valid default arguments, which is why I used null as default argument in the first example, not $timestamp = time(), and combined it with a null check. Literals include arrays (array() or []), booleans, numbers, strings, and null.
If you don't know how many attributes need to be processed, you can use the variadic argument list token(...) introduced in PHP 5.6 (see full documentation here).
Syntax:
function <functionName> ([<type> ]...<$paramName>) {}
For example:
function someVariadricFunc(...$arguments) {
foreach ($arguments as $arg) {
// do some stuff with $arg...
}
}
someVariadricFunc(); // an empty array going to be passed
someVariadricFunc('apple'); // provides a one-element array
someVariadricFunc('apple', 'pear', 'orange', 'banana');
As you can see, this token basically turns all parameters to an array, which you can process in any way you like.
Starting with 7.1 there is a type hinting for nullable parameters
function func(?Object $object) {}
It will work for these cases:
func(null); //as nullable parameter
func(new Object()); // as parameter of declared type
But for optional value signature should look like.
function func(Object $object = null) {} // In case of objects
function func(?Object $object = null) {} // or the same with nullable parameter
function func(string $object = '') {} // In case of scalar type - string, with string value as default value
function func(string $object = null) {} // In case of scalar type - string, with null as default value
function func(?string $object = '') {} // or the same with nullable parameter
function func(int $object = 0) {} // In case of scalar type - integer, with integer value as default value
function func(int $object = null) {} // In case of scalar type - integer, with null as default value
function func(?int $object = 0) {} // or the same with nullable parameter
than it can be invoked as
func(); // as optional parameter
func(null); // as nullable parameter
func(new Object()); // as parameter of declared type