- PHP RFC: Nullable Types
- Proposal
- Return Types
- Parameter Types
- Default Values
- Differences from default values
- Relationship with default values
- PHP Version
- Voting Choices
- Patches and Tests
- RFC Impact
- To Backward Compatibility
- To Existing Extensions
- To Union Types
- Unaffected PHP Functionality
- Future Scope
- Implementation
- Php type string or null
PHP RFC: Nullable Types
It is common in many programming languages including PHP to allow a variable to be of some type or null. This null often indicates an error or lack of something to return. This can already be done using PHP’s dynamic type system by omitting type declarations. It can also be done for parameters in PHP by using a default parameter of null. However, this does not work for return types which do not have a concept of a default value. This RFC proposes a unified way to add nullable types to both parameters and returns.
Proposal
This proposal adds a leading question mark symbol ( ? ) to indicate that a type can also be null . Nullable types can be formed from any currently permitted type. Nullable types are permitted anywhere type declarations are allowed but are subject to some inheritance rules which are outlined later in the RFC .
Here are a few examples to demonstrate basic functionality:
function answer(): ?int { return null; //ok } function answer(): ?int { return 42; // ok } function answer(): ?int { return new stdclass(); // error }
function say(?string $msg) { if ($msg) { echo $msg; } } say('hello'); // ok -- prints hello say(null); // ok -- does not print say(); // error -- missing parameter say(new stdclass); //error -- bad type
Return Types
When subtyping a return type the nullability can be removed by a subclass, but it cannot be added:
interface Fooable { function foo(): ?Fooable; } interface StrictFooable extends Fooable { function foo(): Fooable; // valid }
interface Fooable { function foo(): Fooable; } interface LooseFooable extends Fooable { function foo(): ?Fooable; // invalid }
Parameter Types
The nullable type cannot be removed in a sub-class; it can be added if not present in a super-class. This behavior is consistent with the Liskov substitution principle.
// Valid use: loosening the nullable marker in a parameter: interface Fooable { function foo(Fooable $f); } interface LooseFoo extends Fooable { function foo(?Fooable $f); }
// Invalid use: tightening the nullable marker in a parameter: interface Fooable { function foo(?Fooable $f); } interface StrictFoo extends Fooable { function foo(Fooable $f); }
Default Values
Differences from default values
Parameters with a nullable type do not have a default value. If omitted the value does not default to null and will result in an error:
function f(?callable $p) {} f(); // invalid; function f does not have a default
Relationship with default values
PHP’s existing semantics allow giving a null default value for a parameter to make it nullable and optional:
function foo_default(Bar $bar = null) {} foo_default(new Bar); // valid foo_default(null); // valid foo_default(); // valid
This existing behaviour is not changed by this RFC . The new nullable type feature offers a subset of the functionality of = null with both making a parameter nullable but only = null making a parameter optional and have a default value:
function foo_nullable(?Bar $bar) {} foo_nullable(new Bar); // valid foo_nullable(null); // valid foo_nullable(); // INVALID!
As = null offers a superset of ? ‘s functionality, it could be said that = null implies ? . However, it is perfectly legal to use both to make a parameter’s nullability explicit:
function foo_both(?Bar $bar = null) {} foo_both(new Bar); // valid foo_both(null); // valid foo_both(); // valid
Because a parameter with = null is a superset of ? , you can use a parameter with a default value of null where a nullable type existed in the parent.
The reverse is not true, however: you cannot use only a nullable type where a default value existed in the parent, because the parameter is no longer optional.
PHP Version
Voting Choices
The vote for this RFC is split into two votes. One vote will be for accepting the idea of explicitly nullable types with the short-hand syntax. The second vote determines whether to merge only nullable return types or to also merge nullable parameter types as well.
Voting began Tuesday, May 10th, 2016 and will close on Friday, May 20th, 2016.
Patches and Tests
RFC Impact
To Backward Compatibility
There is a backwards compatibility break in certain cases. This was previously fixed as a bug but it was decided that because of the BC break that it would be pushed to this RFC . See bug 72119 for more info on the BC break.
This BC break is to reject parameter covariance for nullable types:
interface Fooable { function foo(?Fooable $f); } interface StrictFoo extends Fooable { // Invalid; parent type allows null so subtype must also allow it function foo(Fooable $f); }
However, it breaks this code:
interface Fooable { function foo(array $f = null); } interface LooseFoo extends Fooable { function foo(array $f = []); }
Such code should be modified to also allow null:
Note that more handling is probably necessary to make the code robust, but this small change is sufficient for any previously working code to continue to work.
To Existing Extensions
Only extensions that deal with the AST need to be updated. They should be aware of the ZEND_TYPE_NULLABLE attribute that gets set when a ? is present.
To Union Types
Nullable types are a special case of union types where there only two types in the union, one of which is always null . If the union_types RFC is accepted then ?Foo will be exactly equivalent to Foo | Null . The union types RFC will be responsible for intersecting decisions, such as whether ? can be used in conjunction with other union types.
Unaffected PHP Functionality
This RFC does not deprecate the default value syntax. While there is some overlap of features between it and this RFC , they serve different purposes. As such, the default value syntax will remain.
Future Scope
Implementation
After the project is implemented, this section should contain
Php type string or null
While waiting for native support for typed arrays, here are a couple of alternative ways to ensure strong typing of arrays by abusing variadic functions. The performance of these methods is a mystery to the writer and so the responsibility of benchmarking them falls unto the reader.
PHP 5.6 added the splat operator (. ) which is used to unpack arrays to be used as function arguments. PHP 7.0 added scalar type hints. Latter versions of PHP have further improved the type system. With these additions and improvements, it is possible to have a decent support for typed arrays.
function typeArrayNullInt (? int . $arg ): void >
function doSomething (array $ints ): void (function (? int . $arg ) <>)(. $ints );
// Alternatively,
( fn (? int . $arg ) => $arg )(. $ints );
// Or to avoid cluttering memory with too many closures
typeArrayNullInt (. $ints );
function doSomethingElse (? int . $ints ): void /* . */
>
$ints = [ 1 , 2 , 3 , 4 , null ];
doSomething ( $ints );
doSomethingElse (. $ints );
?>
Both methods work with all type declarations. The key idea here is to have the functions throw a runtime error if they encounter a typing violation. The typing method used in doSomethingElse is cleaner of the two but it disallows having any other parameters after the variadic parameter. It also requires the call site to be aware of this typing implementation and unpack the array. The method used in doSomething is messier but it does not require the call site to be aware of the typing method as the unpacking is performed within the function. It is also less ambiguous as the doSomethingElse would also accept n individual parameters where as doSomething only accepts an array. doSomething’s method is also easier to strip away if native typed array support is ever added to PHP. Both of these methods only work for input parameters. An array return value type check would need to take place at the call site.
If strict_types is not enabled, it may be desirable to return the coerced scalar values from the type check function (e.g. floats and strings become integers) to ensure proper typing.
same data type and same value but first function declare as a argument type declaration and return int(7)
and second fucntion declare as a return type declaration but return int(8).
function argument_type_declaration(int $a, int $b) return $a+$b;
>
function return_type_declaration($a,$b) :int return $a+$b;
>