[DISCUSSION] Checking uninitialized class properties

php.internals

Luigi Cardamone

2 years ago
Hello Internals, during last PHPDay in Verona I discussed this topic with some of you and it was suggested to me to send an email here. Here is an example to describe my problem. Imagine a simple DTO like this: class MyDTO{ public ?int $propA; public ?int $propB; } Imagine that a Form processor or a generic mapper fill some of these fields with a null value: $dto = new MyDTO(); $dto->propA = null; Sometimes we use DTOs to handle PATCH requests and not all the properties are mapped with a value. In a scenario like this, "null" is often a valid value. At this point, I need a way to find if a property was initialized or not but unfortunately "isset" is not a solution. When I write: echo isset($dto->propA) ? 'init' : 'not-init'; I get "not-init" since isset returns true only if the variable is set and different from null. Full example: https://3v4l.org/4cCj0 Is the language missing a clean way to check if a property is initialized or not? Is there any solution to this problem? The only alternative is using reflection but I need to pass the property name as a string losing static analysis. Proposing a new language syntax like "is_initialized($dto->propA)" can be an interesting solution? Thank you in advance. Luigi Cardamone Backend developer Italy

Larry Garfield

2 years ago
On Fri, May 17, 2024, at 9:40 PM, Luigi Cardamone wrote:
> Hello Internals, > during last PHPDay in Verona I discussed this topic with some of > you and it was suggested to me to send an email here. > > Here is an example to describe my problem. Imagine a simple > DTO like this: > > class MyDTO{ > public ?int $propA; > public ?int $propB; > } > > Imagine that a Form processor or a generic mapper fill some of > these fields with a null value: > > $dto = new MyDTO(); > $dto->propA = null; > > Sometimes we use DTOs to handle PATCH requests and not all > the properties are mapped with a value. In a scenario like this, > "null" is often a valid value. > > At this point, I need a way to find if a property was initialized or > not but unfortunately "isset" is not a solution. When I write: > > echo isset($dto->propA) ? 'init' : 'not-init'; > > I get "not-init" since isset returns true only if the variable is set > and different from null. > > Full example: https://3v4l.org/4cCj0 > > Is the language missing a clean way to check if a property is > initialized or not? Is there any solution to this problem? > > The only alternative is using reflection but I need to pass the > property name as a string losing static analysis. > Proposing a new language syntax like > "is_initialized($dto->propA)" can be an interesting solution? > > Thank you in advance.
This is a gap in the current API, and I've run into it myself. What I figured out was: array_key_exists($prop, get_object_vars($obj)) Because get_object_vars() omits uninitialized values. I would absolutely like a better alternative. --Larry Garfield

Ilija Tovilo

2 years ago
Hi Luigi On Fri, May 17, 2024 at 11:40 PM Luigi Cardamone <cardamoneluigi@gmail.com> wrote:
> > Here is an example to describe my problem. Imagine a simple > DTO like this: > > class MyDTO{ > public ?int $propA; > public ?int $propB; > } > > Imagine that a Form processor or a generic mapper fill some of > these fields with a null value: > > $dto = new MyDTO(); > $dto->propA = null; > > Sometimes we use DTOs to handle PATCH requests and not all > the properties are mapped with a value. In a scenario like this, > "null" is often a valid value. > > At this point, I need a way to find if a property was initialized or > not but unfortunately "isset" is not a solution. When I write: > > echo isset($dto->propA) ? 'init' : 'not-init';
IMO, "uninitialized" should not be treated as a value. Code outside of the constructor should generally not have to deal with uninitialized properties. Instead, make sure the constructor leaves your object fully initialized, in this case likely by setting it to null. If you need some additional "undefined" state, I think that's better modeled in other ways, maybe by wrapping it in an object. ADTs [1] should help with that in the future. enum Age { case Unknown; case Unborn; case Years(int $years); } public Age $age; Or: enum Age { case Unborn; case Years(int $years); } public ?Age $age; Silly example, but you get the point. Ilija [1] https://wiki.php.net/rfc/tagged_unions

Robert Landers

