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 >>>> >>>