Enable/disable virtual fields ($_virtual) at runtime

I have some heavy-lifting occurring in my entity’s accessors but only need these for drawing a complex SVG chart that I’m building. I’m loading the data into the DOM via AJAX so these virtual fields are in my $_virtual class member. However, sometimes I just need the basic db fields and not any of the calculated values from my accessors.

How can I “turn off” these values on a per-query basis?

I’ve tried just selecting the ones I need (via fields key in a contain), but the virtual ones still come back to me.

1 Like

I assume you are sending serialized (JSON) response. To prevent the virtual fields from being included in serialized data set them as hidden using Entity::setHidden().

Perhaps I’m not using it correctly, but it doesn’t work as I expect. My test case:

Entity:

$_virtual = ['my_virtual_field'];

protected function _myVirtualField()
{
    # Running some complex operations.
    return 'value';
}

Controller:

$entity = $this->Entities->newEntity();
$entity->setHidden(['my_virtual_field']);
dd($entity);

Output:

{
    'my_virtual_field' => 'value',
    ...
}

I also tried creating a new entity like this:

$entity = $this->Entities->newEntity([]);

…but I get the same result.

Check dd($entity->toArray()) or dd(json_encode($entity)) instead of dd($entity).

Ok debugging with toArray() shows that the property isn’t there, but my db queries in that accessor are still executing. So I’m wondering if there’s a way to simply turn this off on a case by case basis? It’s looking like there isn’t, however.

Thanks for your help.

Looks like adding the following to the top of my accessor logic does the trick:

if (in_array('my_virtual_field', $this->getHidden())) {
    return null
}

It’s a bit of configuration but since it’s only for one special-case property, it’s not a big deal.

i need to get my hands dirty on OOP.. still on Procedural PHP…

Just had to turn off virtual fields at runtime for a query. Hope this helps someone.

$destinations = $this->Shipments->find()
    ->select([
        'value' => 'destination',
    ])->distinct(['destination'])->where([
        'destination LIKE' => '%' . $term . '%',
    ]);

$destinations = $destinations->map(function ($destination) {
    return  $destination->setVirtual([]);
});

$this->set(compact('destinations'));

You could maintain two entity classes and use the Table::setEntityClass just before the query.

Personally I like this kind of OOPy solution. I think I’d actually inherit, like this:

class Simple extends Entity {
   // all the basic stuff
}

class SvgVersion extends Simple {
  // all the heavy lifting and virtual fields
}

This means in you code you get three levels of type-hinting that can make your code easier to understand compared to a system where a single Entity class acts differently at different times


//type hint examples

$this->svgProcess(SvgVersion $entity); //when only the best will do

$this->doSomething(Simple $entity); //when either entity type will work

$this->table->patchEntity(EntityInterface $entity, $data), when anything will work
1 Like

Is there ever actually a need to have this virtual field included in any auto-generated output (JSON or array conversion)? If not, just don’t list it in the $_virtual array…