Hi,
After posting this in the issues list
https://github.com/php/php-src/issues/10537, I was asked to mail it here.
This is my first time here, so I hope I do it the right way.
At last I have the opportunity to start using php8, I am very happy with it
and how mature it has become; implementing all kinds of code quality.
But this new uninitialized state is really a pain.
When you set a property in a class, I consider it as set into the class; it
is part of that class, that object.
Whether it is undefined or not. You could access it with an easy if or
ternary operator.
And you got a clean overview of all your properties and how they are
initialized.
class Test {
protected string $name;
protected string $type = 'mytype';
protected ?array $arrLog;
protected ?string $optionalVar;
protected string $table = 'category';
protected bool $isOk = false;
}
This looks clean and gives clear, easy insight to the purpose of each
property.
But since php8, you have to use isset() or empty() to check whether a
property is initialized.
Also php is offering more and more shorthand alternatives like nullsafe
operator, ternary operator etc. Which I was not in favor of, but it seems
to be the standard nowadays, so I let phpstorm do its thing and use the
shorthands.
But when you start using the (very necessary!!!) typed properties this is
not working anymore unless you 'initialize' the property with at least an
empty or dummy value or null.
I read the RFC trying to understand.....
Maybe it is technically difficult or it is against high-standard rules
(which I love!), but this is really unpractical and gives to much overhead
and clutter.
I love good quality in coding, I endured years of being called crazy by
colleagues who learned php by google and stackoverflow and loved it because
it allowed all the bad coding. (Good is good enough)
But now it seems to go over the top, making php less practical.
For this uninitialized-thing we can choose one the following solutions at
the moment:
- leave properties untyped, which is really bad
- set many properties to null or other empty values, which clutters your
property list - add those empty values inside the constructor, which creates useless
extra lines of code - use isset or empty to check if it is set or not, and not be able to
use shorthands
To use typed properties and profit from all the new shorthands and nice new
feature of php 8, my class has to be something like:
class Test {
protected string $name = '';
protected string $type = 'mytype';
protected ?array $arrLog = [];
protected ?string $optionalVar = null;
protected string $table = 'category';
protected bool $isOk = false;
}
This looks cluttered. And I do not understand that we get all those nice
shorthands, but for typed properties, we need to type more....
Why does a nullable type not automaticly default to null, or even to the
empty variant of the type (by giving it not an question-mark but the
exclamation mark, maybe...) like:
class Test {
protected !string $name; // defaults to ''
protected string $type = 'mytype';
protected !array $arrLog; // defaults to []
protected ?string $optionalVar; // defaults to null
protected string $table = 'category';
protected !bool $isOk; // defaults to false
protected !float $someNumber; // default to 0
protected ?myObject $oObject; // no default for objects... to
complicated and risky, or not??, so only nullable
}
I hope this can be considered again.
Greetz, flexJoly (Lydia de Jongh)
https://www.linkedin.com/in/flexjoly/
https://divaeloper.wordpress.com/
Hi Lydia,
I understand where you are coming from because I encountered this a few
times myself, but every time it was actually because I was doing something
wrong. The reason for this limitation is actually very simple and logical.
Let me explain.
When you use untyped properties, the type is not restricted so every
property can be nullable by default. It's very simple for the language to
have NULL
as the default value for all untyped properties.
When using typed properties, the language cannot use NULL
as the default
anymore because the type might not allow NULL, e.g public string $name
allows only string values. Unless you assign some string value to it, the
language will not initialize it with any value. If you need the property to
have a state indicating no value, you can make it nullable and assign NULL
as the default value.
What you are proposing is actually not acceptable from the type system's
point of view. We cannot add a new syntax for assigning default values to
type properties. It might make sense in a very limited scope, e.g. public !
int $number; defaults to 0, but in any other more complex scenario, this
fails miserably. Consider this example:
class Test {
public ! MyObject&SomeInterface $prop1;
public ! int|false|null $prop2;
}
In the example above, what default values could be assigned to the
properties? The correct answer is none. You cannot have a default object,
nor can you pick a default value from the union of three types. There is
simply no way to determine the default value. So if we had a new syntax
like this, it would be severely limited to only the simplest scenarios.
This would create a new inconsistency for the benefit of using a single
character instead of assigning the value. There would be no benefit to
introducing such inconsistency to the language.
It is the job of the developer to specify the default type. Some things can
be left for PHP to infer, but when it comes to the default property value,
the developer needs to assign it.
You said that an isset is necessary when using typed properties. I would
argue that an isset is not needed. Using isset with typed properties is a
code smell in many circumstances. If the property can be unassigned, the
developer should use a sentinel value such as null to indicate this. If the
property is always expected to hold a value, then the code should not allow
a path to access it before it's initialized.
Kind Regards,
Kamil
Hi Kamil,
Thanks for your reply.
With regards to the automatic default, of course I can only agree!
Indeed isset() should not be needed for declared properties!
But because of the typed properties, you have to initialize them before you
can access them. I think that is cluttering up the code.
From a programmer's perspective, the property is declared and should be
accessible. Even if it has no value.
There should be no difference between typed and none-typed properties for
this.
Greetz, Lydia
Op wo 8 feb. 2023 om 15:58 schreef Kamil Tekiela tekiela246@gmail.com:
Hi Lydia,
I understand where you are coming from because I encountered this a few
times myself, but every time it was actually because I was doing something
wrong. The reason for this limitation is actually very simple and logical.
Let me explain.
When you use untyped properties, the type is not restricted so every
property can be nullable by default. It's very simple for the language to
haveNULL
as the default value for all untyped properties.
When using typed properties, the language cannot useNULL
as the default
anymore because the type might not allow NULL, e.g public string $name
allows only string values. Unless you assign some string value to it, the
language will not initialize it with any value. If you need the property to
have a state indicating no value, you can make it nullable and assignNULL
as the default value.What you are proposing is actually not acceptable from the type system's
point of view. We cannot add a new syntax for assigning default values to
type properties. It might make sense in a very limited scope, e.g. public !
int $number; defaults to 0, but in any other more complex scenario, this
fails miserably. Consider this example:class Test {
public ! MyObject&SomeInterface $prop1;
public ! int|false|null $prop2;
}In the example above, what default values could be assigned to the
properties? The correct answer is none. You cannot have a default object,
nor can you pick a default value from the union of three types. There is
simply no way to determine the default value. So if we had a new syntax
like this, it would be severely limited to only the simplest scenarios.
This would create a new inconsistency for the benefit of using a single
character instead of assigning the value. There would be no benefit to
introducing such inconsistency to the language.It is the job of the developer to specify the default type. Some things
can be left for PHP to infer, but when it comes to the default property
value, the developer needs to assign it.You said that an isset is necessary when using typed properties. I would
argue that an isset is not needed. Using isset with typed properties is a
code smell in many circumstances. If the property can be unassigned, the
developer should use a sentinel value such as null to indicate this. If the
property is always expected to hold a value, then the code should not allow
a path to access it before it's initialized.Kind Regards,
Kamil
From a programmer's perspective, the property is declared and should be
accessible. Even if it has no value.
There should be no difference between typed and none-typed properties for
this.
I've actually been considering a proposal to remove this difference the other way around: if a property is declared but never assigned a value, consistently giving an error on access, regardless of whether a type was declared or not.
Currently, properties can be in a number of different states: declared and assigned, undeclared, undeclared but created dynamically by assignment, declared without type and not yet assigned, declared with type and not yet assigned (Uninitialized), declared without type but then unset (distinct from both the unassigned and Uninitialized states) ... possibly other combinations I've forgotten.
Now that we have the Uninitialized state, and have deprecated dynamic properties, this could mostly be reduced to two: has a current valid value, or Uninitialized. But the details of what would need to change and when are the subject for a future discussion.
Regards,
--
Rowan Tommins
[IMSoP]
Hi,
After posting this in the issues list
https://github.com/php/php-src/issues/10537, I was asked to mail it here.
This is my first time here, so I hope I do it the right way.
At last I have the opportunity to start using php8, I am very happy with it
and how mature it has become; implementing all kinds of code quality.But this new uninitialized state is really a pain.
When you set a property in a class, I consider it as set into the class; it
is part of that class, that object.
Whether it is undefined or not. You could access it with an easy if or
ternary operator.And you got a clean overview of all your properties and how they are
initialized.class Test {
protected string $name;
protected string $type = 'mytype';
protected ?array $arrLog;
protected ?string $optionalVar;
protected string $table = 'category';
protected bool $isOk = false;
}This looks clean and gives clear, easy insight to the purpose of each
property.
But since php8, you have to use isset() or empty() to check whether a
property is initialized.Also php is offering more and more shorthand alternatives like nullsafe
operator, ternary operator etc. Which I was not in favor of, but it seems
to be the standard nowadays, so I let phpstorm do its thing and use the
shorthands.But when you start using the (very necessary!!!) typed properties this is
not working anymore unless you 'initialize' the property with at least an
empty or dummy value or null.
I read the RFC trying to understand.....Maybe it is technically difficult or it is against high-standard rules
(which I love!), but this is really unpractical and gives to much overhead
and clutter.
I love good quality in coding, I endured years of being called crazy by
colleagues who learned php by google and stackoverflow and loved it because
it allowed all the bad coding. (Good is good enough)But now it seems to go over the top, making php less practical.
For this uninitialized-thing we can choose one the following solutions at
the moment:
- leave properties untyped, which is really bad
- set many properties to null or other empty values, which clutters your
property list- add those empty values inside the constructor, which creates useless
extra lines of code- use isset or empty to check if it is set or not, and not be able to
use shorthandsTo use typed properties and profit from all the new shorthands and nice new
feature of php 8, my class has to be something like:class Test {
protected string $name = '';
protected string $type = 'mytype';
protected ?array $arrLog = [];
protected ?string $optionalVar = null;
protected string $table = 'category';
protected bool $isOk = false;
}This looks cluttered. And I do not understand that we get all those nice
shorthands, but for typed properties, we need to type more....Why does a nullable type not automaticly default to null, or even to the
empty variant of the type (by giving it not an question-mark but the
exclamation mark, maybe...) like:class Test {
protected !string $name; // defaults to ''
protected string $type = 'mytype';
protected !array $arrLog; // defaults to []
protected ?string $optionalVar; // defaults to null
protected string $table = 'category';
protected !bool $isOk; // defaults to false
protected !float $someNumber; // default to 0
protected ?myObject $oObject; // no default for objects... to
complicated and risky, or not??, so only nullable
}I hope this can be considered again.
Hi Lydia,
I personally appreciate your concern. I program mostly in GoLang these days, its variables and struct properties are initialized to a "zero" value by default, and that works extremely well and cuts down on an entire class of errors.
However, I know this is not what you wanted — and it is likely you already know how to do this — but in case this workaround had not already occurred to you the following is a proof-of-concept that might reduce the annoyance of having to initialize all your typed properties, at least somewhat, given that the list does not appear keen to address your concerns.
This workaround uses a base AutoInit
class with a constructor that uses reflection to inspect the properties and if they are uninitialized and without a default get their type and then initialize them to an appropriate "zero" value.
Yes it uses reflection which is a bit slow, but you may or may not find that the performance drain a concern, given your use-cases.
Also, it is just a proof-of-concept, so you may end up needing to fix edge-case bugs or tweak for your use-cases.
Hope this helps.
-Mike
P.S. This workaround is also a great use-case for a feature I have always wanted in PHP which is the ability for a parent constructor to always be called if not explicitly specified otherwise. Too bad for this workaround that that feature does not already exist.
<?php
class AutoInit {
function __construct() {
$reflector = new \ReflectionObject($this);
foreach($reflector->getProperties() as $prop) {
if ($prop->isInitialized($this)) {
continue;
}
if ($prop->hasDefaultValue()) {
continue;
}
$type = $prop->getType();
if ($type->allowsNull()) {
$type = "null";
} else {
$type = $type->getName();
}
switch ($type){
case "string":
$value = "";
break;
case "array":
$value = [];
break;
case "bool":
$value = false;
break;
case "float":
$value = 0.0;
break;
case "int":
$value = 0;
break;
case "null":
$value = NULL;
break;
default:
continue 2;
}
$prop->setAccessible(true);
$prop->setValue($this,$value);
}
}
}
class Test extends AutoInit {
protected string $name;
protected int $age;
protected float $latitude;
protected float $longitude;
protected string $type = 'unknown';
protected ?array $arrLog;
protected ?string $optionalVar;
protected string $table;
protected bool $isOk;
protected array $labels;
function __construct() {
parent::__construct();
}
}
$t = new Test();
var_export($t);
// END