Hi all,
As briefly mentioned, I think the approach to non-nullable types in the typed
properties proposal needs more discussion, to make sure we're not repeating
Tony Hoare's "billion-dollar mistake".
For parameter hints, nullability is naturally optional, particularly in PHP
where "nullable Foo" is equivalent to a union type "null or Foo". For
properties, this isn't the case, because every property must have some initial
state.
The simplest solution, which I think would be a sensible starting point if
we're not ready for more complex changes, is to say that all typed properties
must have a default value, and if that default value is null, the type hint
must naturally be nullable. In other words, make "public Foo $foo" illegal, but
allow "public ?Foo $foo=null".
The current RFC proposes the next simplest solution, which is to allow
non-nullable types, and trust the user to initialise them before use.
My first objection to this is that it creates an entirely new state for object
properties to be in, which behaves differently from everything else in the
language. There will then be three or four different ways for an object
property to be "unset":
- not declared at all; access gives a notice and the implicit value null
- declared with no default, uninitialised, implicitly null
- declared with a default of null, or explicitly assigned as null
- declared with a non-nullable type, never initialised; rather than an implicit
null, access generates an Error
The bigger problem is that the properties are non-nullable in name only, and
the declaration can't be trusted. Consider the following class:
class Validity {
public \DateTimeInterface $validFrom;
public ?\DateTimeInterface $validTo = null;
}
If I have an instance $v of that class, I would expect to be able to safely
call $v->validFrom->getTimestamp(), but need to check for nulls before doing
the same with validTo. Under the current proposal, doing so will risk errors,
so I will have to check both properties before access anyway. Worse, using
is_null() or ?: will attempt to retrieve the value, so I actually have to be
*more* careful, and use isset() or ?? instead.
Alternatively, I can trust the author of the class, but at that point they
might as well just use a docblock - the type hint documents their intent, but
is not enforced.
Swift is often cited as a language which gets nullability right, and a lot of
attention is given to Options, and the compiler ensuring that the None / null
case is handled; but at least as important is how it handles initialisation of
non-nullable properties, using "two-phase initialisation":
https://docs.swift.org/swift-book/LanguageGuide/Initialization.html
In short, Swift defines a specific point after which all properties must have a
valid value; before this point, the entire object is invalid for read
operations, so there is no need for a specific error when accessing
uninitialised properties.
Swift marks this point by the call to the parent initialiser, ultimately going
up the chain to the root object. PHP has no root object, and no mandatory
parent constructor calls, so would need a different way to mark this stage.
Constructors can already violate the rules the first phase should impose, so we
can't just use the end of the constructor as the validation point.
One possibility would be to add a new keyword like "initialize" which must be
added to constructors in the presence of non-nullable properties. Above that
keyword, use of $this other than to write to its properties would be an error;
afterwards, the constructor could carry on as normal.
As I say, this is complex, and it may be best to add nullable typed properties
first, and give more time to try out approaches to initialisation.
Regards,
--
Rowan Collins
[IMSoP]