How can i show calculated values on Grid/Crud

I have a Job class form data\Model and it has a table in DB, Grid/Crud works great.
I want to have a progress field in this model, so that it will appear in Grid. But the DB table does not have this value. Progress needs to be calculated, using other queries, and values. If it was a simple count or sum, i would have used aggregate, but that is not the case.

I thought about extending a custom Field class, like ProgressField from \atk4\data\Field, set it to read_only, never_persist, and override the get() to return the calculated value.

But i’m not sure if it is possible, and how to do it. Or is there a better way to do this. Like maybe hooking up a callback somewhere, in the Model domain, or in the UI domain?

Can you make your database table use a calculated field and/or subqueries?

Good suggestion. If DBMS supported something like, on xxxx updates, run this query and update this field kind of a thing, which mysql might be doing already, that would probably a good enough way. Although not the most efficient way (since calculation would then be done even when no one is asking for it), it would be good, because it off loads the work to DBMS, and it would be using an available solution. I should definitely look into mysql’s capability on that. Thanks.

What i ended up doing was, i looked through the \atk4\data\Model class and saw a little function named get( $field ) . This guy was returning the requested fields value, and if given null, it was returning all the field values as an array. My Job class was already extended from Model, so overriding get(), solved the problem. I added a field which is ‘never_persist’=>‘true’ and ‘read_only’=>‘true’.

My overridden get() checked if the request was related with my special field, ‘jobProgress’ in this case, and supplied the value in the right way (single value or array) so that components expecting this functionality wouldn’t be effected. This worked. CRUD/Grid started displaying my values. I also enabled this functionality checking a flag first, so that when disabled, it would just return parent::get($field).

However, when i checked later i saw that a CRUD was calling the get() multiple times for each row of data. With a null parameter, and then once for each Field. So i for each row, my implementation had to do the somewhat heavy calculation 2 times, which is not unnecessary.

I abandoned this solution, and looked for other. What i’m happy with now uses the afterLoad hook on Model. I checked the docs and the hook itself, and saw that afterload was being called for each row of data loaded. (or at least it worked like that when accessed from CRUD). So i kept my field the same, read_only, never_persist. And i initialized this field, if progressCalculation was enebled, at afterLoad. This way, if my Model has progressCalculation enabled, then for each row of data loaded, this is calculated once, and put into the field value. The value can then be accessed many times without recalculation.

I wonder this could be done at reference, like if it was a simple aggregated field, like a sum, or a count. But i needed 2 separate sql queries which took a summed a column, and then do a simple percentage math, so i’m not sure if it could be done at reference level using aggregated fields, and expressions and such.

1 Like

There is this hook after_load which you can use to manually calculate the value.

There is also a field type - Callback. Try this:

$model->addField('progress', ['Callback', function($m) { 
    return $m['test'] * 123;
1 Like

Thanks a lot!, thats exactly what i was looking for, i looked everywhere for that. How did i miss it. (i ended up finding a lot of other useful things, going through the code though)

I tried using Callback but it fails with ‘Field requires array for defaults’.
$model->addField(‘subject’, [‘Callback’, function($m) {
$data = unserialize($m[‘data’]);
return $data[‘subject’];

The official docs refer to classes and methods that don’t exist, so ended up in this forum :slight_smile:

oh try $this->addCalculatedField(‘subject’, function($m)…

i forgot about this, but recently found it in the docs.

I did see the addCalculatedField() method in the docs before, but when i tried using it it turned out that it does not exist.

I’m using:
atk4/core 1.3.10
atk4/data 1.4.1
atk4/dsql 1.2.6
atk4/ui 1.6.5


I think you actually need

$model->addExpression('expr_name', [function ($m) { <your custom callback code goes here ...>}])

Thanks Zak,
I shoudl really explain what i’m after, just to avoid confusion.
In Grid context, i want to extract data from certain field in my model and display it in a column of the Grid.
(I have model field that is mapped to a database field containing serialized data, in my case an array.)
I hoped that this would be ‘standard option’ in ATK but may be i’m doing it the wrong way.

addExpression with a callback still needs to return SQL expression, just not during init() but at the time of query. Not the same as callback.

Ah did not knew that. In this case I think @lisik can use afterLoad hook with a never_persist field to set value to

Thank you both! Your suggestion for using afterLoad hook seems to be the (only) solution of this problem. Here is the working code (some lines skipped to keep the post short):

$subject = $model->addField('subject');
$subject->never_persist = true;

$model->addHook('afterLoad', function($m) {
    $m['subject'] = json_decode($m['data'])->subject;

$grid->addColumn('subject', null, ['caption' => 'Subject']);
1 Like

I’m glad this helped