Кеширование PHP функций WordPress

0

Часто бывает так, что некоторые функции в php выполняются слишком медленно. Если при этом, результат выполнения этих функций меняется не слишком часто, такие функции можно закешировать.

Примеры медленных функций на сайте.

  • Сложные запросы к базе данных.
    Пример: поиск популярных записей, комментариев, записи на схожие темы и т.п.
  • Обработка большого материала множеством фильтров.
    Пример: обработка материала плаггинами htmlpurifier, поиск в материале картинок, ссылок и других элементов.
  • Парсинг внешних страниц.
    Пример: сайт использует данные со сторонних ресурсов, например новости в формате xml.
  • Другие медленные запросы.

Если на сайте есть медленные функции, значит они первые кандидаты на кеширование. Однако нужно помнить, что кеширование, в идеале, должно быть не заметным для пользователя — он должен получать актуальный контент. Это значит, что кеш должен удаляться, как только появляется новый контент, который может повлиять на содержание закешированой информации. То есть система кеширования должна управлять как созданием, так и своевременной очисткой кеша.

Задачи для системы кеширования функций:

  • Определённое время хранения результата выполнения функции;
  • Принятие переменных функции;
  • Возможность выполнения функций, находящихся внутри классов;
  • Возможность очистки кеша;

На сайте «Ящик Пандоры», используется система кеширования функций, сохраняющая результат выполнения функции в файлы, хранящиеся в папке временного хранения.

Принцип работы функции следующий:

  • В систему кеширования подаётся имя функции, которую нужно выполнить;
  • Система смотрит, есть ли кеш для этой функции;
  • Если кеш есть — возвращает значение из кеша;
  • Если кеша нет — выполнят функцию, сохраняет кеш и возвращает значение.

Данные закешированной функции обновляются в двух случаях:

  • Старый кеш удалён сборщиком мусора.
    Для каждой функции мы можем выставить дату истечения времени жизни кеша в переменной $path (по умолчанию сутки) и по истечении этого времени, кеш функции будет удалён.
  • У функции изменился уникальный ключ, содержащийся в имени файла.
    Мы можем использовать уникальный ключ и обновлять его всякий раз, когда данные, содержащиеся в закешированной функции, изменились.

Ниже представлен код системы кеширования функций, используемый на сайте «Ящик Пандоры».

<?php

class ThemeCache {
    // Кеш функций

    // Путь к папке хранения кеша
    var $cache_path = WP_CONTENT_DIR . '/uploads/yourCacheFolder/';
    var $path = '';

    function ThemeCache() {
        // Пути к файлам кеша
        $this->path = array(
            'def' => array(
                'folder' => 'def',
                'cache' => array(
                    'getQuoteXML' => 1440,
                )
            ),
            'blocks' => array(
                'folder' => 'blocks',
                'cache' => array(
                    'arhive' => 1440,
                    'topcmt' => 180,
                )
            ),
            'posts' => array(
                'folder' => 'posts',
                'cache' => array(
                    'p-' => 43200, //post 1 mounth
                )
            ),
            //... Другие папки ...
        );
    }


    function cache($name = null, $echo = false, $filename = null, $path_tag = null, $class = null, $arg = null) {
        /* Кеш функций классов
         * name - имя функции
         * echo - функция использует echo или return
         * filename - уникальное имя кеша
         * path - куда сохранять кеш
         * class - функция внутри класса
         * arg - массив аргументов для функции         
         */

        if (!$name) {
            return;
        }

        $path_name = $this->path['def']['folder'];
        if ($path_tag) {
            if (isset($this->path[$path_tag])) {
                $path_name = $this->path[$path_tag]['folder'];
            }
        }

        $path = $this->cache_path . $path_name;

        //Имя функции для кеша
        $fname = $name;
        if ($arg) {
            if (is_array($arg)) {
                $fname = implode('-', $arg);
            } else {
                $fname = $arg;
            }
            $fname = $name . '-' . $fname;
        }

        $cachename = $filename != null ? $filename : $fname;

        //Проверяем наличие кеша	
        //Создаем нужные папки
        $fs = new FileService();
        $fs->check_and_create_dir($path);
        //Проверяем наличие файла 
        $file_name = $path . '/' . $cachename . '.html';

        if (file_exists($file_name)) {
            $fbody = file_get_contents($file_name);
            return $fbody;
        } else {// Если кеша нету, создаём
            if (!$echo) {
                if ($class) {
                    if ($arg) {
                        $string = $class->$name($arg);
                    } else {
                        $string = $class->$name();
                    }
                } else {
                    if ($arg) {
                        $string = $name($arg);
                    } else {
                        $string = $name();
                    }
                }
            } else {
                ob_start(); // начало буферизации
                if ($class) {
                    if ($arg) {
                        $class->$name($arg);
                    } else {
                        $class->$name();
                    }
                } else {
                    if ($arg) {
                        $name($arg);
                    } else {
                        $name();
                    }
                }
                $string = ob_get_contents(); // буфер в переменную
                ob_end_clean();
            }

            //Пишем файл
            $fp = fopen($file_name, "w");
            fwrite($fp, $string);
            fclose($fp);
            chmod($file_name, 0777);

            return $string;
        }
    }

