Referencing a child bean in a null coalescing operation

29 views
Skip to first unread message

Aaron Cooper

unread,
Aug 16, 2022, 2:06:26 AM8/16/22
to redbeanphp
Version 5.7

I'm fairly sure this is something to do with lazy loading child beans. But I wanted to see if there is a more detailed explanation for this. Take this code, dispensing two beans and assigning one to a field for the other. Then later I am doing a check for data in a field within the child bean:

```
$importer = R::findOne('importer', 'id = ?', [$importerId]);
$importer->myfield = 'foo';
R::store($importer);

$importerLog = R::findOrDispense('importerlog', 'id = ?', [$importerLogId]);
$importerLog = reset($importerLog);
$importerLog->importer = $importer;
R::store($importerLog);

$data = $importerLog->importer->myfield ?? 'empty';
```
In the above case, $data on the last line is always 'empty'. However, if I remove the null coalesce check, it is always 'foo' (as set above it).

I also found that if I reference $importerLog->importer just before that line in any way (even a manual debug evaluation), even the null coalesce works as expected. Which is what led me to believe lazy loading is the culprit and the comparison is being made before Redbean loads the child bean.

Any further detail on this? Better patterns to use? As it stands it seems we shouldn't check the value of a child bean field before it's loaded into a variable or used in another way?

Cheers
Aaron

Aaron Cooper

unread,
Aug 16, 2022, 5:45:47 AM8/16/22
to redbeanphp
I did a bit more testing on this.

isset() exhibits the same behaviour. 

if (!isset($importerLog->importer->myfield)) {
     $result = "It's not set. Except it's: " . $importerLog->importer->myfield;
} else {
    $result = "It's set";
}

Outputs "It's not set. Except it's: foo"

is_null does not exhibit the same.

if(is_null($importerLog->importer->myfield)) {
    $nullResult = "It's null";
} else {
    $nullResult = "It's not null";
}

Outputs: "It's not null"

Aaron Cooper

unread,
Aug 19, 2022, 7:40:04 PM8/19/22
to redbeanphp
For future reference. I worked out the explanation and it makes some sense.

1. $importerLog->importer is actually a method call (a magic method getter)
2. You can't pass an executable to isset(). It's a fatal error.
3. Doing so doesn't emit a fatal error if the variable is a bean, because OODBBean.php overloads isset() with __isset(), but this does not iterate into loading child beans. This could make sense from a performance point a view considering there could be a variable hierarchy of of beans (grandchildren, great-grandchildren etc).
4. is_null() has no such problem as, unlike isset(), it executes functions and methods.
5. ?? always returns false on child beans, as it is just a helper for calling isset() and !is_null(), and isset() is called first. I actually found that the long form of ??, with is_null executed first, works as expected. This is because is_null() executes functions and methods and does the work of loading the child bean before isset() runs.

Solution so far seems to be a pattern of ensuring that we load the child bean into a variable before we attempt to use it.

gabor

unread,
Aug 20, 2022, 4:23:28 AM8/20/22
to redbeanphp
Hi Aaron,

Sorry for my late reply but I am extremely busy at the moment.

Yes, the ??-operator works in an annoying way with RedBeanPHP.
I don't think it is possible for me to change the __isset() call in the bean, because everything depends on it.
Anyway, I think we can easily fix this by adding a method that allows for coalescing like:
$importerLog->getImporter()->getMyField()->or('empty');
or:
$imporerLog->coalesce()->importer->myfield ?? 'empty';
or something like that. Any preference?

cheers,
Gabor
Reply all
Reply to author
Forward
0 new messages