PHP supports expression casting to primitive type (like int
) by using “(
type )
expression”, but it currently doesn't allow to use a nullable type as introduced by PHP 7.1 (e.g. ?int
).
Due to the lack of support for nullable casting, it is necessary to write additional code to preserve a possible null
value through the type conversion. This feature would also bring more consistency and completeness to the existing language.
In strict type-checking mode (<?php declare(strict_types=1);
), given two functions like the following (disclaimer: dummy implementation):
function getInt(): int { return mt_rand(); } function processString(string $s): void { printf("process a string of %d byte(s)\n", strlen($s)); }
then the following call:
processString(getInt());
will throw a TypeError
(“Argument 1 passed to processString() must be of the type string, int given”), but here we can use a cast (int
to string
conversion is always safe):
processString((string) getInt());
(which will print something like “process a string of 9 byte(s)”).
Now given two functions like the following (with nullable type declarations):
function getIntOrNull(): ?int { $r = mt_rand(); return $r % 2 === 0 ? $r : null; } function processStringOrNull(?string $s): void { if ($s === null) { printf("process null\n"); } else { printf("process a string of %d byte(s)\n", strlen($s)); } }
then the following call:
processStringOrNull(getIntOrNull());
will sometimes work (print “process null”) and sometimes throw a TypeError
(“Argument 1 passed to processStringOrNull() must be of the type string or null, int given”), so we would want to use a “nullable cast”:
processStringOrNull((?string) getIntOrNull());
but currently this syntax is not supported (“Parse error: syntax error, unexpected '?'”) and we must resort to something more verbose (and error-prone) like:
processStringOrNull(($tmp = getIntOrNull()) === null ? null : (string) $tmp); unset($tmp);
or writing custom casting functions.
(Note that in weak type-checking mode, there is never a TypeError
, the ?int
is automatically converted to ?string
, correctly preserving a null
value. But we can prefer strict typing, to catch unintended conversions.)
When the desired type is not known before runtime, we cannot use a cast operator, but we can use the settype()
function, for example:
function getIntOrNullAs(string $type) { $x = getIntOrNull(); settype($x, $type); return $x; }
but currently $type
cannot contain a nullable type like "?string"
(“Warning: settype(): Invalid type”, $x
not converted).
The proposal is to add support of nullable types to the current casting feature. Basically, (int)
is the “plain” int cast, and (?int)
will be a nullable int cast. Generally speaking, what changes is the possibility to use a leading question mark symbol (?
) before the type of a cast, turning it into a nullable cast.
The only difference of nullable casting compared to plain casting is that if the expression value is null
, it will be kept as null
instead of being forced to the destination plain type:
type: | int | bool | float | string | array | object |
---|---|---|---|---|---|---|
( type) null | 0 | false | 0.0 | "" | [] | {} |
(? type) null | null | null | null | null | null | null |
Notes:
(unset)
cast will not be affected (see the “Unaffected PHP Functionality” section).( int )
” cast and “? int
” type declaration, so e.g. “( ? int )
” will be identical to “(?int)
”.(integer)
and (int)
casts, so (?integer)
will be identical to (?int)
. Likewise for (?boolean)
vs (?bool)
, (?double)
or (?real)
vs (?float)
, and (?binary)
vs (?string)
.
If the expression value is not null
, nullable casting will give the same result as plain casting: e.g. (?int)false will give 0, (?array)"" will give [""].
Additionally, it was requested on the mailing list to consider adding support of nullable types to the settype()
function, e.g. settype($variable, "?int")
, which here would be the same as $variable = (?int)$variable;
and return true
(but in general "?int"
could be a dynamic string).
In short, for a currently valid $type
argument to settype($variable, $type)
, it would enable to use '?'.$type
to preserve nullability.
In "?null"
, the “?
” is redundant (“nullable null”), but it could happen in dynamic code, e.g. settype($x, '?' . gettype($y))
when $y
is null
.
Possible options:
"null"
silently."null"
but emit a specific Notice.For demonstration, the current patch uses option 2.
None.
Next PHP 7.x (7.4 now).
Help needed
Help needed
Help needed
(unset)
cast (always returning null
, deprecated in PHP 7.2 and to be removed in PHP 8.0) is not affected (i.e. the “(?unset)
” syntax is not proposed, and will continue to cause a Parse error).gettype()
function is not affected.(Each child vote result will be considered only if its parent vote passes.)
(The voting period would be two weeks)
Current alternatives:
if
statement), possibly with a temporary variable"foo"
to int
)“I understand the use-case for when you want to pass something to a nullable parameter, but if you think about this cast in isolation, it hardly makes sense.”
But we're missing “arrayval()” and “objectval()”... And we might use short closure fn($x) => (?int)$x
One might expect to also have e.g. (?int)"foo"
and (?int)""
give null
rather than 0
, (?string)[42]
give null
rather than "Array"
... and to be able to use (?int)$value ?? $default
, (?string)$_GET["input"] ?? ""
...
E.g. “(null|int) $x
”