    function clearCacheAll($path_tag = '') {
        // Удаление всего кеша
        $cacheFolder = $this->path['def']['folder'];
        if ($path_tag &amp;&amp; isset($this->path[$path_tag])) {
            $cacheFolder = $this->path[$path_tag]['folder'];
        }

        //Открываем директорию кеша
        $dir = WP_CONTENT_DIR . "/uploads/cache/" . $cacheFolder;
        if ($d = @opendir($dir)) {
            while (($file = readdir($d)) !== false) {
                if ($file == '.' || $file == '..')
                    continue;

                $delcache = '';

                echo "$file - removed. $delcache<br />";

                unlink($dir . '/' . $file); //если больше требуемого времени удаляем				
            }
            closedir($d);
        }
    }

    function clearCache($path_tag = '', $echo = true, $wait_def = 86400) {
        // Удаление кеша
        $output = '';

        $cacheFolder = $this->path['def']['folder'];
        if ($path_tag &amp;&amp; isset($this->path[$path_tag])) {
            $cacheFolder = $this->path[$path_tag]['folder'];
        }

        $customCache = array();
        if (isset($this->path[$path_tag]['cache'])) {
            $customCache = $this->path[$path_tag]['cache'];
        }

        //Открываем директорию кеша
        $dir = $this->cache_path . $cacheFolder;
        if ($d = @opendir($dir)) {

            while (($file = readdir($d)) !== false) {
                if ($file == '.' || $file == '..')
                    continue;

                $whait = $wait_def; //время в секундах, по умолчанию сутки
                //Проверяем наличие файла в массиве
                if (sizeof($customCache) > 0)
                    foreach ($customCache as $key => $value) {
                        if (strstr($file, $key)) {
                            $whait = $value * 60; //переводим в секунды					
                            break;
                        }
                    }

                $ftime = filemtime($dir . '/' . $file); // смотрим время создания

                $ctime = time() - $ftime;

                if ($ctime > $whait) {

                    $delcache = '';

                    $output .= "$ctime > $whait - removed. $delcache $file<br />";


                    unlink($dir . '/' . $file); //если больше требуемого времени удаляем				
                } else {
                    $output .= "$ctime < $whait - wait. $file<br />";
                }
            }
            closedir($d);
        } else
            $output = 'dir not found';
        if ($echo)
            echo $output;
        else
            return $output;
    }

    /*
     * Вспомогательные фукнции
     */
    function get_path($path_name) {
        $path = $this->path['def'];
        if (isset($this->path[$path_name])) {
            $path = $this->path[$path_name];
        }
        return $this->cache_path . $path;
    }

    function teaser_cache_name($pid, $lastmod) {
        # Имя кеша для тизеров
        $path = $this->get_path('teasers');
        $tkey = $this->get_teaser_key($pid, $lastmod);
        $file_name = $path . '/' . $tkey . '.html';
        return $file_name;
    }

    function get_teaser_key($pid, $lastmod) {
        # Получаем ключ кешированной записи        
        $tkey = "t-" . $pid . "-" . $lastmod;
        return $tkey;
    }

    function get_post_key($pid, $lastmod) {
        # Получаем ключ кешированной записи        
        $tkey = "p-" . $pid . "-" . $lastmod;
        return $tkey;
    }

    function key_mounth() {
        $date = $this->get_date("Ym");
        return $date;
    }

    function key_day() {
        $date = $this->get_date("Ymd");
        return $date;
    }

    function key_hour() {
        $date = $this->get_date("YmdH");
        return $date;
    }

    function get_date($string) {
        return gmdate($string, time() + ( get_option('gmt_offset') * HOUR_IN_SECONDS ));
    }

    function get_date_by_url() {
        $url = $_SERVER['REQUEST_URI'];
        //echo $url;
        // /2011/02/
        // /2011-02-28/mertvye-goroda-rossiimedvezhka/

        $date = '';
        if (preg_match('/\/([0-9]{4})[\/\-]{1}([0-9]{2}).*/', $url, $match)) {
            $date = $match[1] . $match[2];
        }

        if ($date == '') {
            $datec = gmdate('Ym', time() + ( get_option('gmt_offset') * HOUR_IN_SECONDS ));
            $date = $datec;
        }
        return $date;
    }

}

За создание директорий отвечает класс FileService

class FileService {

    function check_and_create_dir($path) {
        # Создать директорию

        $path_abs = '';
        if (ABSPATH) {
            $path_abs = ABSPATH;
        }

        $path= str_replace($path_abs, '', $path);

        $arr = explode("/", $path);

        foreach ($arr as $a) {
            if ($a) {
                $path_abs = $path_abs . $a . '/';
                $this->fileman($path_abs);
            }
        }
        return null;
    }

    function fileman($way) {
        //Проверка наличия и создание директории
        // string $way - путь к дириктории
        $ret = true;
        if (!file_exists($way)) {
            if (!mkdir("$way", 0777)) {
                $ret = false;
                // throw new Exception('Can not create dir: ' . $way . ', check cmod');
            }
        }
        return $ret;
    }

}

