File_Cache::compile_expiry()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 2
rs 10
cc 2
nc 2
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
6
/**
7
 * The WordPress transient driver for the PinkCrab Peristant Cache interface.
8
 *
9
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
10
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
11
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
12
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
13
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
14
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
15
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
16
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
17
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
18
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
19
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
20
 *
21
 * @author Glynn Quelch <[email protected]>
22
 * @license http://www.opensource.org/licenses/mit-license.html  MIT License
23
 * @package PinkCrab\WP_PSR16_Cache
24
 */
25
26
namespace PinkCrab\WP_PSR16_Cache;
27
28
use stdClass;
29
use DateInterval;
30
use WP_Filesystem_Direct;
31
use InvalidArgumentException;
32
use Psr\SimpleCache\CacheInterface;
33
use PinkCrab\WP_PSR16_Cache\CacheInterface_Trait;
34
35
class File_Cache implements CacheInterface {
36
37
	/**
38
	 * @uses CacheInterface_Trait::is_valid_key_value()
39
	 * @uses CacheInterface_Trait::ttl_to_seconds()
40
	 * @uses CacheInterface_Trait::all_true()
41
	 */
42
	use CacheInterface_Trait;
43
44
	/**
45
	 * WP_File_System instance.
46
	 *
47
	 * @var WP_Filesystem_Direct
48
	 */
49
	protected $wp_filesystem;
50
51
	/**
52
	 * Path to cache location.
53
	 *
54
	 * @var string
55
	 */
56
	protected $filepath;
57
58
	/**
59
	 * Extension for the cache files.
60
	 *
61
	 * @var string
62
	 */
63
	protected $extension;
64
65
	/**
66
	 * Creates an instance of the FileCache, populated with WP_Filesystem_Direct
67
	 *
68
	 * @param string $filepath
69
	 * @param string $extension
70
	 */
71
	public function __construct( string $filepath, string $extension = '.do' ) {
72
		$this->filepath  = rtrim( $filepath, '\\/' );
73
		$this->extension = $extension;
74
75
		$this->set_wp_file_system();
76
		$this->maybe_create_cache_dir();
77
	}
78
79
	/**
80
	 * Creates an instance of the Direct WP Filesytem.
81
	 *
82
	 * @return void
83
	 */
84
	protected function set_wp_file_system(): void {
85
		require_once ABSPATH . 'wp-admin/includes/file.php';
86
		WP_Filesystem();
87
		global $wp_filesystem;
88
		$this->wp_filesystem = $wp_filesystem;
89
	}
90
91
	/**
92
	 * Creates cache directory if it doesnt exist.
93
	 *
94
	 * @return void
95
	 */
96
	protected function maybe_create_cache_dir(): void {
97
		if ( ! $this->wp_filesystem->exists( $this->filepath ) ) {
98
			$this->wp_filesystem->mkdir( $this->filepath );
99
		}
100
	}
101
102
	/**
103
	 * Checks if key is set.
104
	 *
105
	 * @param string $key
106
	 * @return bool
107
	 * @throws InvalidArgumentException
108
	 */
109
	public function has( $key ) {
110
		if ( ! $this->is_valid_key_value( $key ) ) {
111
			return false;
112
		}
113
		return ! is_null( $this->get( $key ) );
114
	}
115
116
	/**
117
	 * Sets a key.
118
	 * Used to conform with Psr\Simple-Cache
119
	 *
120
	 * @param string                 $key   The key of the item to store.
121
	 * @param mixed                  $value The value of the item to store, must be serializable.
122
	 * @param null|int|\DateInterval $ttl
123
	 * @return bool
124
	 * @throws InvalidArgumentException
125
	 */
126
	public function set( $key, $value, $ttl = null ) {
127
		if ( ! $this->is_valid_key_value( $key ) ) {
128
			return false;
129
		}
130
131
		return $this->wp_filesystem->put_contents(
132
			$this->compile_file_path( $key ),
133
			serialize( $this->compile_cache_item( $key, $value, $ttl ) ) // phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
134
		);
135
	}
136
137
	/**
138
	 * Attempts to get from cache, return defualt if nothing returned.
139
	 *
140
	 * @param string $key
141
	 * @param mixed $default
142
	 * @return mixed
143
	 * @throws InvalidArgumentException
144
	 */
145
	public function get( $key, $default = null ) {
146
		if ( ! $this->is_valid_key_value( $key ) ) {
147
			return $default;
148
		}
149
		$contents = $this->get_contents( $key );
150
		return $contents ? $contents->data : $default;
151
	}
152
153
	/**
154
	 * Clears a defined cached instance.
155
	 *
156
	 * @param string $key
157
	 * @return bool
158
	 * @throws InvalidArgumentException
159
	 */
160
	public function delete( $key ) {
161
		if ( ! $this->is_valid_key_value( $key ) ) {
162
			return false;
163
		}
164
		return $this->wp_filesystem->delete(
165
			$this->compile_file_path( $key )
166
		);
167
	}
168
169
	/**
170
	 * Clears all cache items from the directory.
171
	 *
172
	 * @return bool
173
	 */
174
	public function clear() {
175
		return $this->wp_filesystem->delete( $this->filepath, true );
176
	}
177
178
	/**
179
	 * Gets multiple values, will return default in lue of value
180
	 *
181
	 * @param array<string, mixed> $keys
182
	 * @param string|float|int|array<mixed>|object|resource|bool $default
183
	 * @return array<string, mixed>
184
	 */
185
	public function getMultiple( $keys, $default = null ) {
186
		return array_reduce(
187
			$keys,
188
			function( $carry, $key ) use ( $default ) {
189
				$carry[ $key ] = $this->get( $key, $default );
190
				return $carry;
191
			},
192
			array()
193
		);
194
	}
195
196
	/**
197
	 * Sets multiple values in a key=>value array.
198
	 *
199
	 * @param array<string, mixed> $values
200
	 * @param int|null $ttl
201
	 * @return bool
202
	 */
203
	public function setMultiple( $values, $ttl = null ) {
204
		return $this->all_true(
205
			array_reduce(
206
				array_keys( $values ),
207
				function( $carry, $key ) use ( $values, $ttl ) {
208
					$carry[ $key ] = $this->set( $key, $values[ $key ], $ttl );
209
					return $carry;
210
				},
211
				array()
212
			)
213
		);
214
	}
215
216
	/**
217
	 * Deletes multiple keys
218
	 *
219
	 * @param array<int, string> $keys
220
	 * @return bool
221
	 */
222
	public function deleteMultiple( $keys ) {
223
		return $this->all_true(
224
			array_map(
225
				function( $key ) {
226
					return $this->delete( $key );
227
				},
228
				$keys
229
			)
230
		);
231
	}
232
233
234
	/**
235
	 * Parses the file name based on the settings and key
236
	 *
237
	 * @param string $filename;
238
	 * @return string
239
	 */
240
	protected function compile_file_path( string $filename ): string {
241
		return \wp_normalize_path(
242
			sprintf(
243
				'%s/%s%s',
244
				$this->filepath,
245
				$filename,
246
				$this->extension
247
			)
248
		);
249
	}
250
251
	/**
252
	 * Compiles the cache item object
253
	 *
254
	 * @param string $key
255
	 * @param mixed $data
256
	 * @param null|int|\DateInterval $ttl
257
	 * @return Cache_Item
258
	 */
259
	protected function compile_cache_item( string $key, $data, $ttl ): Cache_Item {
260
		return new Cache_Item(
261
			$key,
262
			$data,
263
			$this->compile_expiry( $this->ttl_to_seconds( $ttl ) )
264
		);
265
	}
266
267
	/**
268
	 * Composes the expiry time with time added to current timestamp.
269
	 *
270
	 * @param int $expiry
271
	 * @return int
272
	 */
273
	protected function compile_expiry( int $expiry ): int {
274
		return $expiry !== 0 ? $expiry + time() : 0;
275
	}
276
277
278
	/**
279
	 * Validates the contents of a file read.
280
	 * Checks key matches, has data and hasnt expired.
281
	 *
282
	 * @param string $key
283
	 * @param Cache_Item $data
284
	 * @return bool
285
	 */
286
	protected function validate_contents( string $key, Cache_Item $data ): bool {
287
		switch ( true ) {
288
			// Key passes doesnt match cache.
289
			case $data->key !== $key:
290
				return false;
291
292
			// Expiry isnt numeric
293
			case ! is_numeric( $data->expiry ):
294
				return false;
295
296
			// If expiry is 0 (Never expires), return true.
297
			case intval( $data->expiry ) === 0:
298
				return true;
299
300
			// If a timestamp, but expired.
301
			case intval( $data->expiry ) < time():
302
				return false;
303
304
			default:
305
				return true;
306
		}
307
	}
308
309
	/**
310
	 * Attempts to get the contents from a key.
311
	 *
312
	 * @param string $key
313
	 * @return Cache_Item|null If we have valid data (not expired), the data else null
314
	 */
315
	protected function get_contents( string $key ): ?Cache_Item {
316
317
		$file_contents = $this->wp_filesystem->get_contents( $this->compile_file_path( $key ) ) ?: '';
318
319
		$file_contents = \maybe_unserialize( $file_contents );
320
321
		return is_a( $file_contents, Cache_Item::class ) && $this->validate_contents( $key, $file_contents )
322
			? $file_contents
323
			: null;
324
	}
325
326
}
327