Re: In php 8.0: unable to distinguish between instance variable being null and unset
[email protected] (Emiel Mols)
| Newsgroups | php.general |
|---|---|
| Message-ID | <CAF5w504GNZZK2sdpSyZu4deDKU0zJkxb1tXWzW1TPQkUu1xB-A@mail.gmail.com> |
Indeed with a cast+array_key_exists we managed to make this working in 8.0. Thanks! Still, it feels a bit hacky. Would be nice to have an object_property_exists() that has identical behavior to array_key_exists(). - Some implementors of Record could implement ArrayAccess. Will conversion semantics hold? (I believe they do) - It would be nice to be able to check this for protected fields (they are converted as "\x0000*\x0000$key" keys in the casted array right now?) On Sun, Sep 5, 2021 at 5:50 PM Emiel Mols <[email protected]> wrote: > 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 >>>>> >>>>