Thanks for the detailed answer Alexandru. Indeed our use case is an ActiveRecord pattern where we lazily deserialize complex (json) variables only when accessed. This is a performance as we have quite wide objects and deserialization is quite expensive (100s of fields, and only need a few); simplified/pseudo:
abstract class Record {
public function hydrate(array $data) {
$this->rawData = $data;
foreach (static::keys() as $key) { unset($this->{$key}); } // unset indicates they need to be populated from rawData later
foreach (static::keys() as $key) { unset($this->{$key}); } // unset indicates they need to be populated from rawData later
}
public function __get(string $key) {
$this->{$key} = static::deserialize($key); // can be expensive-ish
public function __get(string $key) {
$this->{$key} = static::deserialize($key); // can be expensive-ish
return $this->{$key};
}
public function changedValues(): array {
$changed = [];
foreach (static::keys() as $key) {
// magical isUnset we don't have right now - should return false when null
// magical isUnset we don't have right now - should return false when null
if (!isUnset($this->{$key}}) {
$changed[$key] = $this->{$key};
}
}
}
return $changed;
}
}
After your suggestion that array_key_exists($k, (array)$object) has the same behavior, I had a look into the pre-8.0 code <https://github.com/php/php-src/commit/c46b2ed677aecfc2f07993eefad0326f31c5cc44>. Indeed it seems to do exactly the same (namely convert to array first, then do the operation), but that means in our case the operation is unnecessarily expensive (also on 7.4): we're converting to those arrays (with 100s of elements) all the time. And we would be a lot better off if we have a cheaper check to see if a field is actually set on an object instance (ignoring whether it is defined).
After your suggestion that array_key_exists($k, (array)$object) has the same behavior, I had a look into the pre-8.0 code <https://github.com/php/php-src/commit/c46b2ed677aecfc2f07993eefad0326f31c5cc44>. Indeed it seems to do exactly the same (namely convert to array first, then do the operation), but that means in our case the operation is unnecessarily expensive (also on 7.4): we're converting to those arrays (with 100s of elements) all the time. And we would be a lot better off if we have a cheaper check to see if a field is actually set on an object instance (ignoring whether it is defined).
In terms of a suggestion to extend an existing function to support this: property_exist($object, $key, $ignoreDefined=false). When the $ignoreDefined, the path that checks definition on the class could be skipped <https://github.com/php/php-src/blob/a13730c5e465b3c349a7970d15a49e4e132d4e07/Zend/zend_builtin_functions.c#L943>. Not sure about the comprehensibility of such a flag, though.
Thanks!
On Sun, Sep 5, 2021 at 5:51 AM Alexandru Pătrănescu <drealecs@xxxxxxxxx> wrote:
On Sat, Sep 4, 2021 at 11:26 PM Alexandru Pătrănescu <drealecs@xxxxxxxxx> wrote:Hey Emiel,On Fri, Sep 3, 2021 at 3:09 PM Emiel Mols <emiel@xxxxxxxx> wrote:Hello,Our codebase depends on distinguishing whether an instance variable is unset, or simply null:Can you please share more on how you use this information? Is it for lazy loading something that might not be loadable in the end?class C { public int $v = 123; }$x = new C; unset($x->v);$y = new C; $u->v = null;In php <8.0 we made this work with @array_key_exists($x, 'v'), which stopped working. isset($x->x) doesn't work, as it will return true for 'null' as well. property_exists($x, 'v') will always return true as long as the variable is declared: php-src here.Apart from using debug_zval_dump (too expensive), any hints?I think the simplest solution would be to cast the object to an array and you will see that the unset property will not be a key in the array while the null one will be.Just to have it clear for anyone that might be following the thread later:Basically array_key_exists('propName', $object); until 8.0Is equivalent with array_key_exists('propName', (array)$object); starting with 8.0Of course, this fix should be applied starting with PHP 7.4 to remove the deprecation warning, not hide it with the silent operator.You can check how it works based on your last example: https://3v4l.org/7L3qPAnother way to go here would be to use the magic method __get() that would be called on the unset property. But not sure how that fits with your use case.Also something like this could work: https://3v4l.org/7NL0Y, trying to read the property and handling the Error thrown.But it all depends on how you use this... Please share more to help you correctly.This feels like quite the oversight honestly :).This feels like quite a special use case that could be implemented differently to start with. Please share more information so we can help better.I guess you have read the rfc that deprecated this in 7.4 and scheduled for later removal: https://wiki.php.net/rfc/deprecations_php_7_4#array_key_exists_with_objectsBest,EmielAlex