Техника кэширования результатов запросов на стороне сервера

в 18:33, , рубрики: Песочница

Всем привет!

Хочу поделиться своим опытом кэширования результатов запроса на стороне сервера.

На практике часто бывает очень много статичных данных, например, какой-нибудь справочник, который очень редко изменяется, если изменяется вообще. Чтобы не делать лишние запросы, можно просто один раз подтянуть все данные из БД, сериализовать и закэшировать на определенное время. Плюсы очевидны, меньше запросов к БД.

В итоге я создал trait, который принимает параметры для запроса и сохраняет их в кэш, если еще нет данных, иначе данные просто подтягиваются из кэша.

<?php

namespace commonmytraits;

use Yii;
use yiidbQuery;

/**
 * Class CachedKeyValueData
 * @package commonmytraits
 */
trait CachedKeyValueData
{

    public static function getCachedKeyValueData(
        $table,
        $fields,
        $where,
        $key,
        $defaultValue = []
    )
    {
        if (($data = unserialize(Yii::$app->cache->get($table . $key))) === false) {
            $data = $defaultValue;
            $rows = (new Query)
                ->select($fields)
                ->from($table)
                ->where($where)
                ->all();
            if ($rows) {
                foreach ($rows as $row) {
                    $data[$row[$fields[0]]] = $row[isset($fields[1]) ?
                        $fields[1] : $fields[0]];
                }
            }
            Yii::$app->cache->set($table . $key, serialize($data), 86400);
        }
        return $data;
    }

}

Для возможности использования этого trait'а во всех моделях на проекте лучше создать базовый класс ActiveRecord'а, в котором реализовать логику по инвалидации кэша. Так как данные могут измениться, кэш надо будет сбросить:

<?php

namespace commonmyyii2;

use Yii;
use yiidbActiveRecord as YiiActiveRecord;
use commonmytraitsCachedKeyValueData;

/**
 * Class ActiveRecord
 * @package commonmyyii2
 */
class ActiveRecord extends YiiActiveRecord
{

    use CachedKeyValueData;

    /**
     * @return bool
     */
    protected function deleteCache()
    {
        $tableName = trim(self::tableName(), '{%}');
        if ($this->hasMethod('getAllForLists')) {
            Yii::$app->cache->delete($tableName . 'getAllForLists');
        }
        return true;
    }

    /**
     * @param bool $insert
     * @return bool
     */
    public function beforeSave($insert)
    {
        if (parent::beforeSave($insert)) {
            $this->deleteCache();
            return true;
        } else {
            return false;
        }
    }

    /**
     * @return bool
     */
    public function beforeDelete()
    {
        if (parent::beforeDelete()) {
            $this->deleteCache();
            return true;
        } else {
            return false;
        }
    }

}

Затем можно использовать этот метод из trait'а во всех моделях-наследниках этого базового ActiveRecord'а и не парится об актуальности данных в кэше, если вся работа с данными в БД ведется через модели.

Простой пример использования в моделе Category — список всех категорий:

<?php

namespace cabinetmodulescategorymodels;

use commonmyyii2ActiveRecord;

class Category extends ActiveRecord
{

    /* какой-то код */

    public static function getAllForLists()
    {
        return self::getCachedKeyValueData(
            self::tableName(),
            ['id', 'name'],
            ['status' => 1],
            'getAllForLists'
        );
    }

}

Затем эти данные можно легко подтянуть в каком-нибудь контроллере, например, если нужен список всех категорий для новостей:

<?php

namespace cabinetmodulesnewscontrollers;

use Yii;
use cabinetmodulescategorymodelsCategory;
use cabinetmodulesnewsmodelssearchNewsSearch;
use cabinetmyyii2Controller;

/**
 * NewsController implements the CRUD actions for News model.
 */
class NewsController extends Controller
{

    /* какой-то код */

    public function actionIndex()
    {
        $searchModel = new NewsSearch;
        $dataProvider = $searchModel->search(Yii::$app->request->getQueryParams());

        return $this->render('index', [
            'dataProvider' => $dataProvider,
            'searchModel' => $searchModel,
            'categories' => Category::getAllForLists(),
        ]);
    }

}

Всем спасибо за внимание. Надеюсь статья будет полезной.

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js