Очистка кэша в PHP (php clear cache): варианты решений
Основные подходы к очистке кэша PHP
Как гарантированно очистить все типы кэша PHP, используя минимальное количество вызовов?
Наиболее универсальным решением является применение комбинации функций, встроенных в PHP, для разных уровней кэширования. Предполагается, что сервер использует OPcache и APCu (или другие модули). Пример такого подхода:
<?php
// Очистка кэша OPcache (общий)
if (function_exists('opcache_reset')) {
$result = opcache_reset();
echo 'OPcache очищен: ' . ($result ? 'успешно' : 'ошибка') . "\n";
}
// Очистка кэша APCu
if (function_exists('apcu_clear_cache')) {
$result = apcu_clear_cache();
echo 'APCu очищен: ' . ($result ? 'успешно' : 'ошибка') . "\n";
}
// Сброс кэша файловой статистики
clearstatcache();
echo 'Кэш файловой статистики сброшен.\n';
?>
Object cache php (объектный кеш php)
Этот код следует разместить в отдельном скрипте, доступном только администраторам. После выполнения все компилированные копии скриптов (OPcache) и пользовательские данные (APCu) будут удалены.
Как очистить только кэш одного файла, не затрагивая остальные?
Иногда требуется инвалидировать конкретный скрипт, например после его редактирования. Используйте opcache_invalidate():
<?php
$file = '/path/to/updated_script.php';
if (opcache_invalidate($file, true)) {
echo 'Кэш файла ' . $file . ' инвалидирован.';
} else {
echo 'Не удалось инвалидировать кэш файла.';
}
?>
Php clear cache (очистка кеша php)
Параметр true принудительно заставляет перекомпилировать файл при следующем вызове. Без этого параметра файл будет удалён из кэша, но может быть добавлен заново без изменений.
Типичные ошибки: Функция возвращает false, если путь не существует или OPcache отключён. В некоторых конфигурациях (например, FastCGI) изменения могут не вступить сразу до перезапуска пула.
Как очистить кэш, созданный через файловое кэширование (например, Doctrine, Symfony)?
Многие фреймворки хранят кэш в файлах. Простейший способ — удалить все файлы из каталога кэша. Пример для Laravel:
<?php
$cacheDir = __DIR__ . '/storage/framework/cache/data/';
$files = glob($cacheDir . '*'); // Удаление только верхнего уровня
foreach ($files as $file) {
if (is_file($file)) {
unlink($file);
}
}
echo 'Файловый кэш очищен.';
?>
Обратите внимание: при использовании кэша с сериализованными объектами может потребоваться также очистить кэш шаблонов (view cache) и кэш конфигурации.
Проблемы: Прямое удаление файлов может нарушить работу, если другой процесс одновременно пытается их прочитать. Рекомендуется использовать API фреймворка (например, php artisan cache:clear).
Как сбросить кэш Redis или Memcached из PHP?
Для внешних кэш-серверов используйте клиентские команды. Пример с Redis через Predis:
<?php
require 'vendor/autoload.php';
$client = new Predis\Client([
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
]);
try {
$client->flushall();
echo 'Кэш Redis полностью очищен.';
} catch (Exception $e) {
echo 'Ошибка: ' . $e->getMessage();
}
?>
Для Memcached аналогично используется $memcache->flush(). Обратите внимание, что эта операция дорогая и может временно увеличить нагрузку на базу данных.
Ошибки: Если Redis/Memcached требует аутентификации, необходимо передать пароль. Также flushall очищает все базы данных, а не только выбранную.
Как очистить кэш встроенного браузерного кэша с помощью заголовков PHP?
Хотя это не относится к серверному кэшу, иногда разработчики хотят принудительно обновить кэш у клиента. Ответ - установка специальных HTTP-заголовков:
<?php
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
header("Pragma: no-cache");
header("Expires: Sat, 01 Jan 2000 00:00:00 GMT");
echo 'Клиентский кэш отключён для этого ответа.';
?>
Этот подход используется для динамических страниц, которые не должны кэшироваться браузером. Обратите внимание, что прокси-серверы также могут игнорировать эти заголовки.
Каждый из описанных методов решает свою частную задачу. Выбор зависит от используемых инструментов и архитектуры приложения.
Расширенные примеры очистки кэша PHP
Ниже представлены более детализированные и нестандартные примеры программного кода с пояснениями и результатами.
Пример 1. Рекурсивная очистка OPcache для директории
Иногда нужно инвалидировать все файлы внутри каталога. Реализация:
<?php
function invalidateDirCache($dir) {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS)
);
$count = 0;
foreach ($iterator as $file) {
if ($file->isFile() && $file->getExtension() === 'php') {
if (opcache_invalidate($file->getRealPath(), true)) {
$count++;
}
}
}
echo "Инвалидировано $count файлов.";
}
invalidateDirCache('/var/www/html/app');
?>
Инвалидировано 548 файлов.
Важно: функция может быть медленной для больших проектов. Используйте с осторожностью.
Пример 2. Очистка APCu с проверкой и отчётом
Проверка успешности и вывод статистики до и после очистки:
<?php
if (function_exists('apcu_clear_cache')) {
$before = apcu_cache_info(true);
echo "До очистки: " . $before['num_entries'] . " записей\n";
if (apcu_clear_cache()) {
$after = apcu_cache_info(true);
echo "После очистки: " . $after['num_entries'] . " записей\n";
echo "Кэш APCu успешно очищен.\n";
} else {
echo "Ошибка очистки APCu.\n";
}
} else {
echo "APCu не установлен.\n";
}
?>
До очистки: 1243 записей После очистки: 0 записей Кэш APCu успешно очищен.
Пример 3. Файловый кэш с древовидной структурой и защитой от конкурентного доступа
Безопасная очистка файлового кэша с блокировкой:
<?php
function safeClearFileCache($cacheDir) {
if (!is_dir($cacheDir)) {
echo "Директория $cacheDir не существует.";
return;
}
$lockFile = $cacheDir . '/.clear_lock';
$fp = fopen($lockFile, 'c');
if (!flock($fp, LOCK_EX | LOCK_NB)) {
echo "Не удалось получить блокировку. Возможно, очистка уже выполняется.";
fclose($fp);
return;
}
$dirIterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($cacheDir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
$count = 0;
foreach ($dirIterator as $item) {
if ($item->isFile() && $item->getFilename() !== '.clear_lock') {
unlink($item->getRealPath());
$count++;
} elseif ($item->isDir()) {
rmdir($item->getRealPath());
}
}
flock($fp, LOCK_UN);
fclose($fp);
unlink($lockFile);
echo "Удалено $count файлов и очищена структура директорий.";
}
safeClearFileCache('/tmp/experimental_cache');
?>
Удалено 2047 файлов и очищена структура директорий.
Блокировка предотвращает одновременную очистку и запись.
Пример 4. Очистка Redis с сохранением части ключей по паттерну
Иногда нужно удалить только ключи, соответствующие определённому шаблону, не затрагивая остальные:
<?php
require 'vendor/autoload.php';
$redis = new Predis\Client(['host' => 'redis.example.com']);
$pattern = 'user_sessions:*';
$cursor = 0;
$count = 0;
do {
list($cursor, $keys) = $redis->scan($cursor, ['MATCH' => $pattern, 'COUNT' => 100]);
if (!empty($keys)) {
$redis->del($keys);
$count += count($keys);
}
} while ($cursor != 0);
echo "Удалено $count ключей, соответствующих шаблону '$pattern'.";
?>
Удалено 5432 ключей, соответствующих шаблону 'user_sessions:*'.
Использование SCAN вместо KEYS предотвращает блокировку Redis на время выполнения.
Пример 5. Комбинированная очистка для типового веб-приложения
Сценарий, который последовательно очищает OPcache, APCu, систему кэша фреймворка и Redis, с выводом пошагового лога:
<?php
$log = [];
// 1. OPcache
if (function_exists('opcache_reset')) {
$log[] = 'OPcache: ' . (opcache_reset() ? 'OK' : 'FAIL');
} else {
$log[] = 'OPcache: не доступен';
}
// 2. APCu
if (function_exists('apcu_clear_cache')) {
$log[] = 'APCu: ' . (apcu_clear_cache() ? 'OK' : 'FAIL');
} else {
$log[] = 'APCu: не доступен';
}
// 3. Очистка файлового кэша (например, Symfony)
$cacheDir = __DIR__ . '/var/cache/dev';
if (is_dir($cacheDir)) {
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($cacheDir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
$count = 0;
foreach ($files as $file) {
($file->isFile()) ? unlink($file->getRealPath()) : rmdir($file->getRealPath());
$count++;
}
$log[] = "Файловый кэш: удалено $count элементов";
} else {
$log[] = 'Файловый кэш: директория не найдена';
}
// 4. Redis
if (class_exists('\Predis\Client')) {
try {
$redis = new \Predis\Client(['host' => '127.0.0.1']);
$redis->flushall();
$log[] = 'Redis: OK';
} catch (Exception $e) {
$log[] = 'Redis: ошибка - ' . $e->getMessage();
}
} else {
$log[] = 'Redis: Predis не установлен';
}
echo implode("\n", $log);
?>
OPcache: OK APCu: OK Файловый кэш: удалено 15402 элементов Redis: OK
Такой скрипт удобен для развёртывания после обновления кода.