diff --git a/src/wp-includes/class-wp-object-cache.php b/src/wp-includes/class-wp-object-cache.php index cda63e66d49ef..7951d4a79a251 100644 --- a/src/wp-includes/class-wp-object-cache.php +++ b/src/wp-includes/class-wp-object-cache.php @@ -313,6 +313,8 @@ public function set( $key, $data, $group = 'default', $expire = 0 ) { if ( is_object( $data ) ) { $data = clone $data; + } elseif ( is_array( $data ) ) { + $data = $this->deep_copy( $data ); } $this->cache[ $group ][ $key ] = $data; @@ -377,6 +379,8 @@ public function get( $key, $group = 'default', $force = false, &$found = null ) $this->cache_hits += 1; if ( is_object( $this->cache[ $group ][ $key ] ) ) { return clone $this->cache[ $group ][ $key ]; + } elseif ( is_array( $this->cache[ $group ][ $key ] ) ) { + return $this->deep_copy( $this->cache[ $group ][ $key ] ); } else { return $this->cache[ $group ][ $key ]; } @@ -641,4 +645,32 @@ public function stats() { } echo ''; } + + /** + * Performs a deep copy of an item to ensure that arrays of objects + * are properly duplicated + * + * @param mixed $data The data to be duplicated + * @return mixed copy of the data + */ + public function deep_copy( $data ) { + if ( is_object( $data ) ) { + return clone $data; + } elseif ( is_array( $data ) ) { + $new = array(); + foreach ( $data as $key => $value ) { + if ( is_object( $value ) ) { + $new[ $key ] = clone $value; + } elseif ( is_array( $value ) ) { + $new[ $key ] = $this->deep_copy( $value ); + } else { + $new[ $key ] = $value; + } + } + + return $new; + } else { + return $data; + } + } } diff --git a/tests/phpunit/tests/cache.php b/tests/phpunit/tests/cache.php index 1f345652b1fd9..a6c56f7deb682 100644 --- a/tests/phpunit/tests/cache.php +++ b/tests/phpunit/tests/cache.php @@ -250,6 +250,79 @@ public function test_object_refs() { $this->assertSame( 'bravo', $object_a->foo ); } + /** + * + * @ticket 30430 + */ + public function test_array_with_objects_deep_copy() { + if ( wp_using_ext_object_cache() ) { + $this->markTestSkipped( 'This test requires that an external object cache is not in use.' ); + } + + $key = __FUNCTION__; + + $shallow_object = new stdClass(); + $shallow_object->foo = 'alpha'; + + $deep_object = new stdClass(); + $deep_object->value = 'deep_original'; + + $array_with_objects = array( + 'shallow_obj' => $shallow_object, + 'string' => 'gamma', + 'nested' => array( + 'level2' => array( + 'deep_obj' => $deep_object, + 'primitive' => 'unchanged', + ), + ), + ); + + $this->cache->set( $key, $array_with_objects ); + + $shallow_object->foo = 'modified_alpha'; + $deep_object->value = 'deep_modified'; + + $cached_array = $this->cache->get( $key ); + + $this->assertSame( 'alpha', $cached_array['shallow_obj']->foo, 'Shallow cached object should not be affected by changes to original object' ); + $this->assertSame( 'deep_original', $cached_array['nested']['level2']['deep_obj']->value, 'Deep cached object should not be affected by changes to original object' ); + $this->assertSame( 'gamma', $cached_array['string'], 'String values should remain unchanged' ); + $this->assertSame( 'unchanged', $cached_array['nested']['level2']['primitive'], 'Primitive values should remain unchanged' ); + + $cached_array['shallow_obj']->foo = 'modified_from_cache'; + $cached_array['nested']['level2']['deep_obj']->value = 'modified_from_cache'; + + // Retreiving again should not affect the cached data + $cached_array_again = $this->cache->get( $key ); + $this->assertSame( 'alpha', $cached_array_again['shallow_obj']->foo, 'Cached data should not be affected by modifications to retrieved objects' ); + $this->assertSame( 'deep_original', $cached_array_again['nested']['level2']['deep_obj']->value, 'Deep cached data should not be affected by modifications to retrieved objects' ); + } + + /** + * + * @ticket 30430 + */ + public function test_primitive_arrays_unchanged() { + $key = __FUNCTION__; + + $primitive_array = array( + 'string' => 'test', + 'number' => 123, + 'boolean' => false, + 'nested' => array( + 'nested_string' => 'nested_test', + 'nested_number' => 456, + ), + ); + + $this->cache->set( $key, $primitive_array ); + + $cached_array = $this->cache->get( $key ); + + $this->assertSame( $primitive_array, $cached_array, 'Primitive arrays should be cached correctly' ); + } + public function test_incr() { $key = __FUNCTION__;