ar_cacheStore::getIfFresh()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
nc 2
nop 2
dl 0
loc 8
rs 10
c 0
b 0
f 0
ccs 0
cts 8
cp 0
crap 12
1
<?php
2
3
	/* usage
4
5
		simple:
6
7
		if ( !$image = ar('cache')->getIfFresh( $name ) ) {
8
			$image = expensiveOperation();
9
			ar('cache')->set( $naam, $image );
10
		}
11
		echo $image;
12
13
		with locking:
14
15
		if ( !$image = ar('cache')->getIfFresh( $naam ) ) {
16
			if ( ar('cache')->lock( $naam ) ) {
17
				$image = expensiveOperation();
18
				ar('cache')->set( $naam, $image, '2 hours' );
19
			} else if ( ar('cache')->wait( $naam ) ) { // lock failed, another process is generating the cache
20
				// continues here when the lock to be lifted
21
				$image = ar('cache')->get($naam);
22
			} else {
23
				// couldn't lock the file in a reasonable time, you could generate an error here
24
				// or just go with a stale image, or simply do the calculation:
25
				$image = expensiveOperation();
26
			}
27
		}
28
		echo $image;
29
30
	*/
31
32
	ar_pinp::allow('ar_cache');
33
	ar_pinp::allow('ar_cacheStore');
34
	ar_pinp::allow('ar_cacheProxy');
35
36
	class ar_cache extends arBase {
37
38
		public static $cacheStore = null;
39
40
		public static function config( $options ) {
41
			if ( $options['cacheStore'] ) {
42
				self::$cacheStore = $options['cacheStore'];
43
			}
44
		}
45
46
		public static function create( $prefix = null, $timeout = 7200 ) {
47
			// this method is used by pinp scripts to create a specific cache
48
			// so it must be more restrictive than the constructor of the cacheStore
49
			// which must be able to clear any and all caches
50
			if ( !$prefix ) { // make sure you have a default prefix, so you won't clear other prefixes unintended
51
				$prefix = 'default';
52
			}
53
			$prefix = 'pinp/'.$prefix; // make sure the pinp scripts have their own top level
54
			$prefix = $prefix . ar::context()->getPath(); // make sure the cache store is limited to the current path in the context stack
55
			try {
56
				return new ar_cacheStore( $prefix, $timeout );
57
			} catch( Exception $e ) {
58
				return ar_error::raiseError( $e->getMessage(), $e->getCode() );
59
			}
60
		}
61
62
		public static function get( $name ) {
63
			if ( !self::$cacheStore ) {
64
				self::$cacheStore = self::create();
65
			}
66
			return self::$cacheStore->get( $name );
67
		}
68
69
		public static function getIfFresh( $name, $freshness=0 ) {
70
			if ( !self::$cacheStore ) {
71
				self::$cacheStore = self::create();
72
			}
73
			return self::$cacheStore->getIfFresh( $name, $freshness );
0 ignored issues
show
Bug introduced by
The method getIfFresh does only exist in ar_cacheStore, but not in ar_error.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
74
		}
75
76
		public static function lock( $name ) {
77
			if ( !self::$cacheStore ) {
78
				self::$cacheStore = self::create();
79
			}
80
			return self::$cacheStore->lock( $name );
0 ignored issues
show
Bug introduced by
The method lock does only exist in ar_cacheStore, but not in ar_error.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
81
		}
82
83
		public static function wait( $name ) {
84
			if ( !self::$cacheStore ) {
85
				self::$cacheStore = self::create();
86
			}
87
			return self::$cacheStore->wait( $name );
0 ignored issues
show
Bug introduced by
The method wait does only exist in ar_cacheStore, but not in ar_error.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
88
		}
89
90
		public static function set( $name, $value, $timeout = 7200 ) {
91
			if ( !self::$cacheStore ) {
92
				self::$cacheStore = self::create();
93
			}
94
			return self::$cacheStore->set( $name, $value, $timeout );
0 ignored issues
show
Bug introduced by
The method set does only exist in ar_cacheStore, but not in ar_error.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
95
		}
96
97
		public static function info( $name ) {
98
			if ( !self::$cacheStore ) {
99
				self::$cacheStore = self::create();
100
			}
101
			return self::$cacheStore->info( $name );
0 ignored issues
show
Bug introduced by
The method info does only exist in ar_cacheStore, but not in ar_error.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
102
		}
103
104
		public static function clear( $name = null ) {
105
			if ( !self::$cacheStore ) {
106
				self::$cacheStore = self::create();
107
			}
108
			return self::$cacheStore->clear( $name );
0 ignored issues
show
Bug introduced by
The method clear does only exist in ar_cacheStore, but not in ar_error.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
109
		}
110
111
		public static function purge( $name = null ) {
112
			if ( !self::$cacheStore ) {
113
				self::$cacheStore = self::create();
114
			}
115
			return self::$cacheStore->purge( $name );
0 ignored issues
show
Bug introduced by
The method purge does only exist in ar_cacheStore, but not in ar_error.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
116
		}
117
118
		public static function proxy( $object, $timeout = null ) {
119
			if ( !self::$cacheStore ) {
120
				self::$cacheStore = self::create();
121
			}
122
			return new ar_cacheProxy( $object, self::$cacheStore, $timeout );
123
		}
124
125
	}
