Re: In php 8.0: unable to distinguish between instance variable being null and unset

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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@xxxxxxxx> 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@xxxxxxxxx> wrote:
Hey Emiel,

On Sun, Sep 5, 2021 at 3:13 PM Emiel Mols <emiel@xxxxxxxx> 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@xxxxxxxxx> wrote:

On Sat, Sep 4, 2021 at 11:26 PM Alexandru Pătrănescu <drealecs@xxxxxxxxx> wrote:
Hey Emiel,


On Fri, Sep 3, 2021 at 3:09 PM Emiel Mols <emiel@xxxxxxxx> 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.

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 

[Index of Archives]     [PHP Home]     [Apache Users]     [PHP on Windows]     [Kernel Newbies]     [PHP Install]     [PHP Classes]     [Pear]     [Postgresql]     [Postgresql PHP]     [PHP on Windows]     [PHP Database Programming]     [PHP SOAP]

  Powered by Linux