Hi,
As of today, readonly properties cannot have a default value:
class UltimateQuestion {
readonly int $answer = 42; // Fatal error: Readonly property Question::$answer cannot have default value
}
The rationale given in the original RFC (https://wiki.php.net/rfc/readonly_properties_v2) was:
As the default value counts as an initializing assignment, a readonly property with a default value is essentially the same as a constant, and thus not particularly useful. The notion could become more useful in the future, if new expressions are allowed as property default values. At the same time, depending on how exactly property initialization would work in that case, having a default value on a readonly property could preclude userland serialization libraries from working, as they would not be able to replace the default-constructed object. Whether or not this is a concern depends on whether the property is initialized at time of object creation, or as an implicit part of the constructor (or similar). As these are open questions, the conservative choice is to forbid default values until these questions are resolved.
A new fact is that properties will be able to be declared in interface since PHP 8.4 (as introduced in the recently accepted property hooks RFC). It is true that, for an individual class, a readonly property is functionally equivalent to a constant. But my class implements an interface (or, before 8.4, follows a non-written protocol), and whether that particular implementation choose to always hold the same value for a specific property is irrelevant.
interface Question {
public int $answer { get; }
}
class UltimateQuestion implements Question {
readonly int $answer = 42;
}
Therefore I think it is reasonable for readonly properties to accept default values.
About the concern given in the original RFC: “having a default value on a readonly property could preclude userland serialization libraries from working, as they would not be able to replace the default-constructed object”... TBH, as I almost never serialise my objects, and even less often customise serialisation, I can only guess what the effective issue is. My best guess is that a custom __unserialize()
method would not be able to reinstate the incriminated readonly properties, because those would already have been initialised with the default value. If this is the case, a solution is to do for __uninitialize()
the same thing that we have done for __clone()
, see: https://wiki.php.net/rfc/readonly_amendments#proposal_2readonly_properties_can_be_reinitialized_during_cloning
—Claude
On Friday, 7 June 2024 at 12:11, Claude Pache claude.pache@gmail.com
wrote:
Hi,
As of today, readonly properties cannot have a default value:
class UltimateQuestion { readonly int $answer = 42; // Fatal error: Readonly property Question::$answer cannot have default value }
The rationale given in the original RFC (
https://wiki.php.net/rfc/readonly_properties_v2) was:As the default value counts as an initializing assignment, a readonly
property with a default value is essentially the same as a constant, and
thus not particularly useful. The notion could become more useful in the
future, if new expressions are allowed as property default values. At the
same time, depending on how exactly property initialization would work in
that case, having a default value on a readonly property could preclude
userland serialization libraries from working, as they would not be able to
replace the default-constructed object. Whether or not this is a concern
depends on whether the property is initialized at time of object creation,
or as an implicit part of the constructor (or similar). As these are open
questions, the conservative choice is to forbid default values until these
questions are resolved.A new fact is that properties will be able to be declared in interface
since PHP 8.4 (as introduced in the recently accepted property hooks RFC).
It is true that, for an individual class, a readonly property is
functionally equivalent to a constant. But my class implements an interface
(or, before 8.4, follows a non-written protocol), and whether that
particular implementation choose to always hold the same value for a
specific property is irrelevant.interface Question { public int $answer { get; } } class UltimateQuestion implements Question { readonly int $answer = 42; }
Therefore I think it is reasonable for readonly properties to accept
default values.About the concern given in the original RFC: “having a default value on a
readonly property could preclude userland serialization libraries from
working, as they would not be able to replace the default-constructed
object”... TBH, as I almost never serialise my objects, and even less often
customise serialisation, I can only guess what the effective issue is. My
best guess is that a custom__unserialize()
method would not be able to
reinstate the incriminated readonly properties, because those would already
have been initialised with the default value. If this is the case, a
solution is to do for__uninitialize()
the same thing that we have done
for__clone()
, see:
https://wiki.php.net/rfc/readonly_amendments#proposal_2readonly_properties_can_be_reinitialized_during_cloning—Claude
Hi, Claude!
Thank you for bringing up this issue! I agree that the phrase "a readonly
property with a default value is essentially the same as a constant" is not
correct for all cases. For example, I might have a class like this:
final readonly class ObjectCollector
{
private SplObjectStorage $storage;
public function __construct()
{
$this->storage = new SplObjectStorage();
}
public function add(object $object): void
{
$this->storage->attach($object);
}
}
(new ObjectCollector())->add(new stdClass());
It's a fully working example (https://3v4l.org/dSpQO) where $storage
can
not be replaced with a constant. And it would be nice if $storage
could
be initialized without a constructor.
I just looked at some of my PHP >=8.1 projects and noticed that I have a
few places where the constructor has no parameters and only exists to
initialize readonly properties with smth like SplQueue
.
Valentin Udaltsov
Hi,
As of today, readonly properties cannot have a default value:
class UltimateQuestion { readonly int $answer = 42; // Fatal error: Readonly property Question::$answer cannot have default value }
The rationale given in the original RFC
(https://wiki.php.net/rfc/readonly_properties_v2) was:As the default value counts as an initializing assignment, a readonly property with a default value is essentially the same as a constant, and thus not particularly useful. The notion could become more useful in the future, if new expressions are allowed as property default values. At the same time, depending on how exactly property initialization would work in that case, having a default value on a readonly property could preclude userland serialization libraries from working, as they would not be able to replace the default-constructed object. Whether or not this is a concern depends on whether the property is initialized at time of object creation, or as an implicit part of the constructor (or similar). As these are open questions, the conservative choice is to forbid default values until these questions are resolved.
A new fact is that properties will be able to be declared in interface
since PHP 8.4 (as introduced in the recently accepted property hooks
RFC). It is true that, for an individual class, a readonly property is
functionally equivalent to a constant. But my class implements an
interface (or, before 8.4, follows a non-written protocol), and whether
that particular implementation choose to always hold the same value for
a specific property is irrelevant.interface Question { public int $answer { get; } } class UltimateQuestion implements Question { readonly int $answer = 42; }
Therefore I think it is reasonable for readonly properties to accept
default values.About the concern given in the original RFC: “having a default value on
a readonly property could preclude userland serialization libraries
from working, as they would not be able to replace the
default-constructed object”... TBH, as I almost never serialise my
objects, and even less often customise serialisation, I can only guess
what the effective issue is. My best guess is that a custom
__unserialize()
method would not be able to reinstate the
incriminated readonly properties, because those would already have been
initialised with the default value. If this is the case, a solution is
to do for__uninitialize()
the same thing that we have done for
__clone()
, see:
https://wiki.php.net/rfc/readonly_amendments#proposal_2readonly_properties_can_be_reinitialized_during_cloning—Claude
It's also something I'd have to work around in Serde, and other user-space serializers would have to as well. So it would mean work for serializers, not all of which use __unserialize().
Also, if aviz passes, a public private(set) property has no such restriction and can serve virtually the same role without any of the limitations of readonly.
Hooks also offer another workaround already:
class UltimateQuestion implements Question {
public int $answer { get => 42)
}
That would have the same net result (at a very small performance hit for the hook invocation), without any compilation issues. And it doesn't give serializers any more work to do than they already will have to do for hooks and virtual properties.
So while I agree that limitation is annoying, I think there are enough ways around it now and hopefully soon if aviz passes that it's more trouble than it's worth to try and address directly.
--Larry Garfield