Description
Description
(I hope this is the right place to suggest this, if not I'm happy to move it elsewhere.)
In the new property hooks feature of PHP 8.4, I think it would be very useful to be able to unset()
backed properties.
Consider the following object, a forums post with a user object representing the poster which is lazy-loaded with a getter:
<?php
class ForumsPost{
public int $UserId;
public User $User{
get{
if(!isset($this->User)){
$this->User = Db::GetUser($this->UserId);
}
return $this->User;
}
}
}
$fp = new ForumsPost();
$fp->UserId = 1;
print($fp->User->UserId); // `User` is retrieved from the database and `1` is printed.
Now suppose we have already retrieved the User
property, and later we change the UserId
. Now the UserId
property and the User
property are now out of sync. What if we could add a setter hook for the UserId
property, to unset()
any cached User
object at the moment when we set the UserId
? For example:
<?php
class ForumsPost{
public int $UserId{
set(int $value){
// Set the new `UserId`...
$this->UserId = $value;
// And unset any cached `User` we may have fetched earlier...
unset($this->User); // Error! Not allowed in PHP 8.4, but natural to think about and very convenient!
}
}
public User $User{
get{
if(!isset($this->User)){
$this->User = Db::GetUser($this->UserId);
}
return $this->User;
}
}
}
$fp = new ForumsPost();
$fp->UserId = 1;
print($fp->User->UserId); // `User` is retrieved from the database and `1` is printed.
$fp->UserId = 2;
print($fp->User->UserId); // Expected to print `2`, but actually is an error because we cannot `unset()` the backed `User` property.
The current state of affairs means that if we want to return a backed property to a "not initialized" state, we have to use kludges like making it a nullable type and calling null
"not initialized" by convention, or setting it to say an empty string and calling that "not initialized" by convention. Both of these are poor, semantically wrong solutions, especially considering it's already allowed to unset()
plain object properties directly.
Additionally, the consumer of an object might find the following behavior surprising:
<?php
$fp = new ForumsPost();
unset($fp->UserId); // Success!
unset($fp->User); // Error! Huh, why? (`$User` is a property hook...)
The consumer of an object should not need to know the internal implementation of properties. In this example it is well-established in PHP that we can unset()
object properties, but since plain properties and property hooks are indistinguishable to the consumer, unset()
on a property can result in an error if the internal implementation is a property hook. This is surprising to the consumer, who doesn't and shouldn't care how the property is implemented internally.
In the RFC for property hooks, unset()
is explicitly disallowed because "it would involve bypassing any set hook that is defined". But I'm not sure why unset()
ing something would invoke the setter.