Re: In php 8.0: unable to distinguish between instance variable being null and unset
| From: | Emiel Mols | Date: | Mon, 06 Sep 2021 07:33:08 +0000 |
| Subject: | Re: In php 8.0: unable to distinguish between instance variable being null and unset | ||
| References: | 1 2 3 4 5 6 | Groups: | php.general |
| Request: | Send a blank email to php-general+get-327656@lists.php.net to get a copy of this message | ||
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 <emiel@crisp.nl> 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 <drealecs@gmail.com>
> wrote:
>
>> 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
>>>>>> <ûmçc”ï:w¼
>>>>>> ²J…±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: 7J¥!ż: ½—áóU
>>>>> 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
>>>>>
>>>>