Filesystem based transient functions for WordPress

Sometimes we may have a reason to use file-based transients instead of a default DB transients.

For example, most cache plugins automatically moves transients into external cache systems such as Redis, Memcached which might be a problem. Because if for some reason redis cache is reset – then all transients get lost – which is very bad for sure.

Transients are not always data which can easily be regenerated – sometimes they contain very important temporary data and should be kept until it expires.

That’s why W3Total Cache plugin has a great feature for it – you can keep a default transient storage(DB) while using Redis or other external object caching software.

For my own codes, sometimes i use filesystem based transient which i built for myself.

Here are set and get functions:

if ( ! defined( 'ABSPATH' ) ) exit;

define('PSEUDO_TRANSIENT_DATA_DIR',wp_get_upload_dir()['basedir']);

function gvn_set_pseudo_transient( $key, $value, $expire_in_seconds = 3600 ) {
    if ( defined( 'GVN_USE_REAL_TRANSIENTS' ) ) {
        set_transient( $key, $value, $expire_in_seconds );
    } else {
        global $wp_filesystem;

        // Ensure WP_Filesystem is loaded
        if ( ! function_exists( 'WP_Filesystem' ) ) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
        }

        // Force direct method for file handling
        add_filter( 'filesystem_method', function() {
            return 'direct';
        } );

        // Initialize WP_Filesystem
        if ( ! WP_Filesystem() ) {
            return false;
        }

        $dir = PSEUDO_TRANSIENT_DATA_DIR;

        if ( ! $wp_filesystem->is_dir( $dir ) ) {
            if ( ! $wp_filesystem->mkdir( $dir ) ) {
                return false;
            }
        }

        $file_path = trailingslashit( $dir ) . sanitize_file_name( $key ) . '.json';
        $expire_file_path = trailingslashit( $dir ) . sanitize_file_name( $key ) . '_expire.json';

        // Save value file
        $file_contents = wp_json_encode( $value );
        if ( ! $wp_filesystem->put_contents( $file_path, $file_contents, FS_CHMOD_FILE ) ) {
            return false;
        }

        // Save expiration file
        $expire_timestamp = time() + $expire_in_seconds;
        if ( ! $wp_filesystem->put_contents( $expire_file_path, wp_json_encode( $expire_timestamp ), FS_CHMOD_FILE ) ) {
            return false;
        }

        return true;
    }
}


function gvn_get_pseudo_transient( $key ) {
    if ( defined( 'GVN_USE_REAL_TRANSIENTS' ) ) {
        $value = get_transient( $key );
        return $value !== false ? $value : false;
    } else {
        global $wp_filesystem;

        // Initialize WP_Filesystem
        if ( ! function_exists( 'WP_Filesystem' ) ) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
        }

        add_filter( 'filesystem_method', function() {
            return 'direct';
        } );

        if ( ! WP_Filesystem() ) {
            return false;
        }

        $dir = PSEUDO_TRANSIENT_DATA_DIR;
        $file_path = trailingslashit( $dir ) . sanitize_file_name( $key ) . '.json';
        $expire_file_path = trailingslashit( $dir ) . sanitize_file_name( $key ) . '_expire.json';

        // Check expiration file
        if ( $wp_filesystem->exists( $expire_file_path ) ) {
            $expire_contents = $wp_filesystem->get_contents( $expire_file_path );
            $expire_timestamp = json_decode( $expire_contents, true );

            if ( time() > $expire_timestamp ) {
                return false; // Expired
            }
        }

        // Read value file
        if ( $wp_filesystem->exists( $file_path ) ) {
            $file_contents = $wp_filesystem->get_contents( $file_path );
            return json_decode( $file_contents, true );
        }

        return false;
    }
}


function gvn_delete_old_autogenerate_files() {

    $dir = PSEUDO_TRANSIENT_DATA_DIR;

    if ( ! is_dir( $dir ) ) {
        return false;
    }

    $files = scandir( $dir );
    if ( $files === false ) {
        return false;
    }

    $now = time();
    $files_deleted = 0;

    foreach ( $files as $file ) {
        if ( in_array( $file, [ '.', '..' ], true ) || strpos( $file, '_expire.json' ) === false ) {
            continue;
        }

        $expire_file_path = trailingslashit( $dir ) . $file;
        $value_file_path = str_replace( '_expire.json', '.json', $expire_file_path );

        if ( is_file( $expire_file_path ) ) {
            $expire_contents = file_get_contents( $expire_file_path );
            $expire_timestamp = json_decode( $expire_contents, true );

            // Delete files if expired
            if ( $expire_timestamp < $now ) {
                if ( wp_delete_file( $expire_file_path ) && wp_delete_file( $value_file_path ) ) {
                    $files_deleted++;
                }
            }
        }
    }

    return $files_deleted;
}


// Schedule directory cleanup daily
add_action( 'wp_scheduled_delete', 'gvn_delete_old_autogenerate_files' );

So here is how it works:

gvn_set_pseudo_transient – instead of set_transient

gvn_get_pseudo_transient – instead of get_transient

gvn_delete_old_autogenerate_files – is scheduled to clear expired transient files from the directory.

The code will automatically work by just putting it as a snippet to functions.php or Snippets plugin.

The only check should be made is making sure wp-content/uploads is writable (which usually is)


Discover more from WP DEV - Elvin Haci

Subscribe to get the latest posts sent to your email.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from WP DEV - Elvin Haci

Subscribe now to keep reading and get access to the full archive.

Continue reading