126
127
	class ar_cacheProxy extends arWrapper {
128
		// TODO: allow more control on retrieval:
129
		// - get contents from cache even though cache may be stale
130
		//   perhaps through an extra option in __construct?
131
		public $cacheStore = null;
132
		public $cacheController = null;
133
		public $cacheTimeout = '2 hours';
134
135
		public function __construct( $object, $cacheStore, $cacheTimeout = null, $cacheController = null ) {
136
			parent::__construct( $object );
137
			$this->cacheStore = $cacheStore;
138
			$this->cacheController = $cacheController;
139
			if ( isset($cacheTimeout) ) {
140
				$this->cacheTimeout = $cacheTimeout;
141
			}
142
		}
143
144
		protected function __callCatch( $method, $args ) {
145
			ob_start();
146
			$result = parent::__call( $method, $args );
147
			$output = ob_get_contents();
148
			ob_end_clean();
149
			return array(
150
				'output' => $output,
151
				'result' => $result
152
			);
153
		}
154
155
		protected function __callCached( $method, $args, $path ) {
156
			if ( !$cacheData = $this->cacheStore->getIfFresh( $path ) ) {
157
				if ( $this->cacheStore->lock( $path ) ) {
158
					$cacheData = $this->__callCatch( $method, $args );
159
					$this->cacheStore->set( $path, $cacheData, $this->cacheTimeout );
160
				} else if ( $this->cacheStore->wait( $path ) ){
161
					$cacheData = $this->cacheStore->get( $path );
162
				} else {
163
					$cacheData = $this->__callCatch( $method, $args ); // just get the result and return it
164
				}
165
			}
166
			return $cacheData;
167
		}
168
169
		public function __call( $method, $args ) {
170
			$path = $method . '(' . md5( serialize($args) ) . ')';
171
			$cacheData = $this->__callCached( $method, $args, $path );
172
			echo $cacheData['output'];
173
			$result = $cacheData['result'];
174
			if ( is_object( $result ) ) {
175
				$result = new ar_cacheProxy( $result, $this->cacheStore->subStore( $path ) );
176
			}
177
			return $result;
178
		}
179
180
		public function __get( $name ) {
181
			$result = parent::__get( $name );
182
			if ( is_object( $result ) ) {
183
				$result = new ar_cacheProxy( $result, $this->cacheStore->subStore( $name ) );
184
			}
185
			return $result;
186
		}
187
188
	}
189
190
	interface ar_cacheStoreInterface {
191
		public function get( $path );
192
		public function set( $path, $value, $timeout = 7200 );
193
		public function info( $path );
194
		public function clear( $path = null );
195
		public function subStore( $path );
196
		public function isFresh( $path );
197
		public function purge( $name = null );
198
		public function getIfFresh( $name, $freshness=0 );
199
	}
