Created
October 28, 2025 14:27
-
-
Save Konamiman/96aeacc7933b5b89cb735c46f02adf89 to your computer and use it in GitHub Desktop.
Simple file-based persistent object cache for WordPress. For testing scenarios only.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * Simple file-based WordPress object cache implementation. | |
| * | |
| * Drop this file in wp-content/ to enable persistent object caching. | |
| * Each cache group stores its data in a separate file using PHP serialization. | |
| * Files will go in WP_CONTENT_DIR . '/cache/object-cache', or if | |
| * no WP_CONTENT_DIR constant is defined, in '/tmp/wp-object-cache'. | |
| * | |
| * This file is provided as-is without warranty of any kind. | |
| * It's not officially supported by WooCommerce or Automattic. | |
| * | |
| * NOT FOR PRODUCTION - For development/testing only. | |
| */ | |
| class WP_Object_Cache { | |
| /** | |
| * Cache directory path. | |
| * | |
| * @var string | |
| */ | |
| private $cache_dir; | |
| /** | |
| * In-memory cache for this request. | |
| * | |
| * @var array | |
| */ | |
| private $cache = array(); | |
| /** | |
| * Dirty flags to track which groups need saving. | |
| * | |
| * @var array | |
| */ | |
| private $dirty = array(); | |
| /** | |
| * Whether cache directory has been initialized. | |
| * | |
| * @var bool | |
| */ | |
| private $initialized = false; | |
| /** | |
| * Initialize cache directory (lazy initialization). | |
| */ | |
| private function init() { | |
| if ( $this->initialized ) { | |
| return; | |
| } | |
| $this->cache_dir = defined( 'WP_CONTENT_DIR' ) ? WP_CONTENT_DIR . '/cache/object-cache' : '/tmp/wp-object-cache'; | |
| // Create cache directory if it doesn't exist. | |
| if ( ! is_dir( $this->cache_dir ) ) { | |
| // Use mkdir instead of wp_mkdir_p which may not be available yet. | |
| @mkdir( $this->cache_dir, 0755, true ); | |
| } | |
| $this->initialized = true; | |
| } | |
| /** | |
| * Get a value from cache. | |
| * | |
| * @param string $key Cache key. | |
| * @param string $group Cache group. | |
| * @param bool $force Not used. | |
| * @param bool &$found Whether the key was found. | |
| * @return mixed|false The cached value or false if not found. | |
| */ | |
| public function get( $key, $group = 'default', $force = false, &$found = null ) { | |
| $this->init(); | |
| $group = empty( $group ) ? 'default' : $group; | |
| // Check in-memory cache first. | |
| if ( ! isset( $this->cache[ $group ] ) ) { | |
| $this->load_group( $group ); | |
| } | |
| if ( isset( $this->cache[ $group ][ $key ] ) ) { | |
| $entry = $this->cache[ $group ][ $key ]; | |
| // Check if expired. | |
| if ( $entry['expires'] > 0 && $entry['expires'] < time() ) { | |
| unset( $this->cache[ $group ][ $key ] ); | |
| $this->dirty[ $group ] = true; | |
| $found = false; | |
| return false; | |
| } | |
| $found = true; | |
| return $entry['value']; | |
| } | |
| $found = false; | |
| return false; | |
| } | |
| /** | |
| * Set a value in cache. | |
| * | |
| * @param string $key Cache key. | |
| * @param mixed $value Value to cache. | |
| * @param string $group Cache group. | |
| * @param int $expire Expiration in seconds (0 = no expiration). | |
| * @return bool True on success. | |
| */ | |
| public function set( $key, $value, $group = 'default', $expire = 0 ) { | |
| $this->init(); | |
| $group = empty( $group ) ? 'default' : $group; | |
| if ( ! isset( $this->cache[ $group ] ) ) { | |
| $this->cache[ $group ] = array(); | |
| } | |
| $this->cache[ $group ][ $key ] = array( | |
| 'value' => $value, | |
| 'expires' => $expire > 0 ? time() + $expire : 0, | |
| ); | |
| $this->dirty[ $group ] = true; | |
| return true; | |
| } | |
| /** | |
| * Delete a value from cache. | |
| * | |
| * @param string $key Cache key. | |
| * @param string $group Cache group. | |
| * @return bool True on success. | |
| */ | |
| public function delete( $key, $group = 'default' ) { | |
| $this->init(); | |
| $group = empty( $group ) ? 'default' : $group; | |
| if ( isset( $this->cache[ $group ][ $key ] ) ) { | |
| unset( $this->cache[ $group ][ $key ] ); | |
| $this->dirty[ $group ] = true; | |
| return true; | |
| } | |
| return false; | |
| } | |
| /** | |
| * Flush all cache. | |
| * | |
| * @return bool True on success. | |
| */ | |
| public function flush() { | |
| $this->init(); | |
| $this->cache = array(); | |
| $this->dirty = array(); | |
| // Delete all cache files. | |
| $files = glob( $this->cache_dir . '/*.php' ); | |
| if ( $files ) { | |
| foreach ( $files as $file ) { | |
| unlink( $file ); | |
| } | |
| } | |
| return true; | |
| } | |
| /** | |
| * Get cache file path for a group. | |
| * | |
| * @param string $group Group name. | |
| * @return string File path. | |
| */ | |
| private function get_cache_file( $group ) { | |
| // Sanitize group name for filename. | |
| $safe_group = preg_replace( '/[^a-z0-9_\-]/', '_', strtolower( $group ) ); | |
| return $this->cache_dir . '/' . md5( $group ) . '-' . $safe_group . '.php'; | |
| } | |
| /** | |
| * Load a cache group from disk. | |
| * | |
| * @param string $group Group name. | |
| */ | |
| private function load_group( $group ) { | |
| $file = $this->get_cache_file( $group ); | |
| if ( ! file_exists( $file ) ) { | |
| $this->cache[ $group ] = array(); | |
| return; | |
| } | |
| $contents = file_get_contents( $file ); | |
| // Remove the PHP header if present. | |
| if ( strpos( $contents, '<?php' ) === 0 ) { | |
| $contents = substr( $contents, strpos( $contents, "\n" ) + 1 ); | |
| } | |
| $data = @unserialize( $contents ); | |
| if ( $data !== false && is_array( $data ) ) { | |
| $this->cache[ $group ] = $data; | |
| } else { | |
| $this->cache[ $group ] = array(); | |
| } | |
| } | |
| /** | |
| * Save a cache group to disk. | |
| * | |
| * @param string $group Group name. | |
| */ | |
| private function save_group( $group ) { | |
| $file = $this->get_cache_file( $group ); | |
| $data = isset( $this->cache[ $group ] ) ? $this->cache[ $group ] : array(); | |
| // Clean up expired entries before saving. | |
| $now = time(); | |
| foreach ( $data as $key => $entry ) { | |
| if ( $entry['expires'] > 0 && $entry['expires'] < $now ) { | |
| unset( $data[ $key ] ); | |
| } | |
| } | |
| if ( empty( $data ) ) { | |
| // Delete file if group is empty. | |
| if ( file_exists( $file ) ) { | |
| unlink( $file ); | |
| } | |
| } else { | |
| // Save with PHP header to prevent direct access. | |
| $serialized = serialize( $data ); | |
| file_put_contents( $file, "<?php exit; ?>\n" . $serialized ); | |
| } | |
| unset( $this->dirty[ $group ] ); | |
| } | |
| /** | |
| * Save all dirty groups to disk. | |
| */ | |
| private function save_dirty_groups() { | |
| if ( ! $this->initialized ) { | |
| return; | |
| } | |
| foreach ( array_keys( $this->dirty ) as $group ) { | |
| $this->save_group( $group ); | |
| } | |
| } | |
| /** | |
| * Destructor - save dirty groups on shutdown. | |
| */ | |
| public function __destruct() { | |
| $this->save_dirty_groups(); | |
| } | |
| } | |
| /** | |
| * Ensure cache object is initialized. | |
| */ | |
| function wp_cache_init() { | |
| global $wp_object_cache; | |
| if ( ! ( $wp_object_cache instanceof WP_Object_Cache ) ) { | |
| $wp_object_cache = new WP_Object_Cache(); | |
| } | |
| } | |
| // Initialize global cache object. | |
| wp_cache_init(); | |
| /** | |
| * WordPress object cache functions. | |
| */ | |
| function wp_cache_add( $key, $data, $group = '', $expire = 0 ) { | |
| return wp_cache_set( $key, $data, $group, $expire ); | |
| } | |
| function wp_cache_set( $key, $data, $group = '', $expire = 0 ) { | |
| global $wp_object_cache; | |
| if ( ! ( $wp_object_cache instanceof WP_Object_Cache ) ) { | |
| wp_cache_init(); | |
| } | |
| return $wp_object_cache->set( $key, $data, $group, (int) $expire ); | |
| } | |
| function wp_cache_get( $key, $group = '', $force = false, &$found = null ) { | |
| global $wp_object_cache; | |
| if ( ! ( $wp_object_cache instanceof WP_Object_Cache ) ) { | |
| wp_cache_init(); | |
| } | |
| return $wp_object_cache->get( $key, $group, $force, $found ); | |
| } | |
| function wp_cache_delete( $key, $group = '' ) { | |
| global $wp_object_cache; | |
| if ( ! ( $wp_object_cache instanceof WP_Object_Cache ) ) { | |
| wp_cache_init(); | |
| } | |
| return $wp_object_cache->delete( $key, $group ); | |
| } | |
| function wp_cache_replace( $key, $data, $group = '', $expire = 0 ) { | |
| return wp_cache_set( $key, $data, $group, $expire ); | |
| } | |
| function wp_cache_flush() { | |
| global $wp_object_cache; | |
| if ( ! ( $wp_object_cache instanceof WP_Object_Cache ) ) { | |
| wp_cache_init(); | |
| } | |
| return $wp_object_cache->flush(); | |
| } | |
| function wp_cache_incr( $key, $offset = 1, $group = '' ) { | |
| // Not used by our caches, minimal implementation. | |
| return false; | |
| } | |
| function wp_cache_decr( $key, $offset = 1, $group = '' ) { | |
| // Not used by our caches, minimal implementation. | |
| return false; | |
| } | |
| function wp_cache_switch_to_blog( $blog_id ) { | |
| // Multisite not needed for basic testing. | |
| return true; | |
| } | |
| function wp_cache_add_non_persistent_groups( $groups ) { | |
| // All groups are persistent in this implementation. | |
| return true; | |
| } | |
| function wp_cache_add_global_groups( $groups ) { | |
| // Single-site compatibility. | |
| return true; | |
| } | |
| function wp_cache_close() { | |
| return true; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment