Completed
Push — master ( 9ff8c9...39b277 )
by David
02:37
created

Ttl_Cache::get()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 2
nop 1
dl 0
loc 15
rs 9.7666
c 0
b 0
f 0
1
<?php
2
3
namespace Wordlift\Cache;
4
5
use Wordlift_Log_Service;
6
7
/**
8
 * Define an a time lived cache.
9
 *
10
 * Cache has a ttl set by default to 900 seconds. Cached responses are stored in the temp
11
 * folder returned by WordPress' {@link get_temp_dir} function.
12
 *
13
 * Currently the class doesn't cleanup stale cache files.
14
 *
15
 * @since 3.21.2
16
 */
17
// @@todo: add a hook to clear the cached files now and then.
18
class Ttl_Cache {
19
20
	/**
21
	 * The cache name.
22
	 *
23
	 * @var string $name The cache name.
24
	 * @access private
25
	 * @since 3.21.2
26
	 */
27
	private $name;
28
29
	/**
30
	 * The TTL of cached responses in seconds.
31
	 *
32
	 * @var int $ttl The TTL in seconds.
33
	 * @access private
34
	 * @since 3.21.2
35
	 */
36
	private $ttl;
37
38
	/**
39
	 * The cache dir where the cached data is written.
40
	 *
41
	 * @since 3.21.2
42
	 * @access private
43
	 * @var string $cache_dir The cache dir where the cached responses are written.
44
	 */
45
	private $cache_dir;
46
47
	/**
48
	 * A {@link Wordlift_Log_Service} instance.
49
	 *
50
	 * @var Wordlift_Log_Service $log A {@link Wordlift_Log_Service} instance.
51
	 * @access private
52
	 * @since 3.21.2
53
	 */
54
	private $log;
55
56
	/**
57
	 * @var array
58
	 */
59
	private static $caches = array();
60
61
	/**
62
	 * Create a {@link Ttl_Cache} with the specified TTL, default 900 secs.
63
	 *
64
	 * @param string $name The cache name.
65
	 * @param int    $ttl The cache TTL, default 900 secs.
66
	 *
67
	 * @since 3.21.2
68
	 */
69
	public function __construct( $name, $ttl = 900 ) {
70
71
		$this->log = Wordlift_Log_Service::get_logger( get_class() );
72
73
		$this->name = $name;
74
		$this->ttl  = $ttl;
75
76
		// Get the temp dir and add the directory separator if missing.
77
		$temp_dir = get_temp_dir();
78 View Code Duplication
		if ( DIRECTORY_SEPARATOR !== substr( $temp_dir, - strlen( DIRECTORY_SEPARATOR ) ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
79
			$temp_dir .= DIRECTORY_SEPARATOR;
80
		}
81
		$this->cache_dir = self::get_cache_folder() . DIRECTORY_SEPARATOR . md5( $name );
82
83
		$this->log->trace( "Creating the cache folder {$this->cache_dir}..." );
84
		wp_mkdir_p( $this->cache_dir );
85
86
		self::$caches[ $name ] = $this;
87
88
	}
89
90
	/**
91
	 * Get the root cache folder.
92
	 *
93
	 * This is useful to introduce a cache cleaning procedure which will scan and delete older stale cache files.
94
	 *
95
	 * @return string The root cache folder.
96
	 * @since 3.22.5
97
	 */
98
	public static function get_cache_folder() {
99
100
		// Get the temp dir and add the directory separator if missing.
101
		$temp_dir = get_temp_dir();
102 View Code Duplication
		if ( DIRECTORY_SEPARATOR !== substr( $temp_dir, - strlen( DIRECTORY_SEPARATOR ) ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
103
			$temp_dir .= DIRECTORY_SEPARATOR;
104
		}
105
106
		return $temp_dir . 'wl.cache';
107
	}
108
109
	/**
110
	 * Get the cached data for the specified key.
111
	 *
112
	 * @param mixed $key A serializable key.
113
	 *
114
	 * @return mixed|null
115
	 * @since 3.21.2
116
	 */
117
	public function get( $key ) {
118
119
		$filename = $this->get_filename( $key );
120
121
		// If the cache file exists and it's not too old, then return it.
122
		if ( file_exists( $filename ) && $this->ttl >= time() - filemtime( $filename ) ) {
123
			$this->log->trace( "Cache HIT.\n" );
124
125
			return json_decode( file_get_contents( $filename ), true );
126
		}
127
128
		$this->log->trace( "Cache MISS, filename $filename.\n" );
129
130
		return null;
131
	}
132
133
	public function put( $key, $data ) {
134
135
		$filename = $this->get_filename( $key );
136
137
		// Cache.
138
		@unlink( $filename );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
139
		@file_put_contents( $filename, wp_json_encode( $data ) );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
140
141
	}
142
143
	public function flush() {
144
145
		$files = glob( $this->cache_dir . DIRECTORY_SEPARATOR . '*' );
146
		foreach ( $files as $file ) { // iterate files
147
			if ( is_file( $file ) ) {
148
				@unlink( $file );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
149
			}
150
		}
151
152
	}
153
154
	public static function flush_all() {
155
156
		/** @var Ttl_Cache $cache */
157
		foreach ( self::$caches as $cache ) {
158
			$cache->flush();
159
		}
160
161
	}
162
163
	/**
164
	 * Get the full path for the given `$hash`. The file is not checked for its existence.
165
	 *
166
	 * @param string $hash A file hash.
167
	 *
168
	 * @return string The full path to the file.
169
	 * @since 3.21.2
170
	 */
171
	private function get_path( $hash ) {
172
173
		return $this->cache_dir . DIRECTORY_SEPARATOR . $hash;
174
	}
175
176
	private function get_filename( $key ) {
177
178
		// Create a hash and a path to the cache file.
179
		$hash     = md5( json_encode( $key ) );
180
		$filename = $this->get_path( $hash );
181
182
		return $filename;
183
	}
184
185
}
186