Re: In php 8.0: unable to distinguish between instance variable being null and unset

[email protected] (Emiel Mols)
Newsgroups php.general
Message-ID <CAF5w504Jx+NUjce2eUek7BbUoLKT_VwN7m6qHqvp1JNt24TjcA@mail.gmail.com>
Some values can be null! So that's why isset won't work.

On Sun, 5 Sep 2021 at 15:33, Alexandru Pătrănescu <[email protected]>
wrote:

> Hey Emiel,
>
> On Sun, Sep 5, 2021 at 3:13 PM Emiel Mols <[email protected]> wrote:
>
>> 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;
>> }
>> }
>>
>
> Thanks for providing the sample code.
>
> If you change the method to something like this, I'm pretty sure it's
> going to be similarly fast on PHP 7.4, possibly even faster:
>
> public function changedValues(): array {
>     $changed = [];
>     $values = (array)$this;
>     foreach (static::keys() as $key) {
>         if (isset($values[$key])) {
>             $changed[$key] = $values[$key];
>         }
>     }
>     return $changed;
> }
>
>
> Just give it a try!
>
> Regards,
> Alex
>
>
>> 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
>>>>
>>>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.