diff --git a/composer.json b/composer.json index ccd2e9a2..96d3cd80 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,12 @@ "cache decr", "cache delete", "cache flush", + "cache flush-comment", "cache flush-group", + "cache flush-option", + "cache flush-post", + "cache flush-term", + "cache flush-user", "cache get", "cache incr", "cache patch", diff --git a/features/cache-flush-granular.feature b/features/cache-flush-granular.feature new file mode 100644 index 00000000..26c52972 --- /dev/null +++ b/features/cache-flush-granular.feature @@ -0,0 +1,399 @@ +Feature: Granular cache flushing operations + + @skip-object-cache + Scenario: Flush specific post cache + Given a WP install + And a wp-content/mu-plugins/test-harness.php file: + """ + 123, 'post_title' => 'Test' ), 'posts' ); + wp_cache_set( 123, array( 'key' => 'value' ), 'post_meta' ); + }; + $verify_cache_cleared = function(){ + $post = wp_cache_get( 123, 'posts' ); + $meta = wp_cache_get( 123, 'post_meta' ); + if ( false !== $post || false !== $meta ) { + WP_CLI::error( 'Cache was not properly cleared.' ); + } + }; + WP_CLI::add_hook( 'before_invoke:cache flush-post', $cache_post ); + WP_CLI::add_hook( 'after_invoke:cache flush-post', $verify_cache_cleared ); + """ + + When I run `wp cache flush-post 123` + Then STDOUT should contain: + """ + Success: Post cache for ID 123 cleared. + """ + + @skip-object-cache + Scenario: Flush specific term cache + Given a WP install + And a wp-content/mu-plugins/test-harness.php file: + """ + 5 ), 'terms' ); + wp_cache_set( 5, array(), 'term_meta' ); + }; + $verify_cache_cleared = function(){ + $term = wp_cache_get( 5, 'terms' ); + $meta = wp_cache_get( 5, 'term_meta' ); + if ( false !== $term || false !== $meta ) { + WP_CLI::error( 'Cache was not properly cleared.' ); + } + }; + WP_CLI::add_hook( 'before_invoke:cache flush-term', $cache_term ); + WP_CLI::add_hook( 'after_invoke:cache flush-term', $verify_cache_cleared ); + """ + + When I run `wp cache flush-term 5` + Then STDOUT should contain: + """ + Success: Term cache for ID 5 cleared. + """ + + @skip-object-cache + Scenario: Flush cache for an existing term resolves its taxonomy + Given a WP install + + When I run `wp cache flush-term 1` + Then STDOUT should contain: + """ + Success: Term cache for ID 1 cleared. + """ + + @skip-object-cache + Scenario: Flush specific comment cache + Given a WP install + And a wp-content/mu-plugins/test-harness.php file: + """ + 1 ), 'users' ); + wp_cache_set( 1, array(), 'user_meta' ); + }; + $verify_cache_cleared = function(){ + $user = wp_cache_get( 1, 'users' ); + $meta = wp_cache_get( 1, 'user_meta' ); + if ( false !== $user || false !== $meta ) { + WP_CLI::error( 'Cache was not properly cleared.' ); + } + }; + WP_CLI::add_hook( 'before_invoke:cache flush-user', $cache_user ); + WP_CLI::add_hook( 'after_invoke:cache flush-user', $verify_cache_cleared ); + """ + + When I run `wp cache flush-user 1` + Then STDOUT should contain: + """ + Success: User cache for ID 1 cleared. + """ + + @skip-object-cache + Scenario: Flush specific option cache + Given a WP install + And a wp-content/mu-plugins/test-harness.php file: + """ + 'value' ), 'options' ); + }; + $verify_cache_cleared = function(){ + $option = wp_cache_get( 'my_option', 'options' ); + $alloptions = wp_cache_get( 'alloptions', 'options' ); + if ( false !== $option || false !== $alloptions ) { + WP_CLI::error( 'Cache was not properly cleared.' ); + } + }; + WP_CLI::add_hook( 'before_invoke:cache flush-option', $cache_option ); + WP_CLI::add_hook( 'after_invoke:cache flush-option', $verify_cache_cleared ); + """ + + When I run `wp cache flush-option my_option` + Then STDOUT should contain: + """ + Success: Option cache for 'my_option' cleared. + """ + + @skip-object-cache + Scenario: Invalid post ID fails gracefully + Given a WP install + + When I try `wp cache flush-post abc` + Then STDERR should contain: + """ + Please provide a valid post ID. + """ + + @skip-object-cache + Scenario: Invalid term ID fails gracefully + Given a WP install + + When I try `wp cache flush-term xyz` + Then STDERR should contain: + """ + Please provide a valid term ID. + """ + + @skip-object-cache + Scenario: Invalid comment ID fails gracefully + Given a WP install + + When I try `wp cache flush-comment notanumber` + Then STDERR should contain: + """ + Please provide a valid comment ID. + """ + + @skip-object-cache + Scenario: Invalid user ID fails gracefully + Given a WP install + + When I try `wp cache flush-user nope` + Then STDERR should contain: + """ + Please provide a valid user ID. + """ + + @skip-object-cache + Scenario Outline: Non-positive post IDs fail gracefully + Given a WP install + + When I try `wp cache flush-post ` + Then STDERR should contain: + """ + Please provide a valid post ID. + """ + + Examples: + | id | + | 0 | + | -1 | + + @skip-object-cache + Scenario Outline: Non-positive term IDs fail gracefully + Given a WP install + + When I try `wp cache flush-term ` + Then STDERR should contain: + """ + Please provide a valid term ID. + """ + + Examples: + | id | + | 0 | + | -1 | + + @skip-object-cache + Scenario Outline: Non-positive comment IDs fail gracefully + Given a WP install + + When I try `wp cache flush-comment ` + Then STDERR should contain: + """ + Please provide a valid comment ID. + """ + + Examples: + | id | + | 0 | + | -1 | + + @skip-object-cache + Scenario Outline: Non-positive user IDs fail gracefully + Given a WP install + + When I try `wp cache flush-user ` + Then STDERR should contain: + """ + Please provide a valid user ID. + """ + + Examples: + | id | + | 0 | + | -1 | + + @require-wp-6-1 @skip-object-cache + Scenario: Flush all post caches on WordPress 6.1+ + Given a WP install + And a wp-content/mu-plugins/test-harness.php file: + """ + 1 ), 'posts' ); + wp_cache_set( 'post_2', array( 'ID' => 2 ), 'posts' ); + wp_cache_set( 'meta_1', array( 'key' => 'value' ), 'post_meta' ); + }; + $verify_group_cleared = function(){ + if ( function_exists( 'wp_cache_supports' ) && wp_cache_supports( 'flush_group' ) ) { + $post1 = wp_cache_get( 'post_1', 'posts' ); + $post2 = wp_cache_get( 'post_2', 'posts' ); + $meta = wp_cache_get( 'meta_1', 'post_meta' ); + if ( false !== $post1 || false !== $post2 || false !== $meta ) { + WP_CLI::error( 'Group cache was not properly cleared.' ); + } + } + }; + WP_CLI::add_hook( 'before_invoke:cache flush-post', $cache_posts ); + WP_CLI::add_hook( 'after_invoke:cache flush-post', $verify_group_cleared ); + """ + + When I run `wp cache flush-post` + Then STDOUT should contain: + """ + Success: Post caches cleared. + """ + + @require-wp-6-1 @skip-object-cache + Scenario: Flush all term caches on WordPress 6.1+ + Given a WP install + And a wp-content/mu-plugins/test-harness.php file: + """ + 1 ), 'terms' ); + wp_cache_set( 'term_2', array( 'term_id' => 2 ), 'terms' ); + wp_cache_set( 'meta_1', array(), 'term_meta' ); + }; + $verify_group_cleared = function(){ + if ( function_exists( 'wp_cache_supports' ) && wp_cache_supports( 'flush_group' ) ) { + $term1 = wp_cache_get( 'term_1', 'terms' ); + $term2 = wp_cache_get( 'term_2', 'terms' ); + $meta = wp_cache_get( 'meta_1', 'term_meta' ); + if ( false !== $term1 || false !== $term2 || false !== $meta ) { + WP_CLI::error( 'Group cache was not properly cleared.' ); + } + } + }; + WP_CLI::add_hook( 'before_invoke:cache flush-term', $cache_terms ); + WP_CLI::add_hook( 'after_invoke:cache flush-term', $verify_group_cleared ); + """ + + When I run `wp cache flush-term` + Then STDOUT should contain: + """ + Success: Term caches cleared. + """ + + @require-wp-6-1 @skip-object-cache + Scenario: Flush all comment caches on WordPress 6.1+ + Given a WP install + And a wp-content/mu-plugins/test-harness.php file: + """ + 1 ), 'users' ); + wp_cache_set( 'user_2', array( 'ID' => 2 ), 'users' ); + wp_cache_set( 'meta_1', array(), 'user_meta' ); + }; + $verify_group_cleared = function(){ + if ( function_exists( 'wp_cache_supports' ) && wp_cache_supports( 'flush_group' ) ) { + $user1 = wp_cache_get( 'user_1', 'users' ); + $user2 = wp_cache_get( 'user_2', 'users' ); + $meta = wp_cache_get( 'meta_1', 'user_meta' ); + if ( false !== $user1 || false !== $user2 || false !== $meta ) { + WP_CLI::error( 'Group cache was not properly cleared.' ); + } + } + }; + WP_CLI::add_hook( 'before_invoke:cache flush-user', $cache_users ); + WP_CLI::add_hook( 'after_invoke:cache flush-user', $verify_group_cleared ); + """ + + When I run `wp cache flush-user` + Then STDOUT should contain: + """ + Success: User caches cleared. + """ + + @require-wp-6-1 @skip-object-cache + Scenario: Flush all option caches on WordPress 6.1+ + Given a WP install + And a wp-content/mu-plugins/test-harness.php file: + """ + ] + * : Post ID. If not specified, clears all post caches. + * + * ## EXAMPLES + * + * # Clear all post caches. + * $ wp cache flush-post + * Success: Post caches cleared. + * + * # Clear cache for a specific post. + * $ wp cache flush-post 123 + * Success: Post cache for ID 123 cleared. + * + * @param array $args Positional arguments. + */ + public function flush_post( $args ) { + if ( ! empty( $args ) ) { + if ( ! is_numeric( $args[0] ) || (int) $args[0] <= 0 ) { + WP_CLI::error( 'Please provide a valid post ID.' ); + } + $post_id = (int) $args[0]; + clean_post_cache( $post_id ); + WP_CLI::success( "Post cache for ID $post_id cleared." ); + } else { + if ( ! function_exists( 'wp_cache_supports' ) || ! wp_cache_supports( 'flush_group' ) ) { + WP_CLI::error( 'Flushing all post caches requires WordPress 6.1+' ); + } + $posts_flushed = wp_cache_flush_group( 'posts' ); + $post_meta_flushed = wp_cache_flush_group( 'post_meta' ); + if ( ! $posts_flushed || ! $post_meta_flushed ) { + WP_CLI::error( 'Failed to flush post caches.' ); + } + WP_CLI::success( 'Post caches cleared.' ); + } + } + + /** + * Clears term related caches. + * + * @subcommand flush-term + * + * ## OPTIONS + * + * [] + * : Term ID. If not specified, clears all term caches. + * + * ## EXAMPLES + * + * # Clear all term caches. + * $ wp cache flush-term + * Success: Term caches cleared. + * + * # Clear cache for a specific term. + * $ wp cache flush-term 5 + * Success: Term cache for ID 5 cleared. + * + * @param array $args Positional arguments. + */ + public function flush_term( $args ) { + if ( ! empty( $args ) ) { + if ( ! is_numeric( $args[0] ) || (int) $args[0] <= 0 ) { + WP_CLI::error( 'Please provide a valid term ID.' ); + } + $term_id = (int) $args[0]; + $term = get_term( $term_id ); + $taxonomy = ( $term && ! is_wp_error( $term ) ) ? $term->taxonomy : ''; + clean_term_cache( $term_id, $taxonomy ); + wp_cache_delete( $term_id, 'term_meta' ); + WP_CLI::success( "Term cache for ID $term_id cleared." ); + } else { + if ( ! function_exists( 'wp_cache_supports' ) || ! wp_cache_supports( 'flush_group' ) ) { + WP_CLI::error( 'Flushing all term caches requires WordPress 6.1+' ); + } + $terms_flushed = wp_cache_flush_group( 'terms' ); + $term_meta_flushed = wp_cache_flush_group( 'term_meta' ); + if ( ! $terms_flushed || ! $term_meta_flushed ) { + WP_CLI::error( 'Failed to flush term caches.' ); + } + WP_CLI::success( 'Term caches cleared.' ); + } + } + + /** + * Clears comment related caches. + * + * @subcommand flush-comment + * + * ## OPTIONS + * + * [] + * : Comment ID. If not specified, clears all comment caches. + * + * ## EXAMPLES + * + * # Clear all comment caches. + * $ wp cache flush-comment + * Success: Comment caches cleared. + * + * # Clear cache for a specific comment. + * $ wp cache flush-comment 42 + * Success: Comment cache for ID 42 cleared. + * + * @param array $args Positional arguments. + */ + public function flush_comment( $args ) { + if ( ! empty( $args ) ) { + if ( ! is_numeric( $args[0] ) || (int) $args[0] <= 0 ) { + WP_CLI::error( 'Please provide a valid comment ID.' ); + } + $comment_id = (int) $args[0]; + clean_comment_cache( $comment_id ); + wp_cache_delete( $comment_id, 'comment_meta' ); + WP_CLI::success( "Comment cache for ID $comment_id cleared." ); + } else { + if ( ! function_exists( 'wp_cache_supports' ) || ! wp_cache_supports( 'flush_group' ) ) { + WP_CLI::error( 'Flushing all comment caches requires WordPress 6.1+' ); + } + $comment_flushed = wp_cache_flush_group( 'comment' ); + $comment_meta_flushed = wp_cache_flush_group( 'comment_meta' ); + if ( ! $comment_flushed || ! $comment_meta_flushed ) { + WP_CLI::error( 'Failed to flush comment caches.' ); + } + WP_CLI::success( 'Comment caches cleared.' ); + } + } + + /** + * Clears user related caches. + * + * @subcommand flush-user + * + * ## OPTIONS + * + * [] + * : User ID. If not specified, clears all user caches. + * + * ## EXAMPLES + * + * # Clear all user caches. + * $ wp cache flush-user + * Success: User caches cleared. + * + * # Clear cache for a specific user. + * $ wp cache flush-user 1 + * Success: User cache for ID 1 cleared. + * + * @param array $args Positional arguments. + */ + public function flush_user( $args ) { + if ( ! empty( $args ) ) { + if ( ! is_numeric( $args[0] ) || (int) $args[0] <= 0 ) { + WP_CLI::error( 'Please provide a valid user ID.' ); + } + $user_id = (int) $args[0]; + clean_user_cache( $user_id ); + wp_cache_delete( $user_id, 'user_meta' ); + WP_CLI::success( "User cache for ID $user_id cleared." ); + } else { + if ( ! function_exists( 'wp_cache_supports' ) || ! wp_cache_supports( 'flush_group' ) ) { + WP_CLI::error( 'Flushing all user caches requires WordPress 6.1+' ); + } + $users_flushed = wp_cache_flush_group( 'users' ); + $user_meta_flushed = wp_cache_flush_group( 'user_meta' ); + if ( ! $users_flushed || ! $user_meta_flushed ) { + WP_CLI::error( 'Failed to flush user caches.' ); + } + WP_CLI::success( 'User caches cleared.' ); + } + } + + /** + * Clears option related caches. + * + * @subcommand flush-option + * + * ## OPTIONS + * + * [] + * : Option name. If not specified, clears all option caches. + * + * ## EXAMPLES + * + * # Clear all option caches. + * $ wp cache flush-option + * Success: Option caches cleared. + * + * # Clear cache for a specific option. + * $ wp cache flush-option my_option + * Success: Option cache for 'my_option' cleared. + * + * @param array $args Positional arguments. + */ + public function flush_option( $args ) { + if ( ! empty( $args ) ) { + $option_name = $args[0]; + wp_cache_delete( 'alloptions', 'options' ); + wp_cache_delete( $option_name, 'options' ); + WP_CLI::success( "Option cache for '$option_name' cleared." ); + } else { + if ( ! function_exists( 'wp_cache_supports' ) || ! wp_cache_supports( 'flush_group' ) ) { + WP_CLI::error( 'Flushing all option caches requires WordPress 6.1+' ); + } + $options_flushed = wp_cache_flush_group( 'options' ); + if ( ! $options_flushed ) { + WP_CLI::error( 'Failed to flush option caches.' ); + } + WP_CLI::success( 'Option caches cleared.' ); + } + } }