]> git.sur5r.net Git - bacula/bacula/blob - gui/baculum/framework/Caching/TCache.php
99b3f24c5476b9c66e5b626f91a9625ef06d4914
[bacula/bacula] / gui / baculum / framework / Caching / TCache.php
1 <?php
2 /**
3  * TCache and cache dependency classes.
4  *
5  * @author Qiang Xue <qiang.xue@gmail.com>
6  * @link http://www.pradosoft.com/
7  * @copyright Copyright &copy; 2005-2014 PradoSoft
8  * @license http://www.pradosoft.com/license/
9  * @package System.Caching
10  */
11
12 Prado::using('System.Collections.TList');
13
14 /**
15  * TCache class
16  *
17  * TCache is the base class for cache classes with different cache storage implementation.
18  *
19  * TCache implements the interface {@link ICache} with the following methods,
20  * - {@link get} : retrieve the value with a key (if any) from cache
21  * - {@link set} : store the value with a key into cache
22  * - {@link add} : store the value only if cache does not have this key
23  * - {@link delete} : delete the value with the specified key from cache
24  * - {@link flush} : delete all values from cache
25  *
26  * Each value is associated with an expiration time. The {@link get} operation
27  * ensures that any expired value will not be returned. The expiration time by
28  * the number of seconds. A expiration time 0 represents never expire.
29  *
30  * By definition, cache does not ensure the existence of a value
31  * even if it never expires. Cache is not meant to be an persistent storage.
32  *
33  * Child classes must implement the following methods:
34  * - {@link getValue}
35  * - {@link setValue}
36  * - {@link addValue}
37  * - {@link deleteValue}
38  * and optionally {@link flush}
39  *
40  * Since version 3.1.2, TCache implements the ArrayAccess interface such that
41  * the cache acts as an array.
42  *
43  * @author Qiang Xue <qiang.xue@gmail.com>
44  * @package System.Caching
45  * @since 3.0
46  */
47 abstract class TCache extends TModule implements ICache, ArrayAccess
48 {
49         private $_prefix=null;
50         private $_primary=true;
51
52         /**
53          * Initializes the cache module.
54          * This method initializes the cache key prefix and registers the cache module
55          * with the application if the cache is primary.
56          * @param TXmlElement the module configuration
57          */
58         public function init($config)
59         {
60                 if($this->_prefix===null)
61                         $this->_prefix=$this->getApplication()->getUniqueID();
62                 if($this->_primary)
63                 {
64                         if($this->getApplication()->getCache()===null)
65                                 $this->getApplication()->setCache($this);
66                         else
67                                 throw new TConfigurationException('cache_primary_duplicated',get_class($this));
68                 }
69         }
70
71         /**
72          * @return boolean whether this cache module is used as primary/system cache.
73          * A primary cache is used by PRADO core framework to cache data such as
74          * parsed templates, themes, etc.
75          */
76         public function getPrimaryCache()
77         {
78                 return $this->_primary;
79         }
80
81         /**
82          * @param boolean whether this cache module is used as primary/system cache. Defaults to false.
83          * @see getPrimaryCache
84          */
85         public function setPrimaryCache($value)
86         {
87                 $this->_primary=TPropertyValue::ensureBoolean($value);
88         }
89
90         /**
91          * @return string a unique prefix for the keys of cached values.
92          * If it is not explicitly set, it will take the value of {@link TApplication::getUniqueID}.
93          */
94         public function getKeyPrefix()
95         {
96                 return $this->_prefix;
97         }
98
99         /**
100          * @param string a unique prefix for the keys of cached values
101          */
102         public function setKeyPrefix($value)
103         {
104                 $this->_prefix=$value;
105         }
106
107         /**
108          * @param string a key identifying a value to be cached
109          * @return sring a key generated from the provided key which ensures the uniqueness across applications
110          */
111         protected function generateUniqueKey($key)
112         {
113                 return md5($this->_prefix.$key);
114         }
115
116         /**
117          * Retrieves a value from cache with a specified key.
118          * @param string a key identifying the cached value
119          * @return mixed the value stored in cache, false if the value is not in the cache or expired.
120          */
121         public function get($id)
122         {
123                 if(($data=$this->getValue($this->generateUniqueKey($id)))!==false)
124                 {
125                         if(!is_array($data))
126                                 return false;
127                         if(!($data[1] instanceof ICacheDependency) || !$data[1]->getHasChanged())
128                                 return $data[0];
129                 }
130                 return false;
131         }
132
133         /**
134          * Stores a value identified by a key into cache.
135          * If the cache already contains such a key, the existing value and
136          * expiration time will be replaced with the new ones. If the value is
137          * empty, the cache key will be deleted.
138          *
139          * @param string the key identifying the value to be cached
140          * @param mixed the value to be cached
141          * @param integer the number of seconds in which the cached value will expire. 0 means never expire.
142          * @param ICacheDependency dependency of the cached item. If the dependency changes, the item is labeled invalid.
143          * @return boolean true if the value is successfully stored into cache, false otherwise
144          */
145         public function set($id,$value,$expire=0,$dependency=null)
146         {
147                 if(empty($value) && $expire === 0)
148                         $this->delete($id);
149                 else
150                 {
151                         $data=array($value,$dependency);
152                         return $this->setValue($this->generateUniqueKey($id),$data,$expire);
153                 }
154         }
155
156         /**
157          * Stores a value identified by a key into cache if the cache does not contain this key.
158          * Nothing will be done if the cache already contains the key or if value is empty.
159          * @param string the key identifying the value to be cached
160          * @param mixed the value to be cached
161          * @param integer the number of seconds in which the cached value will expire. 0 means never expire.
162          * @param ICacheDependency dependency of the cached item. If the dependency changes, the item is labeled invalid.
163          * @return boolean true if the value is successfully stored into cache, false otherwise
164          */
165         public function add($id,$value,$expire=0,$dependency=null)
166         {
167                 if(empty($value) && $expire === 0)
168                         return false;
169                 $data=array($value,$dependency);
170                 return $this->addValue($this->generateUniqueKey($id),$data,$expire);
171         }
172
173         /**
174          * Deletes a value with the specified key from cache
175          * @param string the key of the value to be deleted
176          * @return boolean if no error happens during deletion
177          */
178         public function delete($id)
179         {
180                 return $this->deleteValue($this->generateUniqueKey($id));
181         }
182
183         /**
184          * Deletes all values from cache.
185          * Be careful of performing this operation if the cache is shared by multiple applications.
186          * Child classes may implement this method to realize the flush operation.
187          * @throws TNotSupportedException if this method is not overridden by child classes
188          */
189         public function flush()
190         {
191                 throw new TNotSupportedException('cache_flush_unsupported');
192         }
193
194         /**
195          * Retrieves a value from cache with a specified key.
196          * This method should be implemented by child classes to store the data
197          * in specific cache storage. The uniqueness and dependency are handled
198          * in {@link get()} already. So only the implementation of data retrieval
199          * is needed.
200          * @param string a unique key identifying the cached value
201          * @return string the value stored in cache, false if the value is not in the cache or expired.
202          */
203         abstract protected function getValue($key);
204
205         /**
206          * Stores a value identified by a key in cache.
207          * This method should be implemented by child classes to store the data
208          * in specific cache storage. The uniqueness and dependency are handled
209          * in {@link set()} already. So only the implementation of data storage
210          * is needed.
211          *
212          * @param string the key identifying the value to be cached
213          * @param string the value to be cached
214          * @param integer the number of seconds in which the cached value will expire. 0 means never expire.
215          * @return boolean true if the value is successfully stored into cache, false otherwise
216          */
217         abstract protected function setValue($key,$value,$expire);
218
219         /**
220          * Stores a value identified by a key into cache if the cache does not contain this key.
221          * This method should be implemented by child classes to store the data
222          * in specific cache storage. The uniqueness and dependency are handled
223          * in {@link add()} already. So only the implementation of data storage
224          * is needed.
225          *
226          * @param string the key identifying the value to be cached
227          * @param string the value to be cached
228          * @param integer the number of seconds in which the cached value will expire. 0 means never expire.
229          * @return boolean true if the value is successfully stored into cache, false otherwise
230          */
231         abstract protected function addValue($key,$value,$expire);
232
233         /**
234          * Deletes a value with the specified key from cache
235          * This method should be implemented by child classes to delete the data from actual cache storage.
236          * @param string the key of the value to be deleted
237          * @return boolean if no error happens during deletion
238          */
239         abstract protected function deleteValue($key);
240
241         /**
242          * Returns whether there is a cache entry with a specified key.
243          * This method is required by the interface ArrayAccess.
244          * @param string a key identifying the cached value
245          * @return boolean
246          */
247         public function offsetExists($id)
248         {
249                 return $this->get($id) !== false;
250         }
251
252         /**
253          * Retrieves the value from cache with a specified key.
254          * This method is required by the interface ArrayAccess.
255          * @param string a key identifying the cached value
256          * @return mixed the value stored in cache, false if the value is not in the cache or expired.
257          */
258         public function offsetGet($id)
259         {
260                 return $this->get($id);
261         }
262
263         /**
264          * Stores the value identified by a key into cache.
265          * If the cache already contains such a key, the existing value will be
266          * replaced with the new ones. To add expiration and dependencies, use the set() method.
267          * This method is required by the interface ArrayAccess.
268          * @param string the key identifying the value to be cached
269          * @param mixed the value to be cached
270          */
271         public function offsetSet($id, $value)
272         {
273                 $this->set($id, $value);
274         }
275
276         /**
277          * Deletes the value with the specified key from cache
278          * This method is required by the interface ArrayAccess.
279          * @param string the key of the value to be deleted
280          * @return boolean if no error happens during deletion
281          */
282         public function offsetUnset($id)
283         {
284                 $this->delete($id);
285         }
286 }
287
288
289 /**
290  * TCacheDependency class.
291  *
292  * TCacheDependency is the base class implementing {@link ICacheDependency} interface.
293  * Descendant classes must implement {@link getHasChanged()} to provide
294  * actual dependency checking logic.
295  *
296  * The property value of {@link getHasChanged HasChanged} tells whether
297  * the dependency is changed or not.
298  *
299  * You may disable the dependency checking by setting {@link setEnabled Enabled}
300  * to false.
301  *
302  * Note, since the dependency objects often need to be serialized so that
303  * they can persist across requests, you may need to implement __sleep() and
304  * __wakeup() if the dependency objects contain resource handles which are
305  * not serializable.
306  *
307  * Currently, the following dependency classes are provided in the PRADO release:
308  * - {@link TFileCacheDependency}: checks whether a file is changed or not
309  * - {@link TDirectoryCacheDependency}: checks whether a directory is changed or not
310  * - {@link TGlobalStateCacheDependency}: checks whether a global state is changed or not
311  * - {@link TChainedCacheDependency}: checks whether any of a list of dependencies is changed or not
312  *
313  * @author Qiang Xue <qiang.xue@gmail.com>
314  * @package System.Caching
315  * @since 3.1.0
316  */
317 abstract class TCacheDependency extends TComponent implements ICacheDependency
318 {
319 }
320
321
322 /**
323  * TFileCacheDependency class.
324  *
325  * TFileCacheDependency performs dependency checking based on the
326  * last modification time of the file specified via {@link setFileName FileName}.
327  * The dependency is reported as unchanged if and only if the file's
328  * last modification time remains unchanged.
329  *
330  * @author Qiang Xue <qiang.xue@gmail.com>
331  * @package System.Caching
332  * @since 3.1.0
333  */
334 class TFileCacheDependency extends TCacheDependency
335 {
336         private $_fileName;
337         private $_timestamp;
338
339         /**
340          * Constructor.
341          * @param string name of the file whose change is to be checked.
342          */
343         public function __construct($fileName)
344         {
345                 $this->setFileName($fileName);
346         }
347
348         /**
349          * @return string the name of the file whose change is to be checked
350          */
351         public function getFileName()
352         {
353                 return $this->_fileName;
354         }
355
356         /**
357          * @param string the name of the file whose change is to be checked
358          */
359         public function setFileName($value)
360         {
361                 $this->_fileName=$value;
362                 $this->_timestamp=@filemtime($value);
363         }
364
365         /**
366          * @return int the last modification time of the file
367          */
368         public function getTimestamp()
369         {
370                 return $this->_timestamp;
371         }
372
373         /**
374          * Performs the actual dependency checking.
375          * This method returns true if the last modification time of the file is changed.
376          * @return boolean whether the dependency is changed or not.
377          */
378         public function getHasChanged()
379         {
380                 return @filemtime($this->_fileName)!==$this->_timestamp;
381         }
382 }
383
384 /**
385  * TDirectoryCacheDependency class.
386  *
387  * TDirectoryCacheDependency performs dependency checking based on the
388  * modification time of the files contained in the specified directory.
389  * The directory being checked is specified via {@link setDirectory Directory}.
390  *
391  * By default, all files under the specified directory and subdirectories
392  * will be checked. If the last modification time of any of them is changed
393  * or if different number of files are contained in a directory, the dependency
394  * is reported as changed. By specifying {@link setRecursiveCheck RecursiveCheck}
395  * and {@link setRecursiveLevel RecursiveLevel}, one can limit the checking
396  * to a certain depth of the subdirectories.
397  *
398  * @author Qiang Xue <qiang.xue@gmail.com>
399  * @package System.Caching
400  * @since 3.1.0
401  */
402 class TDirectoryCacheDependency extends TCacheDependency
403 {
404         private $_recursiveCheck=true;
405         private $_recursiveLevel=-1;
406         private $_timestamps;
407         private $_directory;
408
409         /**
410          * Constructor.
411          * @param string the directory to be checked
412          */
413         public function __construct($directory)
414         {
415                 $this->setDirectory($directory);
416         }
417
418         /**
419          * @return string the directory to be checked
420          */
421         public function getDirectory()
422         {
423                 return $this->_directory;
424         }
425
426         /**
427          * @param string the directory to be checked
428          * @throws TInvalidDataValueException if the directory does not exist
429          */
430         public function setDirectory($directory)
431         {
432                 if(($path=realpath($directory))===false || !is_dir($path))
433                         throw new TInvalidDataValueException('directorycachedependency_directory_invalid',$directory);
434                 $this->_directory=$path;
435                 $this->_timestamps=$this->generateTimestamps($path);
436         }
437
438         /**
439          * @return boolean whether the subdirectories of the directory will also be checked.
440          * It defaults to true.
441          */
442         public function getRecursiveCheck()
443         {
444                 return $this->_recursiveCheck;
445         }
446
447         /**
448          * @param boolean whether the subdirectories of the directory will also be checked.
449          */
450         public function setRecursiveCheck($value)
451         {
452                 $this->_recursiveCheck=TPropertyValue::ensureBoolean($value);
453         }
454
455         /**
456          * @return int the depth of the subdirectories to be checked.
457          * It defaults to -1, meaning unlimited depth.
458          */
459         public function getRecursiveLevel()
460         {
461                 return $this->_recursiveLevel;
462         }
463
464         /**
465          * Sets a value indicating the depth of the subdirectories to be checked.
466          * This is meaningful only when {@link getRecursiveCheck RecursiveCheck}
467          * is true.
468          * @param int the depth of the subdirectories to be checked.
469          * If the value is less than 0, it means unlimited depth.
470          * If the value is 0, it means checking the files directly under the specified directory.
471          */
472         public function setRecursiveLevel($value)
473         {
474                 $this->_recursiveLevel=TPropertyValue::ensureInteger($value);
475         }
476
477         /**
478          * Performs the actual dependency checking.
479          * This method returns true if the directory is changed.
480          * @return boolean whether the dependency is changed or not.
481          */
482         public function getHasChanged()
483         {
484                 return $this->generateTimestamps($this->_directory)!=$this->_timestamps;
485         }
486
487         /**
488          * Checks to see if the file should be checked for dependency.
489          * This method is invoked when dependency of the whole directory is being checked.
490          * By default, it always returns true, meaning the file should be checked.
491          * You may override this method to check only certain files.
492          * @param string the name of the file that may be checked for dependency.
493          * @return boolean whether this file should be checked.
494          */
495         protected function validateFile($fileName)
496         {
497                 return true;
498         }
499
500         /**
501          * Checks to see if the specified subdirectory should be checked for dependency.
502          * This method is invoked when dependency of the whole directory is being checked.
503          * By default, it always returns true, meaning the subdirectory should be checked.
504          * You may override this method to check only certain subdirectories.
505          * @param string the name of the subdirectory that may be checked for dependency.
506          * @return boolean whether this subdirectory should be checked.
507          */
508         protected function validateDirectory($directory)
509         {
510                 return true;
511         }
512
513         /**
514          * Determines the last modification time for files under the directory.
515          * This method may go recursively into subdirectories if
516          * {@link setRecursiveCheck RecursiveCheck} is set true.
517          * @param string the directory name
518          * @param int level of the recursion
519          * @return array list of file modification time indexed by the file path
520          */
521         protected function generateTimestamps($directory,$level=0)
522         {
523                 if(($dir=opendir($directory))===false)
524                         throw new TIOException('directorycachedependency_directory_invalid',$directory);
525                 $timestamps=array();
526                 while(($file=readdir($dir))!==false)
527                 {
528                         $path=$directory.DIRECTORY_SEPARATOR.$file;
529                         if($file==='.' || $file==='..')
530                                 continue;
531                         else if(is_dir($path))
532                         {
533                                 if(($this->_recursiveLevel<0 || $level<$this->_recursiveLevel) && $this->validateDirectory($path))
534                                         $timestamps=array_merge($this->generateTimestamps($path,$level+1));
535                         }
536                         else if($this->validateFile($path))
537                                 $timestamps[$path]=filemtime($path);
538                 }
539                 closedir($dir);
540                 return $timestamps;
541         }
542 }
543
544
545 /**
546  * TGlobalStateCacheDependency class.
547  *
548  * TGlobalStateCacheDependency checks if a global state is changed or not.
549  * If the global state is changed, the dependency is reported as changed.
550  * To specify which global state this dependency should check with,
551  * set {@link setStateName StateName} to the name of the global state.
552  *
553  * @author Qiang Xue <qiang.xue@gmail.com>
554  * @package System.Caching
555  * @since 3.1.0
556  */
557 class TGlobalStateCacheDependency extends TCacheDependency
558 {
559         private $_stateName;
560         private $_stateValue;
561
562         /**
563          * Constructor.
564          * @param string the name of the global state
565          */
566         public function __construct($name)
567         {
568                 $this->setStateName($name);
569         }
570
571         /**
572          * @return string the name of the global state
573          */
574         public function getStateName()
575         {
576                 return $this->_stateName;
577         }
578
579         /**
580          * @param string the name of the global state
581          * @see TApplication::setGlobalState
582          */
583         public function setStateName($value)
584         {
585                 $this->_stateName=$value;
586                 $this->_stateValue=Prado::getApplication()->getGlobalState($value);
587         }
588
589         /**
590          * Performs the actual dependency checking.
591          * This method returns true if the specified global state is changed.
592          * @return boolean whether the dependency is changed or not.
593          */
594         public function getHasChanged()
595         {
596                 return $this->_stateValue!==Prado::getApplication()->getGlobalState($this->_stateName);
597         }
598 }
599
600
601 /**
602  * TChainedCacheDependency class.
603  *
604  * TChainedCacheDependency represents a list of cache dependency objects
605  * and performs the dependency checking based on the checking results of
606  * these objects. If any of them reports a dependency change, TChainedCacheDependency
607  * will return true for the checking.
608  *
609  * To add dependencies to TChainedCacheDependency, use {@link getDependencies Dependencies}
610  * which gives a {@link TCacheDependencyList} instance and can be used like an array
611  * (see {@link TList} for more details}).
612  *
613  * @author Qiang Xue <qiang.xue@gmail.com>
614  * @package System.Caching
615  * @since 3.1.0
616  */
617 class TChainedCacheDependency extends TCacheDependency
618 {
619         private $_dependencies=null;
620
621         /**
622          * @return TCacheDependencyList list of dependency objects
623          */
624         public function getDependencies()
625         {
626                 if($this->_dependencies===null)
627                         $this->_dependencies=new TCacheDependencyList;
628                 return $this->_dependencies;
629         }
630
631         /**
632          * Performs the actual dependency checking.
633          * This method returns true if any of the dependency objects
634          * reports a dependency change.
635          * @return boolean whether the dependency is changed or not.
636          */
637         public function getHasChanged()
638         {
639                 if($this->_dependencies!==null)
640                 {
641                         foreach($this->_dependencies as $dependency)
642                                 if($dependency->getHasChanged())
643                                         return true;
644                 }
645                 return false;
646         }
647 }
648
649
650 /**
651  * TApplicationStateCacheDependency class.
652  *
653  * TApplicationStateCacheDependency performs dependency checking based on
654  * the mode of the currently running PRADO application.
655  * The dependency is reportedly as unchanged if and only if the application
656  * is running in performance mode.
657  *
658  * You may chain this dependency together with other dependencies
659  * so that only when the application is not in performance mode the other dependencies
660  * will be checked.
661  *
662  * @author Qiang Xue <qiang.xue@gmail.com>
663  * @package System.Caching
664  * @since 3.1.0
665  */
666 class TApplicationStateCacheDependency extends TCacheDependency
667 {
668         /**
669          * Performs the actual dependency checking.
670          * This method returns true if the currently running application is not in performance mode.
671          * @return boolean whether the dependency is changed or not.
672          */
673         public function getHasChanged()
674         {
675                 return Prado::getApplication()->getMode()!==TApplicationMode::Performance;
676         }
677 }
678
679 /**
680  * TCacheDependencyList class.
681  *
682  * TCacheDependencyList represents a list of cache dependency objects.
683  * Only objects implementing {@link ICacheDependency} can be added into this list.
684  *
685  * TCacheDependencyList can be used like an array. See {@link TList}
686  * for more details.
687  *
688  * @author Qiang Xue <qiang.xue@gmail.com>
689  * @package System.Caching
690  * @since 3.1.0
691  */
692 class TCacheDependencyList extends TList
693 {
694         /**
695          * Inserts an item at the specified position.
696          * This overrides the parent implementation by performing additional type checking
697          * for each newly added item.
698          * @param integer the specified position.
699          * @param mixed new item
700          * @throws TInvalidDataTypeException if the item to be inserted is not a dependency instance
701          */
702         public function insertAt($index,$item)
703         {
704                 if($item instanceof ICacheDependency)
705                         parent::insertAt($index,$item);
706                 else
707                         throw new TInvalidDataTypeException('cachedependencylist_cachedependency_required');
708         }
709 }
710