Re: In php 8.0: unable to distinguish between instance variable being null and unset
[email protected] (Alexandru Pătrănescu)
| Newsgroups | php.general |
|---|---|
| Message-ID | <CAAwdEzDKyZP1Fqhc69jbtw5p-taFmJN7vPoRWo-D+SWR368oaQ@mail.gmail.com> |
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 >>> >>