Примеры использования кеша функций:

Популярный комментарий

Функция getCommentsTop находит в базе данных популярный комментарий, используя множество параметров. Данная функция находится внутри класса PandoraComments.

class PandoraComments {

    function getCommentsTopCache() {

        $themeCache = new ThemeCache();

        // Созадём уникальный ключ, который истекает каждый час.
        $tkey = 'getCommentsTop_' . $themeCache->key_hour();

        // Получаем уникальный id комментария из кеша
        $cid = (int) $themeCache->cache('getCommentsTop', false, $tkey, 'def', $this);
//...
}

Карусель популярных публикаций в разделе рубрик

Функция catCarouselCache выводит популярные публикации в определённых рубриках, например: Видео, События, Анатилика. Здесь система кеширования должна учитывать аргументы кешируемой функции.

class PandoraPost {
    var $cache = '';
    function PandoraPost() {
        global $themeCache;
        $this->cache = $themeCache;       
    }


    function catCarouselCache($category_id = false, $limit = 7, $mode = '', $days = 28) {
        // Функция кеширования карусели для определённой рубрики.

        // Уникальный ключ кеширования
        $tkey = 'catCarousel-' . $category_id . '-' . $this->cache->key_mounth();

        // Аргументы, передаваемые в функцию кеширования
        $arg = array(
            'category_id' => $category_id,
            'limit' => $limit,
            'mode' => $mode,
            'days' => $days
        );

        // Данные кеширования
        $item = $this->cache->cache('catCarousel', true, $tkey, 'def', $this, $arg);
        print $item;
    }

    function catCarousel($arg) {
        // Переходная функция, принимающая аргументы для кеширования в виде массива.
        $this->carousel($arg['category_id'], $arg['limit'], $arg['mode'], $arg['days']);
    }

    function carousel($category_id = false, $limit = 7, $mode = '', $days = 7) {
    // Стандартная функция обрабатывающая логику взаимодействия с базой данных.

    //...
     }
}

Кеширование публикации с использованием ключа, содержащего дату обновления публикации.

Иногда нужно кешировать вывод функций пока значение вывода функции не изменится. При публикации материала в WordPress, его значение меняется только когда этот материал обновляет автор. Можно кешировать материал, используя его дату обновления. Тогда, при каждом обновлении материала, будет отображаться актуальная версия кеша, а старый кеш будет удалён сборщиком мусора.

class PandoraSingle {

    var $cache = '';

    function PandoraSingle() {
        global $themeCache;
        $this->cache = $themeCache;
    }

    function getSingleCache() {
        global $post, $currentPost;
        $currentPost = $post;
        $preview = $_GET['preview'];
        if ($preview) {
            return $this->getSingle();
        } else {
            global $post;

            // Получаем информацию о последнем редактировании записи.
            $lastmod = mysql2date('G', $post->post_modified, false);

            // Включаем эту информацию в ключ кеша.
            $tkey = $this->cache->get_post_key($post->ID, $lastmod);
            $content = $this->cache->cache('getSingle', false, $tkey, 'posts', $this);
            return $content;
        }
    }
    function getSingle() {
    // Логика отображения публикации
    }
}

Сборщик мусора

За удаление старого кеша отвечают функции clearCache и clearCacheAll. При этом первая функция удаляет просроченный кеш, а вторая — весь кеш без исключения.

Запускать очистку кеша можно с помощью внешних запросов:

$mode = ($_GET['mode']);
$type = ($_GET['type']);


if (class_exists('ThemeCache')) {
    // Новая тема
    $cache = new ThemeCache();

    if (isset($mode) and $mode == 'all') {
        $cache->clearCacheAll($type);
    } else {
        $cache->clearCache($type);
    }
}

Параметр $type указывает на путь в переменной path класса ThemeCache. Например: def, blocks, posts.

Можно запускать сборщик мусора по средствам cron:

wget -O - -q https://yoursite.site/clearYourCache.php?type=posts > /dev/null

При желании, можно добавить в систему кеширования использование Memcached или других сервисов хранения данных в оперативной памяти.

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

Метки: php
Поделиться:

Оставить комментарий

Вы вошли как Гость. Вы можете авторизоваться

Будте вежливы. Не ругайтесь. Оффтоп тоже не приветствуем. Спам убивается моментально.
Оставляя комментарий Вы соглашаетесь с правилами сайта.

(Обязательно)

Информация о сайте

Компания «Емельянов и партнёры» занимается разработкой, поддержкой и оптимизацией веб сайтов.

На данном сайте публикуются материалы по разработке сайтов и другим интересным вопросам.

Прежде чем приступать к просмотру сайта, ознакомьтесь с разделами:

Сайт может содержать контент, не предназначенный для лиц младше 18-ти лет.
Использование материалов сайта приветствуется при размещении активной ссылки на источник.

Со всеми вопросами и предложениями обращайтесь по почте info@emelianovip.ru