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
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
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
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
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
ItalyI 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
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
ItalyIl 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
ItalyI 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:
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
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
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 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 NotSetAn 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/C7rIFI 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
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]
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
All of which is to say that, yes, there are use cases for an "is this
property initialized" check that is less fugly than theget_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:
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 theget_object_vars()
hack I have to rely on now.--Larry Garfield
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 LimaEm seg., 20 de mai. de 2024 às 14:28, Larry Garfield
<larry@garfieldtech.com mailto:larry@garfieldtech.com> escreveu:> 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)
?
If we're talking syntax and introducing new keywords anyway, why not go
with a new language construct likeis_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
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/C7rIFI 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
On Sat, May 18, 2024 at 4:41 PM Rowan Tommins [IMSoP]
imsop.php@rwec.co.uk 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