diff --git a/composer.json b/composer.json index 4142a527..3da063d2 100644 --- a/composer.json +++ b/composer.json @@ -41,6 +41,8 @@ "cache flush-group", "cache get", "cache incr", + "cache patch", + "cache pluck", "cache replace", "cache set", "cache supports", @@ -48,9 +50,11 @@ "transient", "transient delete", "transient get", + "transient list", + "transient patch", + "transient pluck", "transient set", - "transient type", - "transient list" + "transient type" ] }, "autoload": { diff --git a/features/cache-patch.feature b/features/cache-patch.feature new file mode 100644 index 00000000..770cb14a --- /dev/null +++ b/features/cache-patch.feature @@ -0,0 +1,89 @@ +Feature: Patch command available for the object cache + + Scenario: Nested values from cache can be updated at any depth. + Given a WP install + And a wp-content/mu-plugins/test-harness.php file: + """php + 'bar'] ); + wp_cache_set( 'other_key', ['fuz' => 'biz'] ); + + $complex_key = (object) [ + 'foo' => (object) [ + 'bar' => (object) [ + 'baz' => 2, + ], + ], + ]; + wp_cache_set( 'complex_key', $complex_key ); + }; + + WP_CLI::add_hook( 'before_invoke:cache patch', $set_foo ); + """ + + When I run `wp cache patch insert my_key fuz baz` + Then STDOUT should be: + """ + Success: Updated cache key 'my_key'. + """ + + When I run `wp cache patch insert complex_key foo bar fuz 34` + Then STDOUT should be: + """ + Success: Updated cache key 'complex_key'. + """ + + When I try `wp cache patch insert unknown_key foo bar` + Then STDERR should be: + """ + Error: Cannot create key "foo" on data type boolean + """ + + When I run `wp cache patch update my_key foo test` + Then STDOUT should be: + """ + Success: Updated cache key 'my_key'. + """ + + When I run `wp cache patch update other_key fuz biz` + Then STDOUT should be: + """ + Success: Value passed for cache key 'other_key' is unchanged. + """ + + When I run `wp cache patch update complex_key foo bar baz 34` + Then STDOUT should be: + """ + Success: Updated cache key 'complex_key'. + """ + + When I try `wp cache patch update unknown_key foo test` + Then STDERR should be: + """ + Error: No data exists for key "foo" + """ + + When I try `wp cache patch update my_key bar test` + Then STDERR should be: + """ + Error: No data exists for key "bar" + """ + + When I run `wp cache patch delete my_key foo` + Then STDOUT should be: + """ + Success: Updated cache key 'my_key'. + """ + + When I try `wp cache patch delete unknown_key foo` + Then STDERR should be: + """ + Error: No data exists for key "foo" + """ + + When I try `wp cache patch delete my_key bar` + Then STDERR should be: + """ + Error: No data exists for key "bar" + """ diff --git a/features/cache-pluck.feature b/features/cache-pluck.feature new file mode 100644 index 00000000..bec68e34 --- /dev/null +++ b/features/cache-pluck.feature @@ -0,0 +1,40 @@ +Feature: Pluck command available for the object cache + + Scenario: Nested values from cache can be retrieved at any depth. + Given a WP install + And a wp-content/mu-plugins/test-harness.php file: + """php + 'bar'] ); + wp_cache_set( 'my_key_2', ['foo' => ['bar' => 'baz']] ); + wp_cache_set( 'my_key_3', ['foo' => 'bar_custom'], 'my_custom_group' ); + }; + + WP_CLI::add_hook( 'before_invoke:cache pluck', $set_foo ); + """ + + When I run `wp cache pluck my_key foo` + Then STDOUT should be: + """ + bar + """ + + When I run `wp cache pluck my_key_2 foo bar` + Then STDOUT should be: + """ + baz + """ + + When I run `wp cache pluck my_key_3 foo --group=my_custom_group` + Then STDOUT should be: + """ + bar_custom + """ + + When I try `wp cache pluck unknown_key test` + Then STDERR should be: + """ + Warning: No object found for the key 'unknown_key' in group 'default' + """ + diff --git a/features/transient-patch.feature b/features/transient-patch.feature new file mode 100644 index 00000000..f44628db --- /dev/null +++ b/features/transient-patch.feature @@ -0,0 +1,331 @@ +Feature: Patch command available for the transient cache + + Scenario: Nested values from transient can be inserted at any depth. + Given a WP install + And I run `wp eval "set_transient( 'my_key', ['foo' => 'bar'] );"` + And I run `wp eval "set_transient( 'my_key_2', ['foo' => ['bar' => 'baz']] );"` + + When I run `wp transient patch insert my_key fuz baz` + Then STDOUT should be: + """ + Success: Updated transient 'my_key'. + """ + + When I run `wp transient get my_key --format=json` + Then STDOUT should be: + """ + {"foo":"bar","fuz":"baz"} + """ + + When I run `wp transient patch insert my_key foo bar` + Then STDOUT should be: + """ + Success: Value passed for transient 'my_key' is unchanged. + """ + + When I run `wp transient get my_key --format=json` + Then STDOUT should be: + """ + {"foo":"bar","fuz":"baz"} + """ + + When I run `wp transient patch insert my_key_2 foo fuz biz` + Then STDOUT should be: + """ + Success: Updated transient 'my_key_2'. + """ + + When I run `wp transient get my_key_2 --format=json` + Then STDOUT should be: + """ + {"foo":{"bar":"baz","fuz":"biz"}} + """ + + When I run `wp transient patch insert my_key_2 foo bar baz` + Then STDOUT should be: + """ + Success: Value passed for transient 'my_key_2' is unchanged. + """ + + When I run `wp transient get my_key_2 --format=json` + Then STDOUT should be: + """ + {"foo":{"bar":"baz","fuz":"biz"}} + """ + + When I try `wp transient patch insert unknown_key foo bar` + Then STDERR should be: + """ + Error: Cannot create key "foo" on data type boolean + """ + + Scenario: Nested values from transient can be updated at any depth. + Given a WP install + And I run `wp eval "set_transient( 'my_key', ['foo' => 'bar'] );"` + And I run `wp eval "set_transient( 'my_key_2', ['foo' => ['bar' => 'baz']] );"` + + When I run `wp transient patch update my_key foo baz` + Then STDOUT should be: + """ + Success: Updated transient 'my_key'. + """ + + When I run `wp transient get my_key --format=json` + Then STDOUT should be: + """ + {"foo":"baz"} + """ + + When I run `wp transient patch update my_key foo baz` + Then STDOUT should be: + """ + Success: Value passed for transient 'my_key' is unchanged. + """ + + When I run `wp transient get my_key --format=json` + Then STDOUT should be: + """ + {"foo":"baz"} + """ + + When I run `wp transient patch update my_key_2 foo bar biz` + Then STDOUT should be: + """ + Success: Updated transient 'my_key_2'. + """ + + When I run `wp transient get my_key_2 --format=json` + Then STDOUT should be: + """ + {"foo":{"bar":"biz"}} + """ + + When I run `wp transient patch update my_key_2 foo bar biz` + Then STDOUT should be: + """ + Success: Value passed for transient 'my_key_2' is unchanged. + """ + + When I run `wp transient get my_key_2 --format=json` + Then STDOUT should be: + """ + {"foo":{"bar":"biz"}} + """ + + When I try `wp transient patch update unknown_key foo bar` + Then STDERR should be: + """ + Error: No data exists for key "foo" + """ + + Scenario: Nested values from transient can be deleted at any depth. + Given a WP install + And I run `wp eval "set_transient( 'my_key', ['foo' => 'bar'] );"` + And I run `wp eval "set_transient( 'my_key_2', ['foo' => ['bar' => 'baz']] );"` + + When I run `wp transient patch delete my_key foo` + Then STDOUT should be: + """ + Success: Updated transient 'my_key'. + """ + + When I run `wp transient get my_key --format=json` + Then STDOUT should be: + """ + [] + """ + + When I run `wp transient patch delete my_key_2 foo bar` + Then STDOUT should be: + """ + Success: Updated transient 'my_key_2'. + """ + + When I run `wp transient get my_key_2 --format=json` + Then STDOUT should be: + """ + {"foo":[]} + """ + + When I run `wp transient patch delete my_key_2 foo` + Then STDOUT should be: + """ + Success: Updated transient 'my_key_2'. + """ + + When I run `wp transient get my_key_2 --format=json` + Then STDOUT should be: + """ + [] + """ + + When I try `wp transient patch delete unknown_key foo` + Then STDERR should be: + """ + Error: No data exists for key "foo" + """ + + Scenario: Nested values from site transient can be inserted at any depth. + Given a WP multisite install + And I run `wp eval "set_site_transient( 'my_key', ['foo' => 'bar'] );"` + And I run `wp eval "set_site_transient( 'my_key_2', ['foo' => ['bar' => 'baz']] );"` + + When I run `wp transient patch insert my_key fuz baz --network` + Then STDOUT should be: + """ + Success: Updated transient 'my_key'. + """ + + When I run `wp transient get my_key --format=json --network` + Then STDOUT should be: + """ + {"foo":"bar","fuz":"baz"} + """ + + When I run `wp transient patch insert my_key foo bar --network` + Then STDOUT should be: + """ + Success: Value passed for transient 'my_key' is unchanged. + """ + + When I run `wp transient get my_key --format=json --network` + Then STDOUT should be: + """ + {"foo":"bar","fuz":"baz"} + """ + + When I run `wp transient patch insert my_key_2 foo fuz biz --network` + Then STDOUT should be: + """ + Success: Updated transient 'my_key_2'. + """ + + When I run `wp transient get my_key_2 --format=json --network` + Then STDOUT should be: + """ + {"foo":{"bar":"baz","fuz":"biz"}} + """ + + When I run `wp transient patch insert my_key_2 foo bar baz --network` + Then STDOUT should be: + """ + Success: Value passed for transient 'my_key_2' is unchanged. + """ + + When I run `wp transient get my_key_2 --format=json --network` + Then STDOUT should be: + """ + {"foo":{"bar":"baz","fuz":"biz"}} + """ + + When I try `wp transient patch insert unknown_key foo bar --network` + Then STDERR should be: + """ + Error: Cannot create key "foo" on data type boolean + """ + + Scenario: Nested values from site transient can be updated at any depth. + Given a WP multisite install + And I run `wp eval "set_site_transient( 'my_key', ['foo' => 'bar'] );"` + And I run `wp eval "set_site_transient( 'my_key_2', ['foo' => ['bar' => 'baz']] );"` + + When I run `wp transient patch update my_key foo baz --network` + Then STDOUT should be: + """ + Success: Updated transient 'my_key'. + """ + + When I run `wp transient get my_key --format=json --network` + Then STDOUT should be: + """ + {"foo":"baz"} + """ + + When I run `wp transient patch update my_key foo baz --network` + Then STDOUT should be: + """ + Success: Value passed for transient 'my_key' is unchanged. + """ + + When I run `wp transient get my_key --format=json --network` + Then STDOUT should be: + """ + {"foo":"baz"} + """ + + When I run `wp transient patch update my_key_2 foo bar biz --network` + Then STDOUT should be: + """ + Success: Updated transient 'my_key_2'. + """ + + When I run `wp transient get my_key_2 --format=json --network` + Then STDOUT should be: + """ + {"foo":{"bar":"biz"}} + """ + + When I run `wp transient patch update my_key_2 foo bar biz --network` + Then STDOUT should be: + """ + Success: Value passed for transient 'my_key_2' is unchanged. + """ + + When I run `wp transient get my_key_2 --format=json --network` + Then STDOUT should be: + """ + {"foo":{"bar":"biz"}} + """ + + When I try `wp transient patch update unknown_key foo bar --network` + Then STDERR should be: + """ + Error: No data exists for key "foo" + """ + + Scenario: Nested values from site transient can be deleted at any depth. + Given a WP multisite install + And I run `wp eval "set_site_transient( 'my_key', ['foo' => 'bar'] );"` + And I run `wp eval "set_site_transient( 'my_key_2', ['foo' => ['bar' => 'baz']] );"` + + When I run `wp transient patch delete my_key foo --network` + Then STDOUT should be: + """ + Success: Updated transient 'my_key'. + """ + + When I run `wp transient get my_key --format=json --network` + Then STDOUT should be: + """ + [] + """ + + When I run `wp transient patch delete my_key_2 foo bar --network` + Then STDOUT should be: + """ + Success: Updated transient 'my_key_2'. + """ + + When I run `wp transient get my_key_2 --format=json --network` + Then STDOUT should be: + """ + {"foo":[]} + """ + + When I run `wp transient patch delete my_key_2 foo --network` + Then STDOUT should be: + """ + Success: Updated transient 'my_key_2'. + """ + + When I run `wp transient get my_key_2 --format=json --network` + Then STDOUT should be: + """ + [] + """ + + When I try `wp transient patch delete unknown_key foo --network` + Then STDERR should be: + """ + Error: No data exists for key "foo" + """ diff --git a/features/transient-pluck.feature b/features/transient-pluck.feature new file mode 100644 index 00000000..61be9ade --- /dev/null +++ b/features/transient-pluck.feature @@ -0,0 +1,47 @@ +Feature: Pluck command available for the transient cache + + Scenario: Nested values from transient can be retrieved at any depth. + Given a WP install + And I run `wp eval "set_transient( 'my_key', ['foo' => 'bar'] );"` + And I run `wp eval "set_transient( 'my_key_2', ['foo' => ['bar' => 'baz']] );"` + + When I run `wp transient pluck my_key foo` + Then STDOUT should be: + """ + bar + """ + + When I run `wp transient pluck my_key_2 foo bar` + Then STDOUT should be: + """ + baz + """ + + When I try `wp transient pluck unknown_key foo` + Then STDERR should be: + """ + Warning: Transient with key "unknown_key" is not set. + """ + + Scenario: Nested values from site transient can be retrieved at any depth. + Given a WP multisite install + And I run `wp eval "set_site_transient( 'my_key', ['foo' => 'bar'] );"` + And I run `wp eval "set_site_transient( 'my_key_2', ['foo' => ['bar' => 'baz']] );"` + + When I run `wp transient pluck my_key foo --network` + Then STDOUT should be: + """ + bar + """ + + When I run `wp transient pluck my_key_2 foo bar --network` + Then STDOUT should be: + """ + baz + """ + + When I try `wp transient pluck unknown_key foo --network` + Then STDERR should be: + """ + Warning: Transient with key "unknown_key" is not set. + """ diff --git a/src/Cache_Command.php b/src/Cache_Command.php index b25ba1d7..b56eda0f 100644 --- a/src/Cache_Command.php +++ b/src/Cache_Command.php @@ -1,5 +1,8 @@ + * : Cache key. + * + * ... + * : The name(s) of the keys within the value to locate the value to pluck. + * + * [--group=] + * : Method for grouping data within the cache which allows the same key to be used across groups. + * --- + * default: default + * --- + * + * [--format=] + * : The output format of the value. + * --- + * default: plaintext + * options: + * - plaintext + * - json + * - yaml + * --- + */ + public function pluck( $args, $assoc_args ) { + list( $key ) = $args; + $group = Utils\get_flag_value( $assoc_args, 'group' ); + + $value = wp_cache_get( $key, $group ); + + if ( false === $value ) { + WP_CLI::warning( "No object found for the key '$key' in group '$group'" ); + exit; + } + + $key_path = array_map( + function ( $key ) { + if ( is_numeric( $key ) && ( (string) intval( $key ) === $key ) ) { + return (int) $key; + } + return $key; + }, + array_slice( $args, 1 ) + ); + + $traverser = new RecursiveDataStructureTraverser( $value ); + + try { + $value = $traverser->get( $key_path ); + } catch ( \Exception $e ) { + die( 1 ); + } + + WP_CLI::print_value( $value, $assoc_args ); + } + + /** + * Update a nested value from the cache. + * + * ## OPTIONS + * + * + * : Patch action to perform. + * --- + * options: + * - insert + * - update + * - delete + * --- + * + * + * : Cache key. + * + * ... + * : The name(s) of the keys within the value to locate the value to patch. + * + * [] + * : The new value. If omitted, the value is read from STDIN. + * + * [--group=] + * : Method for grouping data within the cache which allows the same key to be used across groups. + * --- + * default: default + * --- + * + * [--expiration=] + * : Define how long to keep the value, in seconds. `0` means as long as possible. + * --- + * default: 0 + * --- + * + * [--format=] + * : The serialization format for the value. + * --- + * default: plaintext + * options: + * - plaintext + * - json + * --- + */ + public function patch( $args, $assoc_args ) { + list( $action, $key ) = $args; + $group = Utils\get_flag_value( $assoc_args, 'group' ); + $expiration = Utils\get_flag_value( $assoc_args, 'expiration' ); + + $key_path = array_map( + function ( $key ) { + if ( is_numeric( $key ) && ( (string) intval( $key ) === $key ) ) { + return (int) $key; + } + + return $key; + }, + array_slice( $args, 2 ) + ); + + if ( 'delete' === $action ) { + $patch_value = null; + } else { + $stdin_value = Utils\has_stdin() + ? trim( WP_CLI::get_value_from_arg_or_stdin( $args, -1 ) ) + : null; + + if ( ! empty( $stdin_value ) ) { + $patch_value = WP_CLI::read_value( $stdin_value, $assoc_args ); + } elseif ( count( $key_path ) > 1 ) { + $patch_value = WP_CLI::read_value( array_pop( $key_path ), $assoc_args ); + } else { + $patch_value = null; + } + + if ( null === $patch_value ) { + WP_CLI::error( 'Please provide value to update.' ); + } + } + + /* Need to make a copy of $current_value here as it is modified by reference */ + $old_value = wp_cache_get( $key, $group ); + $current_value = $old_value; + if ( is_object( $old_value ) ) { + $current_value = clone $old_value; + } + + $traverser = new RecursiveDataStructureTraverser( $current_value ); + + try { + $traverser->$action( $key_path, $patch_value ); + } catch ( \Exception $e ) { + WP_CLI::error( $e->getMessage() ); + } + + $patched_value = $traverser->value(); + + if ( $patched_value === $old_value ) { + WP_CLI::success( "Value passed for cache key '$key' is unchanged." ); + } else { + $success = wp_cache_set( $key, $patched_value, $group, $expiration ); + if ( $success ) { + WP_CLI::success( "Updated cache key '$key'." ); + } else { + WP_CLI::error( "Could not update cache key '$key'." ); + } + } + } } diff --git a/src/Transient_Command.php b/src/Transient_Command.php index 3d21ab21..9166935a 100644 --- a/src/Transient_Command.php +++ b/src/Transient_Command.php @@ -1,5 +1,6 @@ display_items( $results ); } + /** + * Get a nested value from a transient. + * + * ## OPTIONS + * + * + * : Key for the transient. + * + * ... + * : The name(s) of the keys within the value to locate the value to pluck. + * + * [--format=] + * : The output format of the value. + * --- + * default: plaintext + * options: + * - plaintext + * - json + * - yaml + * --- + * + * [--network] + * : Get the value of a network|site transient. On single site, this is + * a specially-named cache key. On multisite, this is a global cache + * (instead of local to the site). + */ + public function pluck( $args, $assoc_args ) { + list( $key ) = $args; + + $func = Utils\get_flag_value( $assoc_args, 'network' ) ? 'get_site_transient' : 'get_transient'; + $value = $func( $key ); + + if ( false === $value ) { + WP_CLI::warning( 'Transient with key "' . $key . '" is not set.' ); + exit; + } + + $key_path = array_map( + function ( $key ) { + if ( is_numeric( $key ) && ( (string) intval( $key ) === $key ) ) { + return (int) $key; + } + return $key; + }, + array_slice( $args, 1 ) + ); + + $traverser = new RecursiveDataStructureTraverser( $value ); + + try { + $value = $traverser->get( $key_path ); + } catch ( \Exception $e ) { + die( 1 ); + } + + WP_CLI::print_value( $value, $assoc_args ); + } + + /** + * Update a nested value from a transient. + * + * ## OPTIONS + * + * + * : Patch action to perform. + * --- + * options: + * - insert + * - update + * - delete + * --- + * + * + * : Key for the transient. + * + * ... + * : The name(s) of the keys within the value to locate the value to patch. + * + * [] + * : The new value. If omitted, the value is read from STDIN. + * + * [--format=] + * : The serialization format for the value. + * --- + * default: plaintext + * options: + * - plaintext + * - json + * --- + * + * [--expiration=] + * : Time until expiration, in seconds. + * + * [--network] + * : Get the value of a network|site transient. On single site, this is + * a specially-named cache key. On multisite, this is a global cache + * (instead of local to the site). + */ + public function patch( $args, $assoc_args ) { + list( $action, $key ) = $args; + $expiration = (int) Utils\get_flag_value( $assoc_args, 'expiration', 0 ); + + $read_func = Utils\get_flag_value( $assoc_args, 'network' ) ? 'get_site_transient' : 'get_transient'; + $write_func = Utils\get_flag_value( $assoc_args, 'network' ) ? 'set_site_transient' : 'set_transient'; + + $key_path = array_map( + function ( $key ) { + if ( is_numeric( $key ) && ( (string) intval( $key ) === $key ) ) { + return (int) $key; + } + + return $key; + }, + array_slice( $args, 2 ) + ); + + if ( 'delete' === $action ) { + $patch_value = null; + } else { + $stdin_value = Utils\has_stdin() + ? trim( WP_CLI::get_value_from_arg_or_stdin( $args, -1 ) ) + : null; + + if ( ! empty( $stdin_value ) ) { + $patch_value = WP_CLI::read_value( $stdin_value, $assoc_args ); + } elseif ( count( $key_path ) > 1 ) { + $patch_value = WP_CLI::read_value( array_pop( $key_path ), $assoc_args ); + } else { + $patch_value = null; + } + + if ( null === $patch_value ) { + WP_CLI::error( 'Please provide value to update.' ); + } + } + + /* Need to make a copy of $current_value here as it is modified by reference */ + $old_value = $read_func( $key ); + $current_value = $old_value; + if ( is_object( $old_value ) ) { + $current_value = clone $old_value; + } + + $traverser = new RecursiveDataStructureTraverser( $current_value ); + + try { + $traverser->$action( $key_path, $patch_value ); + } catch ( \Exception $e ) { + WP_CLI::error( $e->getMessage() ); + } + + $patched_value = $traverser->value(); + + if ( $patched_value === $old_value ) { + WP_CLI::success( "Value passed for transient '$key' is unchanged." ); + } else { + $success = $write_func( $key, $patched_value, $expiration ); + if ( $success ) { + WP_CLI::success( "Updated transient '$key'." ); + } else { + WP_CLI::error( "Could not update transient '$key'." ); + } + } + } + /** * Retrieves the expiration time. *