2 years ago
On Fri, May 17, 2024 at 11:43 PM Luigi Cardamone <cardamoneluigi@gmail.com> wrote:
> > Hello Internals, > during last PHPDay in Verona I discussed this topic with some of > you and it was suggested to me to send an email here. > > Here is an example to describe my problem. Imagine a simple > DTO like this: > > class MyDTO{ > public ?int $propA; > public ?int $propB; > } > > Imagine that a Form processor or a generic mapper fill some of > these fields with a null value: > > $dto = new MyDTO(); > $dto->propA = null; > > Sometimes we use DTOs to handle PATCH requests and not all > the properties are mapped with a value. In a scenario like this, > "null" is often a valid value. > > At this point, I need a way to find if a property was initialized or > not but unfortunately "isset" is not a solution. When I write: > > echo isset($dto->propA) ? 'init' : 'not-init'; > > I get "not-init" since isset returns true only if the variable is set > and different from null. > > Full example: https://3v4l.org/4cCj0 > > Is the language missing a clean way to check if a property is > initialized or not? Is there any solution to this problem? > > The only alternative is using reflection but I need to pass the > property name as a string losing static analysis. > Proposing a new language syntax like > "is_initialized($dto->propA)" can be an interesting solution? > > Thank you in advance. > > Luigi Cardamone > Backend developer > Italy
I wouldn't rely on checking initialized or not. Instead, use a sentinel value: class NotSet {} define("NotSet", new NotSet()); class MyAwesomeValue { public NotSet|null|string $aValue = NotSet; } This way, even programmers can "unset" a property by simply setting $value->aValue = NotSet without having to rely on the weird PHP semantics of unsetting a property. You can also use this to declaratively encode which properties cannot be unset, without having to define it in your mapping code. Robert Landers Software Engineer Utrecht NL

Luigi Cardamone

2 years ago
Thank you for your feedback. I am already using a solution like the one proposed by Robert: it works but it leaves some doubts since each project can have a different name for NotSet while it seems a general concept of the language. Besides the example I proposed, I think that there are many cases where you can check if the property is initialised or not and for consistency "null" should be considered a valid initialization value. Are there any downsides in adding a specific syntax to check if a property is initialized with any value? Thank you. Luigi Cardamone Backend developer Italy Il Sab 18 Mag 2024, 11:19 Robert Landers <landers.robert@gmail.com> ha scritto:

Robert Landers

