Re: In php 8.0: unable to distinguish between instance variable being null and unset
[email protected] (Emiel Mols)
| Newsgroups | php.general |
|---|---|
| Message-ID | <CAF5w5060NV8Hr17RGgK0Qkw-QxT3ZhfJmJRqPsT6u6W11n-afw@mail.gmail.com> |
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
}
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
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).
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 <[email protected]>
wrote:
>
> On Sat, Sep 4, 2021 at 11:26 PM Alexandru Pătrănescu <[email protected]>
> wrote:
>
>> Hey Emiel,
>>
>>
>> On Fri, Sep 3, 2021 at 3:09 PM Emiel Mols <[email protected]> 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
>>> <https://github.com/php/php-src/blob/a13730c5e465b3c349a7970d15a49e4e132d4e07/Zend/zend_builtin_functions.c#L943>
>>> .
>>>
>>> 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.0
> Is equivalent with *array_key_exists('propName', (array)$object);*
> starting with 8.0
> Of 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/7L3qP
>
>
>> Another 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_objects
>>
>>
>>>
>>> Best,
>>>
>>> Emiel
>>>
>>
>> Alex
>>
>