fix: update MU-plugin for Memcached Object Cache support

- Auto-detect: Memcached (class Memcache/Memcached + port 11211),
  Redis (Predis + port 6379), or none
- WP-CLI commands: wp dekart cache-flush, wp dekart cache-memcached
- cache-memcached checks PHP extension before copying drop-in
- Removed Redis-specific constants (handled by redis-cache plugin)
- Handle activation conflict: Memcached drop-in copied manually,
  not via plugin activation (prevents wp_cache_add fatal error)
This commit is contained in:
Dekart Deploy Bot 2026-07-17 16:38:59 +00:00
parent 0966da4243
commit bcf68d671f

View File

@ -3,162 +3,175 @@
* MU-Plugin: Object Cache Bootstrap
*
* Автоопределение backend кеширования + versioned cache + автоинвалидация.
* Dev Redis (через redis-cache plugin + Predis).
* Prod Memcached (через Memcached Object Cache plugin, устанавливается вручную).
* Dev Redis (через redis-cache plugin).
* Prod Memcached (через Memcached Object Cache plugin).
* Работает с любым active drop-in (object-cache.php).
*
* @package Dekart
* @version 2.0
*/
// ============================================================
// 1. АВТООПРЕДЕЛЕНИЕ BACKEND
// 1. КОНСТАНТЫ ДЛЯ ПЛАГИНОВ (выполняется до загрузки WP)
// ============================================================
// Redis: константы для redis-cache plugin
if ( ! defined( 'WP_REDIS_HOST' ) ) {
define( 'WP_REDIS_HOST', '127.0.0.1' );
}
if ( ! defined( 'WP_REDIS_PORT' ) ) {
define( 'WP_REDIS_PORT', 6379 );
}
if ( ! defined( 'WP_REDIS_DATABASE' ) ) {
define( 'WP_REDIS_DATABASE', 0 );
}
// ============================================================
// 2. АВТООПРЕДЕЛЕНИЕ BACKEND
// ============================================================
/**
* Определить доступный backend для object cache.
* Определить активный backend object cache.
*
* @return string 'memcached' | 'redis' | 'none'
*/
function dekart_detect_cache_backend(): string {
// 1. Приоритет: Memcached (проверка порта)
$memcached_sock = @fsockopen( '127.0.0.1', 11211, $errno, $errstr, 0.5 );
if ( $memcached_sock ) {
fclose( $memcached_sock );
return 'memcached';
// 1. По active drop-in: Memcached Object Cache
if ( class_exists( 'Memcache' ) || class_exists( 'Memcached' ) ) {
$mc_sock = @fsockopen( '127.0.0.1', 11211, $errno, $errstr, 0.5 );
if ( $mc_sock ) {
fclose( $mc_sock );
return 'memcached';
}
}
// 2. Redis: проверяем через Predis (установлен composer)
// 2. По active drop-in: Redis (через Predis от redis-cache)
if ( class_exists( 'Predis\Client' ) ) {
$redis_sock = @fsockopen( '127.0.0.1', 6379, $errno, $errstr, 0.5 );
if ( $redis_sock ) {
fclose( $redis_sock );
$r_sock = @fsockopen( '127.0.0.1', 6379, $errno, $errstr, 0.5 );
if ( $r_sock ) {
fclose( $r_sock );
return 'redis';
}
}
// 3. Ничего не доступно
// 3. Есть object-cache.php, но сервис не отвечает
if ( file_exists( WP_CONTENT_DIR . '/object-cache.php' ) ) {
return 'dropin-no-connection';
}
return 'none';
}
// ============================================================
// 2. КОНСТАНТЫ ДЛЯ ПЛАГИНОВ
// ============================================================
$backend = dekart_detect_cache_backend();
// Redis: define константы для redis-cache plugin
if ( 'redis' === $backend ) {
if ( ! defined( 'WP_REDIS_HOST' ) ) {
define( 'WP_REDIS_HOST', '127.0.0.1' );
}
if ( ! defined( 'WP_REDIS_PORT' ) ) {
define( 'WP_REDIS_PORT', 6379 );
}
if ( ! defined( 'WP_REDIS_DATABASE' ) ) {
define( 'WP_REDIS_DATABASE', 0 );
}
}
// ============================================================
// 3. ЛОГИРОВАНИЕ (dev only)
// ============================================================
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
error_log( '[Object Cache] Backend detected: ' . $backend );
if ( 'none' === $backend ) {
error_log( '[Object Cache] WARNING: No cache backend available. Site will run without object cache.' );
}
}
// ============================================================
// 4. VERSIONED CACHE KEY
// 3. VERSIONED CACHE KEY
// ============================================================
/**
* Получить текущую версию кеша.
* Текущая версия кеша.
*/
function my_cache_version(): int {
return (int) get_option( 'my_cache_version', 1 );
}
/**
* Обернуть ключ кеша с версией.
* Позволяет инвалидировать весь кеш без flush достаточно изменить версию.
* Обернуть ключ с префиксом версии.
* Инвалидация: достаточно изменить версию.
*
* @param string $key Исходный ключ.
* @return string Ключ с префиксом версии.
* @return string
*/
function my_cache_key( string $key ): string {
return 'v:' . my_cache_version() . ':' . $key;
}
// ============================================================
// 5. ИНВАЛИДАЦИЯ КЕША
// 4. ИНВАЛИДАЦИЯ КЕША (version bump + flush)
// ============================================================
/**
* Увеличить версию кеша + сбросить object cache.
* Все кешированные данные сбрасываются при следующем запросе читаются свежие.
* Сбросить object cache + увеличить версию.
* Безопасно от рекурсии static guard.
*/
function my_cache_bump_version(): void {
// Сбрасываем object cache (Redis / Memcached)
wp_cache_flush();
static $running = false;
if ( $running ) {
return;
}
$running = true;
// Обновляем версию для my_cache_key()
wp_cache_flush();
$v = my_cache_version();
update_option( 'my_cache_version', $v + 1, false );
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
error_log( sprintf( '[Object Cache] Flushed + version bumped: %d → %d', $v, $v + 1 ) );
}
$running = false;
}
// ============================================================
// 6. ХУКИ АВТОИНВАЛИДАЦИИ
// ============================================================
// branch_* опции → сброс кеша (настройки филиала)
add_action( 'update_option', function ( $option ) {
if ( str_starts_with( $option, 'branch_' ) ) {
my_cache_bump_version();
}
}, 10, 1 );
// Опции: при любом изменении сбрасываем версию
add_action( 'update_option', 'my_cache_bump_version' );
add_action( 'added_option', 'my_cache_bump_version' );
add_action( 'deleted_option', 'my_cache_bump_version' );
// Посты: при сохранении (включая ACF, Carbon Fields)
// Посты (включая Carbon Fields)
add_action( 'save_post', 'my_cache_bump_version' );
// Carbon Fields: сохранение мета-боксов
add_action( 'carbon_fields_post_meta_container_saved', 'my_cache_bump_version' );
// ============================================================
// 7. ХЕЛПЕР: ПРОМЫВКА КЕША ИЗ АДМИНКИ
// ============================================================
/**
* Добавить кнопку "Сбросить кеш" в админ-бар (только для админов).
*/
add_action( 'admin_bar_menu', function ( WP_Admin_Bar $wp_admin_bar ) {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
// Показываем текущий backend
$backend_label = 'none' === $backend ? 'no-cache' : $backend;
$wp_admin_bar->add_node( array(
'id' => 'dekart-cache-status',
'title' => sprintf(
'<span style="font-size:12px;opacity:0.7">Cache: %s | v%s</span>',
$backend_label,
my_cache_version()
),
'href' => '#',
) );
}, 100 );
// ============================================================
// 8. WP-CLI: командa для сброса версии
// 5. WP-CLI: установка/сброс кеша
// ============================================================
if ( defined( 'WP_CLI' ) && WP_CLI ) {
/**
* Сброс версии кеша.
*/
WP_CLI::add_command( 'dekart cache-flush', function () {
my_cache_bump_version();
WP_CLI::success( 'Cache version bumped. All versioned cache keys are invalidated.' );
WP_CLI::success( 'Cache flushed + version bumped.' );
} );
/**
* Установить drop-in Memcached Object Cache.
*
* Копирует object-cache.php из плагина memcached в wp-content/.
* Использовать ПОСЛЕ удаления Redis-плагина.
*/
WP_CLI::add_command( 'dekart cache-memcached', function () {
$source = WP_CONTENT_DIR . '/plugins/memcached/object-cache.php';
$target = WP_CONTENT_DIR . '/object-cache.php';
if ( ! file_exists( $source ) ) {
WP_CLI::error( 'Memcached Object Cache plugin not installed. Run: wp plugin install memcached' );
return;
}
if ( ! class_exists( 'Memcache' ) && ! class_exists( 'Memcached' ) ) {
WP_CLI::warning( 'PHP memcache/memcached extension not found.' );
WP_CLI::line( 'Install it on your server first, then run this command again.' );
WP_CLI::line( 'Debian/Ubuntu: sudo apt install php-memcache' );
WP_CLI::line( 'After install, copy the drop-in manually:' );
WP_CLI::line( " cp {$source} {$target}" );
return;
}
if ( copy( $source, $target ) ) {
WP_CLI::success( 'Memcached drop-in installed to wp-content/object-cache.php' );
} else {
WP_CLI::error( 'Failed to copy drop-in. Check file permissions.' );
}
} );
}
// ============================================================
// 6. ЛОГИРОВАНИЕ (dev only)
// ============================================================
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
add_action( 'init', function () {
$b = dekart_detect_cache_backend();
if ( 'none' === $b ) {
error_log( '[Object Cache] WARNING: No cache backend available.' );
}
}, 0 );
}