200
201
	class ar_cacheStore implements ar_cacheStoreInterface, arKeyValueStoreInterface {
202
203
		protected $basePath = '';
204
		protected $timeout = 7200;
205
		protected $mode = 0777;
206
207
		public function __construct( $basePath, $timeout = 7200, $mode = 0777 ) {
208
			$this->basePath = preg_replace('/\.\./', '', $basePath);
209
210
			if ( is_string($timeout) ) {
211
				$timeout = strtotime( $timeout, 0);
212
			}
213
			$this->timeout = $timeout;
214
			$this->mode = $mode;
215
216
			if ( !defined("ARCacheDir") ) {
217
				define( "ARCacheDir", sys_get_temp_dir().'/ar_cache/' );
218
			}
219
			if ( !file_exists( ARCacheDir ) ) {
220
				mkdir( ARCacheDir, $this->mode );
221
			}
222
			if ( !file_exists( ARCacheDir ) ) {
223
				throw new ar_error("Cache Directory does not exist ( ".ARCacheDir." )", 1);
224
			}
225
			if ( !is_dir( ARCacheDir ) ) {
226
				throw new ar_error("Cache Directory is not a directory ( ".ARCacheDir." )", 1);
227
			}
228
			if ( !is_writable( ARCacheDir ) ) {
229
				throw new ar_error("Cache Directory is not writable ( ".ARCacheDir." )", 1);
230
			}
231
		}
232
233
		protected function cachePath( $path ) {
234
			// last '=' is added to prevent conflicts between subdirectories and cache images
235
			// images always end in a '=', directories never end in a '='
236
			return ARCacheDir . $this->basePath . preg_replace('/(\.\.|\=)/', '', $path) . '=';
237
		}
238
239
		public function subStore( $path ) {
240
			return new ar_cacheStore( $this->basePath . preg_replace('/(\.\.|\=)/', '', $path) );
241
		}
242
243
		public function get( $path ) {
244
			$cachePath = $this->cachePath( $path );
245
			if ( file_exists( $cachePath ) ) {
246
				return unserialize( file_get_contents( $cachePath ) );
247
			} else {
248
				return null;
249
			}
250
		}
251
252
		public function getvar( $name ) {
253
			return $this->get( $name );
254
		}
255
256
		public function isFresh( $path ) {
257
			$cachePath = $this->cachePath( $path );
258
			if ( file_exists( $cachePath ) ) {
259
				return ( filemtime( $cachePath ) > time() );
260
			} else {
261
				return false;
262
			}
263
		}
264
265
		public function getIfFresh( $path, $freshness = 0 ) {
266
			$info = $this->info( $path );
267
			if ( $info && $info['timeout'] >= $freshness ) {
268
				return $this->get( $path );
269
			} else {
270
				return false;
271
			}
272
		}
273
274
		public function lock( $path, $blocking = false ) {
275
			// locks the file against writing by other processes, so generation of time or resource expensive images
276
			// will not happen by multiple processes simultaneously
277
			$cachePath = $this->cachePath( $path );
278
			$dir = dirname( $cachePath );
279
			if ( !file_exists( $dir ) ) {
280
				mkdir( $dir, $this->mode, true ); //recursive
281
			}
282
			$lockFile = fopen( $cachePath, 'c' );
283
			$lockMode = LOCK_EX;
284
			if ( !$blocking ) {
285
				$lockMode = $lockMode|LOCK_NB;
286
			}
287
			return flock( $lockFile, $lockMode );
288
		}
289
290
		public function wait( $path ) {
291
			$cachePath = $this->cachePath( $path );
292
			$lockFile = fopen( $cachePath, 'c' );
293
			$result = flock( $lockFile, LOCK_EX );
294
			fclose( $lockFile );
295
			return $result;
296
		}
297
298
		public function putvar( $name, $value ) {
299
			return $this->set( $name, $value );
300
		}
301
302
		public function set( $path, $value, $timeout = null ) {
303
			$cachePath = $this->cachePath( $path );
304
			if ( !isset( $timeout ) ) {
305
				$timeout = $this->timeout;
306
			}
307
			if ( is_string( $timeout ) ) {
308
				$timeout = strtotime( $timeout, 0);
309
			}
310
			$dir = dirname( $cachePath );
311
			if ( !file_exists( $dir ) ) {
312
				mkdir( $dir, $this->mode, true ); //recursive
313
			}
314
			if ( false !== file_put_contents( $cachePath, serialize( $value ), LOCK_EX ) ) {
315
				// FIXME: check dat de lock gemaakt met lock() weg is na file_put_contents
316
				touch( $cachePath, time() + $timeout );
317
			} else {
318
				return false;
319
			}
320
		}
321
322
		public function info( $path ) {
323
			$cachePath = $this->cachePath( $path );
324
			if ( file_exists( $cachePath ) && is_readable( $cachePath ) ) {
325
				return array(
326
					'size' => filesize($cachePath),
327
					'fresh' => $this->isFresh( $path ),
328
					'ctime' => filectime( $cachePath ),
329
					'timeout' => filemtime( $cachePath ) - time()
330
				);
331
			} else {
332
				return false;
333
			}
334
		}
335
336
		public function clear( $path = null ) {
337
			$cachePath = $this->cachePath( $path );
338
			if ( file_exists( $cachePath ) ) {
339
				return unlink( $cachePath );
340
			} else {
341
				return true;
342
			}
343
		}
344
345
		public function purge( $path = null ) {
346
			$this->clear( $path );
347
			$cachePath = substr( $this->cachePath( $path ), 0, -1 ); // remove last '='
348
			if ( file_exists( $cachePath ) ) {
349
				if ( is_dir( $cachePath ) ){
350
					$cacheDir = dir( $cachePath );
351
					while (false !== ($entry = $cacheDir->read())) {
352
						if ( $entry != '.' && $entry != '..' ) {
353
							$this->purge( $path . '/' . $entry );
354
						}
355
					}
356
					return rmdir( $cachePath );
357
				} else {
358
					return unlink( $cachePath );
359
				}
360
			} else {
361
				return true;
362
			}
363
		}
364
	}
365