Как в GridView использовать вызов функции только один раз?

Сразу упомяну, что использовать чисты array я не могу, только Active Record.

Есть грид и его колонки:

$gridColumns = [
    ['class' => SerialColumn::class],
    [
        'attribute' => 'cap',
        'label' => 'Всего, Зан., Св.',
        'format' => 'raw',
        'value' => function($model) {
            $data = Yii::$app->db->createCommand("SELECT * FROM dbFunction({$model->point_id}")->queryOne();
            $html = <<< HTML
<div class="item">{$data['capacity']}</div>
<div class="item">{$data['occupied']}</div>
<div class="item">{$data['free']}</div>
HTML;       
            return $html;
        },      
    ],  
];

echo GridView::widget([
    'id' => 'my-table',
    'columns' => $gridColumns,
    'dataProvider' => $dataProvider,
    'filterModel' => $searchModel,  
    'rowOptions' => function($model) {
        $data = Yii::$app->db->createCommand("SELECT * FROM dbFunction({$model->point_id}")->queryOne();        
        
        $class = $data['free'] === 0 ? 'free-point' : '';
                
        if ($data['capacity'] === 0 && $data['occupied'] === 0 && $data['free'] === 0)
            $class = 'd-none';
        
        return [
            'data-id' => $model->id,
            'class' =>  $class,
        ];
    },
]);

Всё работает, но проблема в том, что функция dbFunction на каждую строчку выполняется по два раза (один раз для ячейки и один раз для определения условий отрисовки самой строки). А функция очень тяжеловесная.

Вопрос: можно ли где-то её вызвать единожды (для итерации) и использовать результат в обоих местах? Не имею понятия как сделать, потому что для columns внутри движка идёт свой цикл, и для rowOptions видимо тоже что-то отдельное. А как это скомпановать?

P.S. На js я могу перенести условия для rowOptions, но тогда получится что написано "отображается 30 записей из 60", а на деле будет отображено 5. Поэтому хотелось бы решить это так же на бэке.


Ответы (1 шт):

Автор решения: Федор Досумов

Помогли с решением на английском SO.

Добавить функцию в модель и приватную переменную, куда складировать результат:

class MyModel extends ActiveRecord
{
    private ?array $capacityData = null;
    
    public function capacityData(): array
    {
        if ($this->capacityData === null) {
            $this->capacityData  = $this->getDb()->createCommand(
                "SELECT * FROM dbFunction({$model->point_id})"
            )->queryOne();
        }

        return $this->capacityData;
    }

    // ... other definitions ...
}

Потом её использовать:

'value' => function($model) {
     $data = $model->capacityData();
     $html = <<< HTML
<div class="item">{$data['capacity']}</div>
<div class="item">{$data['occupied']}</div>
<div class="item">{$data['free']}</div>
HTML;       
     return $html;
},      

и

'rowOptions' => function($model) {
    $data = $model->capacityData();        
        
    $class = $data['free'] === 0 ? 'free-point' : '';
                
    if ($data['capacity'] === 0 && $data['occupied'] === 0 && $data['free'] === 0)
        $class = 'd-none';
        
    return [
        'data-id' => $model->id,
        'class' =>  $class,
    ];
},

SQL выполнится только один раз и положит результат в $capacityData. При других вызовах, если переменная не null - то сразу вернёт значение

→ Ссылка