diff --git a/readme.txt b/readme.txt index 07c4daa2a0..45e94879c0 100644 --- a/readme.txt +++ b/readme.txt @@ -1,5 +1,10 @@ == Changelog == += [TBD] TBD = + +* Feature - Add the `has( string $key, string $expiration_trigger = '' ): bool`; add the `&$found` parameter to the `Tribe__Cache::get` method. +* Feature - Add the `has_shape( mixed $array, array $shape ): bool ` method to the `Tribe__Utils__Array` class. + = [5.2.3] 2024-02-19 = * Tweak - Refactor JS logic to prevent ticketing of recurring events. [ET-1936] diff --git a/src/Tribe/Cache.php b/src/Tribe/Cache.php index 97f8868bbe..a70d47d68f 100755 --- a/src/Tribe/Cache.php +++ b/src/Tribe/Cache.php @@ -1,4 +1,9 @@ non_persistent_keys[ $id ] ) ? 'tribe-events-non-persistent' : 'tribe-events'; - $value = wp_cache_get( $this->get_id( $id, $expiration_trigger ), $group ); + public function get( $id, $expiration_trigger = '', $default = false, $expiration = 0, $args = [], ?bool &$found = false ) { + $group = isset( $this->non_persistent_keys[ $id ] ) ? 'tribe-events-non-persistent' : 'tribe-events'; + $value = wp_cache_get( $this->get_id( $id, $expiration_trigger ), $group, false, $found ); // Value found. if ( false !== $value ) { @@ -149,7 +160,7 @@ public function get_transient( $id, $expiration_trigger = '' ) { * @return bool */ public function delete( $id, $expiration_trigger = '' ) { - $group = isset( $this->non_persistent_keys[ $id ] ) ? 'tribe-events-non-persistent' : 'tribe-events'; + $group = isset( $this->non_persistent_keys[ $id ] ) ? 'tribe-events-non-persistent' : 'tribe-events'; // Delete from non-persistent keys list. if ( 'tribe-events-non-persistent' === $group ) { @@ -369,7 +380,6 @@ public function make_key( $components, $prefix = '', $sort = true ) { * * @return boolean Whether the offset exists in the cache. *@link http://php.net/manual/en/arrayaccess.offsetexists.php - * */ #[\ReturnTypeWillChange] public function offsetExists( $offset ): bool { @@ -499,7 +509,7 @@ public function warmup_post_caches( $post_ids, $update_post_meta_cache = false ) do { $limit_clause = $limit < 0 ? sprintf( 'LIMIT %d,%d', $limit * $page, $limit ) : ''; - $page++; + ++$page; $these_ids = array_splice( $buffer, 0, $limit ); $interval = implode( ',', array_map( 'absint', $these_ids ) ); $posts_query = "SELECT * FROM {$wpdb->posts} WHERE ID IN ({$interval}) {$limit_clause}"; @@ -604,7 +614,6 @@ public function get_chunkable_transient( $id, $expiration_trigger = '' ) { * * The method will redirect to the `set_transient` function if the site is using object caching. * - * * @since 4.13.3 * * @param string $id The transient ID. @@ -643,4 +652,21 @@ public function set_chunkable_transient( $id, $value, $expiration = 0, $expirati return true; } + + /** + * Checks whether a value is set in the cache or not. + * + * @since TBD + * + * @param string $id The key for the cached value. + * @param string $expiration_trigger Optional. Hook to trigger cache invalidation. + * + * @return bool Whether the value is set in the cache or not. + */ + public function has( $id, $expiration_trigger = '' ): bool { + $group = isset( $this->non_persistent_keys[ $id ] ) ? 'tribe-events-non-persistent' : 'tribe-events'; + wp_cache_get( $this->get_id( $id, $expiration_trigger ), $group, false, $found ); + + return $found; + } } diff --git a/src/Tribe/Utils/Array.php b/src/Tribe/Utils/Array.php index 6acbb5dd61..26c4bbb5f1 100644 --- a/src/Tribe/Utils/Array.php +++ b/src/Tribe/Utils/Array.php @@ -732,5 +732,55 @@ public static function usearch( $needle, array $haystack, callable $callback ) { return false; } + + /** + * Checks if an array has a specific shape. + * + * @since TBD + * + * @param array $array The array to check. + * @param array $shape The shape to check for. Each key, either a string or an integer, + * maps to a callable that will be used to validate the value at that key. + * The callable must have the signature `fn( mixed $value ) :bool`. + * @param bool $strict Whether the array should only contain the keys specified in the shape. + * + * @return bool Whether the array has the specified shape. + */ + public static function has_shape( $array, array $shape, bool $strict = false ): bool { + if ( ! is_array( $array ) ) { + return false; + } + + if ( + $strict + && ( + array_intersect_key( $array, $shape ) !== $array + || + array_diff_key( $array, $shape ) !== [] + ) + ) { + return false; + } + + if ( count( array_intersect_key( $shape, $array ) ) < count( $shape ) ) { + return false; + } + + foreach ( $shape as $key => $check ) { + if ( ! is_callable( $check ) ) { + throw new \BadMethodCallException( 'The shape array must contain only callables as values.' ); + } + + try { + if ( ! $check( $array[ $key ] ) ) { + return false; + } + } catch ( \Throwable $th ) { + return false; + } + } + + return true; + } } } diff --git a/tests/unit/Tribe/Utils/ArrayTest.php b/tests/unit/Tribe/Utils/Array_Test.php similarity index 55% rename from tests/unit/Tribe/Utils/ArrayTest.php rename to tests/unit/Tribe/Utils/Array_Test.php index 5133f4e3a4..8b658586d8 100644 --- a/tests/unit/Tribe/Utils/ArrayTest.php +++ b/tests/unit/Tribe/Utils/Array_Test.php @@ -2,9 +2,9 @@ use Tribe__Utils__Array as Arr; -class Tribe__Utils__Array_Test extends \Codeception\Test\Unit { +class Array_Test extends \Codeception\Test\Unit { public function shape_filter_data_provider() { - $test_shape = [ + $test_shape = [ 'a' => [ 'deeply' => @@ -12,7 +12,7 @@ public function shape_filter_data_provider() { 'nested' => [ 'key', - 'key_2' + 'key_2', ], ], ], @@ -23,12 +23,12 @@ public function shape_filter_data_provider() { 'nested' => [ 'key', - 'key_2' + 'key_2', ], ], ], 'key_2', - 'key_3' + 'key_3', ]; $test_optional_shape = [ '?a' => @@ -38,7 +38,7 @@ public function shape_filter_data_provider() { 'nested' => [ 'key', - 'key_2' + 'key_2', ], ], ], @@ -49,16 +49,16 @@ public function shape_filter_data_provider() { '?nested' => [ 'key', - 'key_2' + 'key_2', ], ], ], '?key_2', - 'key_3' + 'key_3', ]; return [ - 'empty array, non empty shape' => [ + 'empty array, non empty shape' => [ 'array' => [], 'shape' => $test_shape, 'expected' => [ @@ -69,7 +69,7 @@ public function shape_filter_data_provider() { 'nested' => [ 'key' => null, - 'key_2' => null + 'key_2' => null, ], ], ], @@ -80,7 +80,7 @@ public function shape_filter_data_provider() { 'nested' => [ 'key' => null, - 'key_2' => null + 'key_2' => null, ], ], ], @@ -88,7 +88,7 @@ public function shape_filter_data_provider() { 'key_3' => null, ], ], - 'empty array, optional shape' => [ + 'empty array, optional shape' => [ 'array' => [], 'shape' => $test_optional_shape, 'expected' => [ @@ -96,15 +96,15 @@ public function shape_filter_data_provider() { [ 'deeply' => [], ], - 'key_3' => null + 'key_3' => null, ], ], - 'empty array, empty shape' => [ + 'empty array, empty shape' => [ 'array' => [], 'shape' => [], 'expected' => [], ], - 'non-empty array, empty shape' => [ + 'non-empty array, empty shape' => [ 'array' => [ 'a' => [ @@ -113,7 +113,7 @@ public function shape_filter_data_provider() { 'nested' => [ 'key' => 23, - 'key_2' => 89 + 'key_2' => 89, ], ], ], @@ -134,7 +134,7 @@ public function shape_filter_data_provider() { 'shape' => [], 'expected' => [], ], - 'test shape on non-empty array' => [ + 'test shape on non-empty array' => [ 'array' => [ 'a' => [ @@ -143,7 +143,7 @@ public function shape_filter_data_provider() { 'nested' => [ 'key' => 23, - 'key_2' => 89 + 'key_2' => 89, ], ], ], @@ -158,7 +158,7 @@ public function shape_filter_data_provider() { 'nested' => [ 'key' => 23, - 'key_2' => 89 + 'key_2' => 89, ], ], ], @@ -177,13 +177,13 @@ public function shape_filter_data_provider() { 'key_3' => null, ], ], - 'test optional shape on non-empty array' => [ + 'test optional shape on non-empty array' => [ 'array' => [ 'a' => [ 'b' => [ 'c' => [ 'd' ] ], ], - 'key_3' => 'foo-bar' + 'key_3' => 'foo-bar', ], 'shape' => $test_optional_shape, 'expected' => [ @@ -205,7 +205,7 @@ public function shape_filter_data_provider() { 'key_3' => 'foo-bar', ], ], - 'test shape on diff. sorted array' => [ + 'test shape on diff. sorted array' => [ 'array' => [ 'key_2' => 'bar-baz', 'another' => @@ -293,7 +293,7 @@ public function shape_filter_data_provider() { 'key_2' => 'bar-baz', 'key_3' => 'foo-bar', ], - ] + ], ]; } @@ -306,10 +306,10 @@ public function test_shape_filter( array $input, array $shape, array $expected ) } public function usearch_data_provider() { - $value_gt_needle = static function ( $needle, $value ): bool { + $value_gt_needle = static function ( $needle, $value ): bool { return $value > $needle; }; - $matches_needle = static function ( $needle, $value ): bool { + $matches_needle = static function ( $needle, $value ): bool { return $value === $needle; }; $callback_using_value_and_key = static function ( $needle, $value, $key ): bool { @@ -317,31 +317,39 @@ public function usearch_data_provider() { }; return [ - 'empty haysatck' => [ 'foo', [], false, $value_gt_needle ], - 'haystack not contains needle' => [ + 'empty haysatck' => [ 'foo', [], false, $value_gt_needle ], + 'haystack not contains needle' => [ 23, [ 'foo', 'bar', 'baz' ], false, - $value_gt_needle + $value_gt_needle, ], - 'haystack contains 1 needle' => [ 23, [ 89, 23, 113, 17 ], 1, $matches_needle ], - 'haystack contains multiple needles' => [ + 'haystack contains 1 needle' => [ 23, [ 89, 23, 113, 17 ], 1, $matches_needle ], + 'haystack contains multiple needles' => [ 23, [ 89, 23, 113, 17, 23, 11, 23 ], 1, - $matches_needle + $matches_needle, ], 'haystack contains multiple needles w/ string keys' => [ 23, - [ 'one' => 89, 'two' => 23, 'three' => 23 ], + [ + 'one' => 89, + 'two' => 23, + 'three' => 23, + ], 'two', - $matches_needle + $matches_needle, ], - 'callback using value and key' => [ + 'callback using value and key' => [ 23, - [ 'one' => 89, 'two' => 23, 'three' => 23 ], + [ + 'one' => 89, + 'two' => 23, + 'three' => 23, + ], 'three', - $callback_using_value_and_key + $callback_using_value_and_key, ], ]; } @@ -352,4 +360,132 @@ public function usearch_data_provider() { public function test_usearch( $needle, array $haystack, $expected, callable $callback ) { $this->assertEquals( $expected, Arr::usearch( $needle, $haystack, $callback ) ); } + + public function has_shape_data_provider(): array { + return [ + 'not an array' => [ 'foo', [], true, false ], + 'empty array, empty shape' => [ [], [], true, true ], + 'empty array, non-empty shape, strict' => [ + [], + [ 'foo' => 'is_string' ], + true, + false, + ], + 'empty array, non-empty shape, non-strict' => [ + [], + [ 'foo' => 'is_string' ], + false, + false, + ], + 'non-empty array, function shape, missing key, strict' => [ + [ 'foo' => 23 ], + [ 'bar' => 'is_string' ], + true, + false, + ], + 'non-empty array, function shape, missing key, non-strict' => [ + [ 'foo' => 23 ], + [ 'bar' => 'is_string' ], + false, + false, + ], + 'non-empty array, function shape, extra key, strict' => [ + [ + 'foo' => 23, + 'bar' => 'baz', + ], + [ 'foo' => 'is_int' ], + true, + false, + ], + 'non-empty array, function shape, extra key, non-strict' => [ + [ + 'foo' => 23, + 'bar' => 'baz', + ], + [ 'foo' => 'is_int' ], + false, + true, + ], + 'non-empty array, closure shape, all key fail failure, strict' => [ + [ + 'foo' => 23, + 'bar' => 89, + ], + [ + 'foo' => fn( $foo ) => $foo === 'hello', + 'bar' => fn( $bar ) => $bar === 'world', + ], + true, + false, + ], + 'non-empty array, closure shape, all key fail failure, non-strict' => [ + [ + 'foo' => 23, + 'bar' => 89, + ], + [ + 'foo' => fn( $foo ) => $foo === 'hello', + 'bar' => fn( $bar ) => $bar === 'world', + ], + false, + false, + ], + 'non-empty array, closure shape, all key pass, strict' => [ + [ + 'foo' => 'hello', + 'bar' => 'world', + ], + [ + 'foo' => fn( $foo ) => $foo === 'hello', + 'bar' => fn( $bar ) => $bar === 'world', + ], + true, + true, + ], + 'non-empty array, closure shape, all key pass, non-strict ' => [ + [ + 'foo' => 'hello', + 'bar' => 'world', + ], + [ + 'foo' => fn( $foo ) => $foo === 'hello', + 'bar' => fn( $bar ) => $bar === 'world', + ], + false, + true, + ], + 'non-empty array, closure shape, some key pass, strict' => [ + [ + 'foo' => 'hello', + 'bar' => 89, + ], + [ + 'foo' => fn( $foo ) => $foo === 'hello', + 'bar' => fn( $bar ) => $bar === 'world', + ], + true, + false, + ], + 'non-empty array, closure shape, some key pass, non-strict' => [ + [ + 'foo' => 'hello', + 'bar' => 89, + ], + [ + 'foo' => fn( $foo ) => $foo === 'hello', + 'bar' => fn( $bar ) => $bar === 'world', + ], + false, + false, + ], + ]; + } + + /** + * @dataProvider has_shape_data_provider + */ + public function test_has_shape( $input, $shape, $strict, $expected ): void { + $this->assertEquals( $expected, Arr::has_shape( $input, $shape, $strict ) ); + } } diff --git a/tests/wpunit/Tribe/CacheTest.php b/tests/wpunit/Tribe/Cache_Test.php similarity index 68% rename from tests/wpunit/Tribe/CacheTest.php rename to tests/wpunit/Tribe/Cache_Test.php index b705c7cf2b..8b8dacd3be 100644 --- a/tests/wpunit/Tribe/CacheTest.php +++ b/tests/wpunit/Tribe/Cache_Test.php @@ -4,8 +4,9 @@ use Tribe\Tests\Traits\With_Uopz; use Tribe__Cache as Cache; +use Tribe__Cache_Listener as Triggers; -class CacheTest extends \Codeception\TestCase\WPTestCase { +class Cache_Test extends \Codeception\TestCase\WPTestCase { use With_Uopz; /** @@ -37,9 +38,12 @@ public function should_expire_cache_on_each_trigger() { $hook = 'faux_hook'; // Cache is now "listening" to this hook. - add_action( $hook, function () use ( $cache, $hook ) { - $cache->set_last_occurrence( $hook ); - } ); + add_action( + $hook, + function () use ( $cache, $hook ) { + $cache->set_last_occurrence( $hook ); + } + ); // Each unique values to test $values = [ 'a', 'b', 1, 2, time() ]; @@ -109,7 +113,7 @@ public function it_should_allow_setting_many_different_values_using_array_access foreach ( $key_values as $key => $value ) { // Attempt to add a longer cache key to trigger the md5() cache key logic. - $key .= __METHOD__ . '-' . $key; + $key .= __METHOD__ . '-' . $key; $expected[ substr( $key, 0, 6 ) . '... => ' . $value ] = [ $key, $value ]; } @@ -140,7 +144,7 @@ public function should_treat_some_values_as_not_set_data_provider(): array { // Null fails isset(), should be the same for our cache utility. 'null is not cached' => [ uniqid(), null ], // Because wp core cache utility sends false if no cache found. - 'false is not cached' => [ uniqid(), false ] + 'false is not cached' => [ uniqid(), false ], ]; } @@ -191,8 +195,22 @@ public function it_should_allow_removing_value_using_array_access_api() { * @test */ public function it_should_correctly_fabricate_keys() { - $components_1 = [ __FILE__, [ 23, 89 ], [ 'foo' => 'bar', 'bar' => 'baz' ] ]; - $components_2 = [ __FILE__, [ 23, 89 ], [ 'bar' => 'baz', 'foo' => 'bar' ] ]; + $components_1 = [ + __FILE__, + [ 23, 89 ], + [ + 'foo' => 'bar', + 'bar' => 'baz', + ], + ]; + $components_2 = [ + __FILE__, + [ 23, 89 ], + [ + 'bar' => 'baz', + 'foo' => 'bar', + ], + ]; $cache = $this->make_instance(); @@ -208,12 +226,19 @@ public function it_should_correctly_fabricate_keys() { * @test */ public function it_should_correctly_handle_long_keys() { - $components = [ __FILE__, [ 23, 89 ], [ 'foo' => 'bar', 'bar' => 'baz' ] ]; + $components = [ + __FILE__, + [ 23, 89 ], + [ + 'foo' => 'bar', + 'bar' => 'baz', + ], + ]; $cache = $this->make_instance(); - $long_prefix = 'some very long prefix that should trigger some kind of minification on the key creation or so I hope'; - $key = $cache->make_key( $components, $long_prefix ); + $long_prefix = 'some very long prefix that should trigger some kind of minification on the key creation or so I hope'; + $key = $cache->make_key( $components, $long_prefix ); $cache[ $key ] = 'bar'; $this->assertTrue( isset( $cache[ $key ] ) ); @@ -226,8 +251,24 @@ public function it_should_correctly_handle_long_keys() { * @test */ public function it_should_correctly_generate_key_for_numeric_array_components() { - $components_1 = [ __FILE__, [ 23, 89 ], [ 1 => 'bar', 23 => 'baz', 89 => 'bar' ] ]; - $components_2 = [ __FILE__, [ 23, 89 ], [ 89 => 'bar', 23 => 'baz', 1 => 'bar' ] ]; + $components_1 = [ + __FILE__, + [ 23, 89 ], + [ + 1 => 'bar', + 23 => 'baz', + 89 => 'bar', + ], + ]; + $components_2 = [ + __FILE__, + [ 23, 89 ], + [ + 89 => 'bar', + 23 => 'baz', + 1 => 'bar', + ], + ]; $cache = $this->make_instance(); @@ -247,10 +288,13 @@ public function it_should_not_try_to_delete_transients_right_away() { $passed = false; - add_filter( 'tribe_cache_delete_expired_transients_sql', static function( $sql ) use ( $passed ) { - $passed = true; - return $sql; - } ); + add_filter( + 'tribe_cache_delete_expired_transients_sql', + static function ( $sql ) use ( $passed ) { + $passed = true; + return $sql; + } + ); $cache->set_last_occurrence( 'foo_bar' ); @@ -275,12 +319,15 @@ public function should_not_clean_expired_transients_more_than_once_per_request() $cache_instance_two = new \Tribe__Cache(); $passed = 0; - add_filter( 'tribe_cache_delete_expired_transients_sql', static function () use ( & $passed ) { - $passed ++; + add_filter( + 'tribe_cache_delete_expired_transients_sql', + static function () use ( &$passed ) { + $passed++; - // Return a real query to make sure the "cancellation" will go through. - return 'SELECT 1'; - } ); + // Return a real query to make sure the "cancellation" will go through. + return 'SELECT 1'; + } + ); $provided_cache->flag_required_delete_transients( true ); @@ -315,12 +362,15 @@ public function should_not_clean_transients_more_than_once_per_request_when_trig $cache_instance_two = new \Tribe__Cache(); $passed = 0; - add_filter( 'tribe_cache_delete_expired_transients_sql', static function () use ( & $passed ) { - $passed ++; + add_filter( + 'tribe_cache_delete_expired_transients_sql', + static function () use ( &$passed ) { + $passed++; - // Return a real query to make sure the "cancellation" will go through. - return 'SELECT 1'; - } ); + // Return a real query to make sure the "cancellation" will go through. + return 'SELECT 1'; + } + ); $provided_cache->delete_expired_transients(); $provided_cache->delete_expired_transients(); @@ -343,10 +393,14 @@ public function should_not_clean_transients_more_than_once_per_request_when_trig public function should_not_cache_overly_large_strings_in_transients() { $max_allow_packet = 200; // Filter the feature detection (tested elsewhere). - add_filter( 'tribe_max_allowed_packet_size', static function () use ( $max_allow_packet ) { - return $max_allow_packet; - } ); + add_filter( + 'tribe_max_allowed_packet_size', + static function () use ( $max_allow_packet ) { + return $max_allow_packet; + } + ); // Simulate a case where external object caching is NOT in use. + // phpcs:ignore $GLOBALS['_wp_using_ext_object_cache'] = false; $small_size_value = str_repeat( '#', $max_allow_packet * .1 ); $medium_size_value = str_repeat( '#', $max_allow_packet * .5 ); @@ -374,9 +428,12 @@ public function should_not_cache_overly_large_strings_in_transients() { public function should_not_cache_overly_large_values_in_database() { $max_allow_packet = 200; // Filter the feature detection (tested elsewhere). - add_filter( 'tribe_max_allowed_packet_size', static function () use ( $max_allow_packet ) { - return $max_allow_packet; - } ); + add_filter( + 'tribe_max_allowed_packet_size', + static function () use ( $max_allow_packet ) { + return $max_allow_packet; + } + ); // Build an object whose serialized size is known before-hand. $build_object_to_size = function ( int $size ) { $template_size = 33; @@ -391,9 +448,11 @@ public function should_not_cache_overly_large_values_in_database() { $this->assertEquals( $size, strlen( $serialized ) ); + // phpcs:ignore return unserialize( $serialized ); }; // Simulate a case where external object caching is NOT in use. + // phpcs:ignore $GLOBALS['_wp_using_ext_object_cache'] = false; $medium_size_value = $build_object_to_size( $max_allow_packet * .5 ); $large_size_value = $build_object_to_size( $max_allow_packet * .9 ); @@ -418,10 +477,14 @@ public function should_not_cache_overly_large_values_in_database() { public function should_not_prevent_caching_of_large_values_when_using_external_cache() { $max_allow_packet = 200; // Filter the feature detection (tested elsewhere). - add_filter( 'tribe_max_allowed_packet_size', static function () use ( $max_allow_packet ) { - return $max_allow_packet; - } ); + add_filter( + 'tribe_max_allowed_packet_size', + static function () use ( $max_allow_packet ) { + return $max_allow_packet; + } + ); // Simulate a case where external object caching is NOT in use. + // phpcs:ignore $GLOBALS['_wp_using_ext_object_cache'] = true; $large_size_value = str_repeat( '#', $max_allow_packet * .9 ); $too_large_size_value = str_repeat( '#', $max_allow_packet * 1.1 ); @@ -430,7 +493,7 @@ public function should_not_prevent_caching_of_large_values_when_using_external_c $cache = tribe( 'cache' ); $this->assertTrue( $cache->set_transient( 'test', $large_size_value ) ); - $this->assertFalse( $cache->data_size_over_packet_size( $large_size_value) ); + $this->assertFalse( $cache->data_size_over_packet_size( $large_size_value ) ); $this->assertTrue( $cache->set_transient( 'test', $too_large_size_value ) ); $this->assertFalse( $cache->data_size_over_packet_size( $too_large_size_value ) ); } @@ -442,27 +505,42 @@ public function should_not_prevent_caching_of_large_values_when_using_external_c */ public function should_allow_storing_too_large_transients_in_chunks() { // Set a size for the MySQL max_allowed_packet_size in bytes. - add_filter( 'tribe_max_allowed_packet_size', static function () { - return 100; - } ); + add_filter( + 'tribe_max_allowed_packet_size', + static function () { + return 100; + } + ); // Create a value that, in string format, is 4+ times the max allowed packet size. $value = (object) [ 'value' => str_repeat( 'test', 100 ) ]; $set_transient_calls = []; $this->set_fn_return( 'wp_using_ext_object_cache', false ); - $this->set_fn_return( 'set_transient', static function ( $name, $value ) use ( &$set_transient_calls ) { - $set_transient_calls[$name] = $value; + $this->set_fn_return( + 'set_transient', + static function ( $name, $value ) use ( &$set_transient_calls ) { + $set_transient_calls[ $name ] = $value; - return true; - }, true ); + return true; + }, + true + ); $cache = $this->make_instance(); $set = $cache->set_chunkable_transient( '__test__', $value, DAY_IN_SECONDS, [ 'save_post' ] ); $this->assertTrue( $set ); - $this->assertCount( 5, array_filter( $set_transient_calls, static function ( $key ) { - return strpos( $key, '__test__' ) === 0; - }, ARRAY_FILTER_USE_KEY ) ); + $this->assertCount( + 5, + array_filter( + $set_transient_calls, + static function ( $key ) { + return strpos( $key, '__test__' ) === 0; + }, + ARRAY_FILTER_USE_KEY + ) + ); + // phpcs:ignore $this->assertSame( serialize( $value ), implode( '', $set_transient_calls ) ); } @@ -473,27 +551,40 @@ public function should_allow_storing_too_large_transients_in_chunks() { */ public function should_redirect_chunkable_not_too_large_transients_to_normal_transients() { // Set a size for the MySQL max_allowed_packet_size in bytes. - add_filter( 'tribe_max_allowed_packet_size', static function () { - return 100; - } ); + add_filter( + 'tribe_max_allowed_packet_size', + static function () { + return 100; + } + ); // Create a value that, in string format, is below the max allowed packet size. $value = (object) [ 'value' => 'test' ]; $set_transient_calls = []; $this->set_fn_return( 'wp_using_ext_object_cache', false ); - $this->set_fn_return( 'set_transient', static function ( $name ) use ( &$set_transient_calls ) { - $set_transient_calls[] = $name; + $this->set_fn_return( + 'set_transient', + static function ( $name ) use ( &$set_transient_calls ) { + $set_transient_calls[] = $name; - return true; - }, true ); + return true; + }, + true + ); $cache = $this->make_instance(); $set = $cache->set_chunkable_transient( '__test__', $value, DAY_IN_SECONDS, [ 'save_post' ] ); $this->assertTrue( $set ); - $this->assertCount( 1, array_filter( $set_transient_calls, static function ( $key ) { - return strpos( $key, '__test__' ) === 0; - } ) ); + $this->assertCount( + 1, + array_filter( + $set_transient_calls, + static function ( $key ) { + return strpos( $key, '__test__' ) === 0; + } + ) + ); } /** @@ -503,36 +594,59 @@ public function should_redirect_chunkable_not_too_large_transients_to_normal_tra */ public function should_delete_inserted_transients_when_one_chunk_insertion_fails() { // Set a size for the MySQL max_allowed_packet_size in bytes. - add_filter( 'tribe_max_allowed_packet_size', static function () { - return 100; - } ); + add_filter( + 'tribe_max_allowed_packet_size', + static function () { + return 100; + } + ); // Create a value that, in string format, is below the max allowed packet size. - $value = (object) [ 'value' => str_repeat( 'test', 100 ) ]; - $set_transient_calls = []; + $value = (object) [ 'value' => str_repeat( 'test', 100 ) ]; + $set_transient_calls = []; $delete_transient_calls = []; $this->set_fn_return( 'wp_using_ext_object_cache', false ); - $this->set_fn_return( 'set_transient', static function ( $name ) use ( &$set_transient_calls ) { - $set_transient_calls[] = $name; - - // On the insertion of the 3rd one return `false`. - return count( $set_transient_calls ) <= 2; - }, true ); - $this->set_fn_return( 'delete_transient', static function ( $name ) use ( &$delete_transient_calls ) { - $delete_transient_calls[] = $name; - return true; - }, true ); + $this->set_fn_return( + 'set_transient', + static function ( $name ) use ( &$set_transient_calls ) { + $set_transient_calls[] = $name; + + // On the insertion of the 3rd one return `false`. + return count( $set_transient_calls ) <= 2; + }, + true + ); + $this->set_fn_return( + 'delete_transient', + static function ( $name ) use ( &$delete_transient_calls ) { + $delete_transient_calls[] = $name; + return true; + }, + true + ); $cache = $this->make_instance(); $set = $cache->set_chunkable_transient( '__test__', $value, DAY_IN_SECONDS, [ 'save_post' ] ); $this->assertFalse( $set ); - $this->assertCount( 3, array_filter( $set_transient_calls, static function ( $key ) { - return strpos( $key, '__test__' ) === 0; - } ) ); - $this->assertCount( 2, array_filter( $delete_transient_calls, static function ( $key ) { - return strpos( $key, '__test__' ) === 0; - } ) ); + $this->assertCount( + 3, + array_filter( + $set_transient_calls, + static function ( $key ) { + return strpos( $key, '__test__' ) === 0; + } + ) + ); + $this->assertCount( + 2, + array_filter( + $delete_transient_calls, + static function ( $key ) { + return strpos( $key, '__test__' ) === 0; + } + ) + ); } /** @@ -542,19 +656,26 @@ public function should_delete_inserted_transients_when_one_chunk_insertion_fails */ public function should_return_false_when_getting_chunkable_in_incoherent_state() { // Set a size for the MySQL max_allowed_packet_size in bytes. - add_filter( 'tribe_max_allowed_packet_size', static function () { - return 100; - } ); + add_filter( + 'tribe_max_allowed_packet_size', + static function () { + return 100; + } + ); // Create a value that, in string format, is below the max allowed packet size. - $value = (object) [ 'value' => str_repeat( 'test', 100 ) ]; - $set_transient_calls = []; + $value = (object) [ 'value' => str_repeat( 'test', 100 ) ]; + $set_transient_calls = []; $this->set_fn_return( 'wp_using_ext_object_cache', false ); - $this->set_fn_return( 'set_transient', static function ( $name, $value ) use ( &$set_transient_calls ) { - $set_transient_calls[] = $name; + $this->set_fn_return( + 'set_transient', + static function ( $name, $value ) use ( &$set_transient_calls ) { + $set_transient_calls[] = $name; - // Log the call. - return set_transient( $name, $value ); - }, true ); + // Log the call. + return set_transient( $name, $value ); + }, + true + ); $cache = $this->make_instance(); @@ -572,27 +693,77 @@ public function should_return_false_when_getting_chunkable_in_incoherent_state() * * @test */ - public function should_return_the_chunks_from_the_cache_correctly_when_the_cache_is_stored() { - // Set a size for the MySQL max_allowed_packet_size in bytes. - add_filter( 'tribe_max_allowed_packet_size', static function () { - return 100; - } ); - // Create a value that, in string format, is higher the max allowed packet size. - $value = [ 'value' => str_repeat( 'test', 250 ) ]; - $set_transient_calls = []; - $this->set_fn_return( 'wp_using_ext_object_cache', false ); - $this->set_fn_return( 'set_transient', static function ( $name, $value ) use ( &$set_transient_calls ) { - $set_transient_calls[] = $name; - - // Log the call. - return set_transient( $name, $value ); - }, true ); - - $cache = $this->make_instance(); - - $this->assertTrue( $cache->set_chunkable_transient( '__test___retrival__from__cache', $value, DAY_IN_SECONDS, [ 'save_post' ] ) ); - - // The value from the cache should be the same that was stored. - $this->assertSame( $value, $cache->get_chunkable_transient( '__test___retrival__from__cache', [ 'save_post' ] ) ); - } + public function should_return_the_chunks_from_the_cache_correctly_when_the_cache_is_stored() { + // Set a size for the MySQL max_allowed_packet_size in bytes. + add_filter( + 'tribe_max_allowed_packet_size', + static function () { + return 100; + } + ); + // Create a value that, in string format, is higher the max allowed packet size. + $value = [ 'value' => str_repeat( 'test', 250 ) ]; + $set_transient_calls = []; + $this->set_fn_return( 'wp_using_ext_object_cache', false ); + $this->set_fn_return( + 'set_transient', + static function ( $name, $value ) use ( &$set_transient_calls ) { + $set_transient_calls[] = $name; + + // Log the call. + return set_transient( $name, $value ); + }, + true + ); + + $cache = $this->make_instance(); + + $this->assertTrue( $cache->set_chunkable_transient( '__test___retrival__from__cache', $value, DAY_IN_SECONDS, [ 'save_post' ] ) ); + + // The value from the cache should be the same that was stored. + $this->assertSame( $value, $cache->get_chunkable_transient( '__test___retrival__from__cache', [ 'save_post' ] ) ); + } + + /** + * It should allow knowing whether a value is in cache or not + * + * @test + */ + public function should_allow_knowing_whether_a_value_is_in_cache_or_not(): void { + $cache = tribe_cache(); + + $this->assertFalse( $cache->has( 'foo-bar' ) ); + $this->assertFalse( $cache->has( 'foo-bar-save-post', Triggers::TRIGGER_SAVE_POST ) ); + $this->assertFalse( $cache->has( 'foo-bar-updated-option', Triggers::TRIGGER_UPDATED_OPTION ) ); + $this->assertFalse( $cache->has( 'foo-bar-generate-rewrite-rules', Triggers::TRIGGER_GENERATE_REWRITE_RULES ) ); + + $this->assertFalse( $cache->get( 'foo-bar', '', false, 0, [], $found ) ); + $this->assertFalse( $found ); + $this->assertFalse( $cache->get( 'foo-bar-save-post', Triggers::TRIGGER_SAVE_POST, false, 0, [], $found ) ); + $this->assertFalse( $found ); + $this->assertFalse( $cache->get( 'foo-bar-updated-option', Triggers::TRIGGER_UPDATED_OPTION, false, 0, [], $found ) ); + $this->assertFalse( $found ); + $this->assertFalse( $cache->get( 'foo-bar-generate-rewrite-rules', Triggers::TRIGGER_GENERATE_REWRITE_RULES, false, 0, [], $found ) ); + $this->assertFalse( $found ); + + $cache->set( 'foo-bar', 'bar' ); + $this->assertTrue( $cache->has( 'foo-bar' ) ); + $this->assertEquals( 'bar', $cache->get( 'foo-bar', '', false, 0, [], $found ) ); + $this->assertTrue( $found ); + + $cache->set( 'foo-bar-save-post', 'bar', 0, Triggers::TRIGGER_SAVE_POST ); + $this->assertTrue( $cache->has( 'foo-bar-save-post', Triggers::TRIGGER_SAVE_POST ) ); + $this->assertEquals( 'bar', $cache->get( 'foo-bar-save-post', Triggers::TRIGGER_SAVE_POST, false, 0, [], $found ) ); + $this->assertTrue( $found ); + + $cache->set( 'foo-bar-updated-option', 'bar', 0, Triggers::TRIGGER_UPDATED_OPTION ); + $this->assertTrue( $cache->has( 'foo-bar-updated-option', Triggers::TRIGGER_UPDATED_OPTION ) ); + $this->assertEquals( 'bar', $cache->get( 'foo-bar-updated-option', Triggers::TRIGGER_UPDATED_OPTION, false, 0, [], $found ) ); + $this->assertTrue( $found ); + + $cache->set( 'foo-bar-generate-rewrite-rules', 'bar', 0, Triggers::TRIGGER_GENERATE_REWRITE_RULES ); + $this->assertTrue( $cache->has( 'foo-bar-generate-rewrite-rules', Triggers::TRIGGER_GENERATE_REWRITE_RULES ) ); + $this->assertEquals( 'bar', $cache->get( 'foo-bar-generate-rewrite-rules', Triggers::TRIGGER_GENERATE_REWRITE_RULES, false, 0, [], $found ) ); + $this->assertTrue( $found ); + } }