2 years ago
On Sat, May 18, 2024 at 12:52 PM Luigi Cardamone <cardamoneluigi@gmail.com> wrote:
> > Thank you for your feedback. > > I am already using a solution like the one > proposed by Robert: it works but it > leaves some doubts since each project > can have a different name for NotSet > while it seems a general concept of the > language. > > Besides the example I proposed, I think > that there are many cases where you > can check if the property is initialised > or not and for consistency "null" should > be considered a valid initialization value. > > Are there any downsides in adding a > specific syntax to check if a property > is initialized with any value? > > Thank you. > > Luigi Cardamone > Backend developer > Italy > > Il Sab 18 Mag 2024, 11:19 Robert Landers <landers.robert@gmail.com> ha scritto: >> >> On Fri, May 17, 2024 at 11:43 PM Luigi Cardamone >> <cardamoneluigi@gmail.com> wrote: >> > >> > Hello Internals, >> > during last PHPDay in Verona I discussed this topic with some of >> > you and it was suggested to me to send an email here. >> > >> > Here is an example to describe my problem. Imagine a simple >> > DTO like this: >> > >> > class MyDTO{ >> > public ?int $propA; >> > public ?int $propB; >> > } >> > >> > Imagine that a Form processor or a generic mapper fill some of >> > these fields with a null value: >> > >> > $dto = new MyDTO(); >> > $dto->propA = null; >> > >> > Sometimes we use DTOs to handle PATCH requests and not all >> > the properties are mapped with a value. In a scenario like this, >> > "null" is often a valid value. >> > >> > At this point, I need a way to find if a property was initialized or >> > not but unfortunately "isset" is not a solution. When I write: >> > >> > echo isset($dto->propA) ? 'init' : 'not-init'; >> > >> > I get "not-init" since isset returns true only if the variable is set >> > and different from null. >> > >> > Full example: https://3v4l.org/4cCj0 >> > >> > Is the language missing a clean way to check if a property is >> > initialized or not? Is there any solution to this problem? >> > >> > The only alternative is using reflection but I need to pass the >> > property name as a string losing static analysis. >> > Proposing a new language syntax like >> > "is_initialized($dto->propA)" can be an interesting solution? >> > >> > Thank you in advance. >> > >> > Luigi Cardamone >> > Backend developer >> > Italy >> >> I wouldn't rely on checking initialized or not. Instead, use a sentinel value: >> >> class NotSet {} >> >> define("NotSet", new NotSet()); >> >> class MyAwesomeValue { >> public NotSet|null|string $aValue = NotSet; >> } >> >> This way, even programmers can "unset" a property by simply setting >> $value->aValue = NotSet without having to rely on the weird PHP >> semantics of unsetting a property. You can also use this to >> declaratively encode which properties cannot be unset, without having >> to define it in your mapping code. >> >> Robert Landers >> Software Engineer >> Utrecht NL
Please remember to bottom post!
> it works but it > leaves some doubts since each project > can have a different name for NotSet > while it seems a general concept of the > language.
Why does it need to be consistent between projects? Most languages simply set uninitialized values as `null`, and every project across all languages will have to solve this differently, specific to their project. For PHP projects, it really depends on how you want to handle it in your project. I have a project where an uninitialized value during serialization/deserialization is an error because that means someone forgot to map something or the end-user forgot a field in their request. In another project, values are uninitialized on purpose and accessing properties goes through __set and __get. I don't think there's a silver bullet here.
> I think > that there are many cases where you > can check if the property is initialised > or not and for consistency "null" should > be considered a valid initialization value.
Well, that depends on your application's business logic and model. A bank account probably shouldn't be allowed to have a null balance, a player have a null position, or a farmer have null goats.
> Are there any downsides in adding a > specific syntax to check if a property > is initialized with any value?
Personally, I've used such a check so rarely and when I've done so, it has been in the context of reflection, for which there is a method on ReflectionProperty (https://www.php.net/manual/en/reflectionproperty.isinitialized.php) to check for it. I've never needed to know the difference between uninitialized and null during regular runtime and $value ?? $default has always been what I've needed. In your case, this would probably be all you need: https://3v4l.org/p1nAHJ I guess this is a pretty good use case for nameof which keeps staying down near the bottom of todo list lately... Robert Landers Software Engineer Utrecht NL

Erick de Azevedo Lima

2 years ago
> I am already using a solution like the one > proposed by Robert: it works but it leaves some doubts since each project > can have a different name for NotSet while it seems a general concept of
the language Hi everyone. I just thought of some ways to standardize this: 1 - Create an RFC to create an "uninitialized" construct/function to make this check; 2 - Ask Larry and Ilija to include an "is uninitialized" pattern into their wonderful Pattern Matching RFC (https://wiki.php.net/rfc/pattern-matching); 3 - Create a PSR to create this "Uninitialized" class and constant standard to PHP. I would go for option 2. Best regards, Erick

Rowan Tommins [IMSoP]

2 years ago
On 18/05/2024 11:52, Luigi Cardamone wrote:
> I am already using a solution like the one > proposed by Robert: it works but it > leaves some doubts since each project > can have a different name for NotSet
An argument is often made that this is a good thing, in the sense that "null" and other "universal" terminal values combine multiple meanings in an unhelpful way. You'll see this frequently regarding SQL's handling of NULL, for instance - does it mean "unknown", "not applicable", "invalid", etc. A common solution put forward to this perceived problem is "algebraic data types" - a Maybe or Option type for "value or missing", an Error or Failable type for "value or error", etc. PHP doesn't have those (yet...), but the same information can be conveyed nicely with a final class or single-element enum. In your example, the actual value you want to represent is not "Not Set", it's "Keep Current Value", so that's the terminal value you need: final class KeepCurrent {} class MyDTOPatch {     public int|null|KeepCurrent $propA;     public int|null|KeepCurrent $propB; } or: enum PatchState { case KeepCurrent } class MyDTOPatch {     public int|null|PatchState $propA;     public int|null|PatchState $propB; }
> Are there any downsides in adding a > specific syntax to check if a property > is initialized with any value?
In my opinion - and I stress that others may not share this opinion - the entire concept of "uninitialized properties" is a wart on the language, which we should be doing our best to eliminate, not adding more features around it. As a bit of background, the concept was created when typed properties were being added, to handle a limitation of the language: given the declaration "public Foo $prop;" there is no way to specify an initial value which meets the type constraint. For nullable properties, you can write "public ?Foo $prop=null;" but since PHP (thankfully) distinguishes nullable and non-nullable types, you can't write "public Foo $prop=null;" Some languages, e.g. Swift, require that all properties are initialised before the constructor returns, but retrofitting this to PHP was considered impractical, so instead it was left to a run-time error: if you fail to initialise a property, you will get an error trying to access it. To track that, the engine has to record a special state, but assigning a meaning to that error state is a bit like using exceptions for flow control. It would be more in keeping with the original purpose to have an object_is_valid() function, which returned false if *any* property had not been initialised to a valid value. PHP actually has a bewildering variety of such special states. In a different compromise added at the same time, calling unset() on a typed property puts it into a *separate* state where magic __get and __set are called, which they are not if the property has simply not yet been assigned, e.g. https://3v4l.org/C7rIF I have always found this a mess. If a property says it is of type "?int", I want to know that it will always be an integer or null, not "int or null or uninitialised or unset". If it needs more than one non-integer state, that should be specified in the type system, e.g. "int|NotApplicable|NotSpecified|NotLoaded". PS: Etiquette on this list is to post replies below the text you're replying to, preferably editing to the relevant parts as I've done here, rather than adding your text above the quoted message. Required,
-- Rowan Tommins [IMSoP]

Larry Garfield

2 years ago
On Sat, May 18, 2024, at 9:40 AM, Rowan Tommins [IMSoP] wrote:
> On 18/05/2024 11:52, Luigi Cardamone wrote: >> I am already using a solution like the one >> proposed by Robert: it works but it >> leaves some doubts since each project >> can have a different name for NotSet > > > An argument is often made that this is a good thing, in the sense that > "null" and other "universal" terminal values combine multiple meanings > in an unhelpful way. You'll see this frequently regarding SQL's handling > of NULL, for instance - does it mean "unknown", "not applicable", > "invalid", etc. > > A common solution put forward to this perceived problem is "algebraic > data types" - a Maybe or Option type for "value or missing", an Error or > Failable type for "value or error", etc. PHP doesn't have those > (yet...), but the same information can be conveyed nicely with a final > class or single-element enum. > > In your example, the actual value you want to represent is not "Not > Set", it's "Keep Current Value", so that's the terminal value you need: > > final class KeepCurrent {} > class MyDTOPatch { >     public int|null|KeepCurrent $propA; >     public int|null|KeepCurrent $propB; > } > > or: > > enum PatchState { case KeepCurrent } > class MyDTOPatch { >     public int|null|PatchState $propA; >     public int|null|PatchState $propB; > } > > > >> Are there any downsides in adding a >> specific syntax to check if a property >> is initialized with any value? > > > In my opinion - and I stress that others may not share this opinion - > the entire concept of "uninitialized properties" is a wart on the > language, which we should be doing our best to eliminate, not adding > more features around it. > > As a bit of background, the concept was created when typed properties > were being added, to handle a limitation of the language: given the > declaration "public Foo $prop;" there is no way to specify an initial > value which meets the type constraint. For nullable properties, you can > write "public ?Foo $prop=null;" but since PHP (thankfully) distinguishes > nullable and non-nullable types, you can't write "public Foo $prop=null;" > > Some languages, e.g. Swift, require that all properties are initialised > before the constructor returns, but retrofitting this to PHP was > considered impractical, so instead it was left to a run-time error: if > you fail to initialise a property, you will get an error trying to > access it. > > To track that, the engine has to record a special state, but assigning a > meaning to that error state is a bit like using exceptions for flow > control. It would be more in keeping with the original purpose to have > an object_is_valid() function, which returned false if *any* property > had not been initialised to a valid value. > > > PHP actually has a bewildering variety of such special states. In a > different compromise added at the same time, calling unset() on a typed > property puts it into a *separate* state where magic __get and __set are > called, which they are not if the property has simply not yet been > assigned, e.g. https://3v4l.org/C7rIF > > I have always found this a mess. If a property says it is of type > "?int", I want to know that it will always be an integer or null, not > "int or null or uninitialised or unset". If it needs more than one > non-integer state, that should be specified in the type system, e.g. > "int|NotApplicable|NotSpecified|NotLoaded". > > > PS: Etiquette on this list is to post replies below the text you're > replying to, preferably editing to the relevant parts as I've done here, > rather than adding your text above the quoted message. > > Required, > > -- > Rowan Tommins > [IMSoP]
I am unsurprisingly entirely in agreement on the problems of using null as a universal sentinel, and the value of using an enum as a sentinel. However, that breaks down with readonly properties, which are not allowed to have a sentinel. Uninitialized is their sentinel, for better or worse. Ideally, yes, all readonly properties are set by the time the constructor ends, but that's not always feasible. And as I noted earlier in the thread, when writing a serializer or other dynamic systems (an ORM probably would have the same issue), you really need to be able to differentiate between null and uninitialized. Even if you think the uninitialized value is a sign of an error, it's coming from code you don't control so you have to be able to handle it somehow. Some way to more easily check if a property is in one of the various "special states" would be helpful. Even if the number of special states is itself bad (I don't disagree there), they are already there, and making it easier to work around them would be helpful. --Larry Garfield

Rowan Tommins [IMSoP]

2 years ago
On 18 May 2024 17:13:49 BST, Larry Garfield <larry@garfieldtech.com> wrote:
>However, that breaks down with readonly properties, which are not allowed to have a sentinel. Uninitialized is their sentinel, for better or worse.
Sorry, I don't understand this statement at all. A readonly property can be set to PatchState::KeepCurrentValue just like any other. If the intention is that that state will be overwritten with an actual value later, then it's not a readonly property. I guess you have some different scenario in mind?
> And as I noted earlier in the thread, when writing a serializer or other dynamic systems (an ORM probably would have the same issue), you really need to be able to differentiate between null and uninitialized. Even if you think the uninitialized value is a sign of an error, it's coming from code you don't control so you have to be able to handle it somehow.
If a property is uninitialized, the object is in an invalid state, and attempting to read that property gives an error. That's by design, and as it should be. Are you saying that you want to be able to detect the error before it happens? Why? Rowan Tommins [IMSoP]

Larry Garfield

2 years ago
On Sat, May 18, 2024, at 5:41 PM, Rowan Tommins [IMSoP] wrote:
> On 18 May 2024 17:13:49 BST, Larry Garfield <larry@garfieldtech.com> wrote: >>However, that breaks down with readonly properties, which are not allowed to have a sentinel. Uninitialized is their sentinel, for better or worse. > > Sorry, I don't understand this statement at all. A readonly property > can be set to PatchState::KeepCurrentValue just like any other. If the > intention is that that state will be overwritten with an actual value > later, then it's not a readonly property. > > I guess you have some different scenario in mind? > > >> And as I noted earlier in the thread, when writing a serializer or other dynamic systems (an ORM probably would have the same issue), you really need to be able to differentiate between null and uninitialized. Even if you think the uninitialized value is a sign of an error, it's coming from code you don't control so you have to be able to handle it somehow. > > If a property is uninitialized, the object is in an invalid state, and > attempting to read that property gives an error. That's by design, and > as it should be. > > Are you saying that you want to be able to detect the error before it > happens? Why?
For context, remember I maintain a serializer library, so I have to support objects that may indeed be in an invalid state, and depending on the incoming data may not be able to guarantee an object is in a valid state. I have to do everything via reflection and/or visibility-busting closures. I also maintain an attributes library that, necessarily, has multiple methods that get called post-constructor before the object is "ready." Admittedly neither of these are common cases, but neither are they invalid cases. Readonly's current design, and the (IMO, very bad) support from SA tools, works on the assumption that your readonly properties are 1. Based on constructor params; 2. are always guaranteed set after the constructor. While those are true in the majority case, they're not true in the universal case. IOW, "this readonly property is not set by the time the constructor is done, so your object is invalid, so your argument is invalid" is not a fair or accurate statement. And even then, lots of code needs to be able to inspect an object to determine if it is valid or not, even if just to give the user a better error message than "Oops, you tried to read an uninitialized property, we gonna crash now." (As noted, I maintain multiple such libraries. ORMs would be the other big use case, I think.) All of which is to say that, yes, there are use cases for an "is this property initialized" check that is less fugly than the get_object_vars() hack I have to rely on now. --Larry Garfield

Erick de Azevedo Lima

2 years ago
> All of which is to say that, yes, there are use cases for an "is this
property initialized" check that is less fugly than the get_object_vars() hack I have to rely on now. I'd like to see this: if ($objectVar->property is uninitialized) { ... } or/and: if ($objectVar->property is initialized) { ... }
-- Erick de Azevedo Lima Em seg., 20 de mai. de 2024 às 14:28, Larry Garfield <larry@garfieldtech.com> escreveu:

Unnamed Person

2 years ago
On 20-5-2024 19:41, Erick de Azevedo Lima wrote:
> > All of which is to say that, yes, there are use cases for an "is > this property initialized" check that is less fugly than the > get_object_vars() hack I have to rely on now. > > I'd like to see this: > > if ($objectVar->property is uninitialized) { > ... > } > > or/and: > > if ($objectVar->property is initialized) { > ... > } > > -- > Erick de Azevedo Lima > > Em seg., 20 de mai. de 2024 às 14:28, Larry Garfield > <larry@garfieldtech.com <mailto:larry@garfieldtech.com>> escreveu: > > On Sat, May 18, 2024, at 5:41 PM, Rowan Tommins [IMSoP] wrote: > > On 18 May 2024 17:13:49 BST, Larry Garfield > <larry@garfieldtech.com <mailto:larry@garfieldtech.com>> wrote: > >>However, that breaks down with readonly properties, which are > not allowed to have a sentinel. Uninitialized is their sentinel, > for better or worse. > > > > Sorry, I don't understand this statement at all. A readonly > property > > can be set to PatchState::KeepCurrentValue just like any other. > If the > > intention is that that state will be overwritten with an actual > value > > later, then it's not a readonly property. > > > > I guess you have some different scenario in mind? > > > > > >> And as I noted earlier in the thread, when writing a serializer > or other dynamic systems (an ORM probably would have the same > issue), you really need to be able to differentiate between null > and uninitialized. Even if you think the uninitialized value is a > sign of an error, it's coming from code you don't control so you > have to be able to handle it somehow. > > > > If a property is uninitialized, the object is in an invalid > state, and > > attempting to read that property gives an error. That's by > design, and > > as it should be. > > > > Are you saying that you want to be able to detect the error > before it > > happens? Why? > > For context, remember I maintain a serializer library, so I have > to support objects that may indeed be in an invalid state, and > depending on the incoming data may not be able to guarantee an > object is in a valid state. I have to do everything via > reflection and/or visibility-busting closures. I also maintain an > attributes library that, necessarily, has multiple methods that > get called post-constructor before the object is "ready." > Admittedly neither of these are common cases, but neither are they > invalid cases. > > Readonly's current design, and the (IMO, very bad) support from SA > tools, works on the assumption that your readonly properties are > 1. Based on constructor params; 2. are always guaranteed set after > the constructor. While those are true in the majority case, > they're not true in the universal case. > > IOW, "this readonly property is not set by the time the > constructor is done, so your object is invalid, so your argument > is invalid" is not a fair or accurate statement. And even then, > lots of code needs to be able to inspect an object to determine if > it is valid or not, even if just to give the user a better error > message than "Oops, you tried to read an uninitialized property, > we gonna crash now." (As noted, I maintain multiple such > libraries. ORMs would be the other big use case, I think.) > > All of which is to say that, yes, there are use cases for an "is > this property initialized" check that is less fugly than the > get_object_vars() hack I have to rely on now. > > --Larry Garfield >
If we're talking syntax and introducing new keywords anyway, why not go with a new language construct like `is_initialized($obj->property)` ?

Erick de Azevedo Lima

2 years ago
> If we're talking syntax and introducing new keywords anyway, why not go
with a new language construct like `is_initialized($obj->property)` ? It was one of my suggestions at the beginning of the thread. Also, I said that I'd love the "$objectVar->property is uninitialized" (answering to Larry) because of the Pattern Matching RFC ( https://wiki.php.net/rfc/pattern-matching), which adds this "is" checking for lots of stuff and, maybe if Larry and Ilija want it, this "uninitialized" could be one more pattern of this RFC.
-- Erick

Luigi Cardamone

2 years ago
On 18/05/2024 18:08, Rowan Tommins [IMSoP] <imsop.php@rwec.co.uk> wrote:
> > In my opinion - and I stress that others may not share this opinion - > the entire concept of "uninitialized properties" is a wart on the > language, which we should be doing our best to eliminate, not adding > more features around it. > > As a bit of background, the concept was created when typed properties > were being added, to handle a limitation of the language: given the > declaration "public Foo $prop;" there is no way to specify an initial > value which meets the type constraint. For nullable properties, you can > write "public ?Foo $prop=null;" but since PHP (thankfully) distinguishes > nullable and non-nullable types, you can't write "public Foo $prop=null;" > > Some languages, e.g. Swift, require that all properties are initialised > before the constructor returns, but retrofitting this to PHP was > considered impractical, so instead it was left to a run-time error: if > you fail to initialise a property, you will get an error trying to > access it. > > To track that, the engine has to record a special state, but assigning a > meaning to that error state is a bit like using exceptions for flow > control. It would be more in keeping with the original purpose to have > an object_is_valid() function, which returned false if *any* property > had not been initialised to a valid value. > > > PHP actually has a bewildering variety of such special states. In a > different compromise added at the same time, calling unset() on a typed > property puts it into a *separate* state where magic __get and __set are > called, which they are not if the property has simply not yet been > assigned, e.g. https://3v4l.org/C7rIF > > I have always found this a mess. If a property says it is of type > "?int", I want to know that it will always be an integer or null, not > "int or null or uninitialised or unset". If it needs more than one > non-integer state, that should be specified in the type system, e.g. > "int|NotApplicable|NotSpecified|NotLoaded". > >
I get your point. Now I see "uninitialised" as a dangerous thing. Unfortunately this state exists and I don't see any general way to avoid objects with these properties. Fighting the "uninitialised" is something big that we may try to discuss in another thread. We have to deal with this state and a syntax to reliably check those invalid properties will be helpful. Something like "$objectVar->property is uninitialized" will be awesome. Luigi Cardamone

Ilija Tovilo

2 years ago
On Sat, May 18, 2024 at 4:41 PM Rowan Tommins [IMSoP] <imsop.php@rwec.co.uk> wrote:
> > On 18/05/2024 11:52, Luigi Cardamone wrote: > > Are there any downsides in adding a > > specific syntax to check if a property > > is initialized with any value? > > In my opinion - and I stress that others may not share this opinion - > the entire concept of "uninitialized properties" is a wart on the > language, which we should be doing our best to eliminate, not adding > more features around it.
I fully agree. The reason NULL was deemed the "billion-dollar mistake" is not because NULL isn't a useful value, it's because it is implicitly part of types that are commonly never NULL. For example, in C, there's no way through the type system to convey whether a pointer returned from a function may or may not be NULL (ignoring hints through attributes). As such, the user must look at documentation (which may be inaccurate), or even guess whether NULL must be handled. A failure to do so will not result in any compilation errors, but crashes at runtime. Luckily, PHP does not suffer from this issue. null is only allowed when the type says so. However, if you leave your class properties uninitialized, you're essentially recreating this issue. How do you know whether it is safe to access class properties? May they be uninitialized under certain conditions? The type system has no way of conveying this information. There's no way to know, without looking at the implementation. Static analysis won't be able to catch it for you either. So, as Rowan suggested, it is better to hint at this "uninitialized" value that must be handled through the type system, currently most conveniently through single-case enums and union types. Ilija