Re: In php 8.0: unable to distinguish between instance variable being null and unset
| From: | Alexandru Pătrănescu | Date: | Sun, 05 Sep 2021 13:33:41 +0000 |
| Subject: | Re: In php 8.0: unable to distinguish between instance variable being null and unset | ||
| References: | 1 2 3 4 | Groups: | php.general |
| Request: | Send a blank email to php-general+get-327654@lists.php.net to get a copy of this message | ||
Hey Emiel,
On Sun, Sep 5, 2021 at 3:13 PM Emiel Mols <emiel@crisp.nl> 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 <drealecs@gmail.com>
> wrote:
>
>>
>> On Sat, Sep 4, 2021 at 11:26 PM Alexandru Pătrănescu <drealecs@gmail.com>
>> wrote:
>>
>>> Hey Emiel,
>>>
>>>
>>> On Fri, Sep 3, 2021 at 3:09 PM Emiel Mols <emiel@crisp.nl> 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
>>>
>>