Skip to content

Commit 4515974

Browse files
committed
Issue #159 : caching system
* New Cache class with these methods: Public: - setKey() - getKey() - flush() Private: - deleteKey() - isValidKey() - isObsoleteKey() - getKeyPath() - getCachePath() - isActivated() * New constants: - CACHE_ENABLED activates/deactivates the cache - CACHE_TIME is the default duration of the cache - CACHE_PATH is the path to the cache folder There are also fallback constants in the class to use it outside of Transvision as a drop-in class. * GET['nocache'] to force a page not to use caching * Cache duration - age of the last generation of data (uses a timestamp file in glossaire.sh), defaults to 5h20 if the timestamp file is missing - can be defined in seconds when using getKey(), ex getKey($id, 300) * Onestring model makes a use of the class, ex: if (! $data = Cache::getKey($cache_id)) { /* create $data */ /* Now cache data */ Cache::setKey($cache_id, $data); } * Tests for all methods
1 parent c976a45 commit 4515974

File tree

8 files changed

+282
-14
lines changed

8 files changed

+282
-14
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ web/docs
1010
app/config/config.ini
1111
web/p12n
1212
web/TMX
13+
cache/*.cache
14+
cache/lastdataupdate.txt

app/classes/Transvision/Bugzilla.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class Bugzilla
1818
*/
1919
public static function getBugzillaComponents()
2020
{
21-
$cache_file = CACHE . 'bugzilla_components.json';
21+
$cache_file = CACHE_PATH . 'bugzilla_components.json';
2222
if (!file_exists($cache_file)) {
2323
$json_url = 'https://bugzilla.mozilla.org/jsonrpc.cgi?method=Product.get&params=[%20{%20%22names%22:%20[%22Mozilla%20Localizations%22]}%20]';
2424
file_put_contents($cache_file, file_get_contents($json_url));

app/classes/Transvision/Cache.php

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
<?php
2+
namespace Transvision;
3+
4+
/**
5+
* Cache class
6+
*
7+
* A simple and fast file caching system.
8+
*
9+
* 3 global constants are used: CACHE_ENABLED, CACHE_PATH and CACHE_TIME
10+
* If those app constants are not available, the system temp folder
11+
* and the class constants CACHE_ENABLED and CACHE_TIME are used.
12+
*
13+
* @package Transvision
14+
*/
15+
class Cache
16+
{
17+
/** Fallback for activation of Cache */
18+
const CACHE_ENABLED = true;
19+
20+
/** Duration of the cache */
21+
const CACHE_TIME = 3600;
22+
23+
/**
24+
* Create a cache file with serialized data
25+
*
26+
* We use PHP serialization and not json for example as it allows
27+
* storing not only data but also data representations and
28+
* instantiated objects.
29+
*
30+
* @param string $id
31+
* @param object $data
32+
* @return boolean True if cache file is created. False if there was an error.
33+
*/
34+
public static function setKey($id, $data)
35+
{
36+
if (! self::isActivated()) {
37+
return false;
38+
}
39+
40+
return file_put_contents(self::getKeyPath($id), serialize($data)) ? true : false;
41+
}
42+
43+
/**
44+
* Get the cached serialized data via its UID
45+
*
46+
* @param string $id UID of the cache
47+
* @param int $ttl Number of seconds for time to live. Default to 0.
48+
* @return object Unserialized cached data. Or False
49+
*/
50+
public static function getKey($id, $ttl = 0)
51+
{
52+
if (! self::isActivated()) {
53+
return false;
54+
}
55+
56+
if ($ttl == 0) {
57+
$ttl = defined('CACHE_TIME') ? CACHE_TIME : self::CACHE_TIME;
58+
}
59+
60+
return self::isValidKey($id, $ttl)
61+
? unserialize(file_get_contents(self::getKeyPath($id)))
62+
: false;
63+
}
64+
65+
/**
66+
* Flush our cache
67+
*
68+
* @return boolean True if files in cache are deleted, False if some files were not deleted
69+
*/
70+
public static function flush()
71+
{
72+
$files = glob(self::getCachePath() . '*.cache');
73+
74+
return ! in_array(false, array_map('unlink', $files));
75+
}
76+
77+
/**
78+
* Is the caching system activated?
79+
* We look at the existence of a CACHE constant and if it's at True
80+
*
81+
* @return boolean True if activated, False if deactivated
82+
*/
83+
private static function isActivated()
84+
{
85+
return defined('CACHE_ENABLED') ? CACHE_ENABLED : self::CACHE_ENABLED;
86+
}
87+
88+
/**
89+
* Check if cached data for a key is usable
90+
*
91+
* @param string $id UID for the data
92+
* @param int $ttl Number of seconds for time to live
93+
* @return boolean if valid data, false if no usable cached data
94+
*/
95+
private static function isValidKey($id, $ttl)
96+
{
97+
// No cache file
98+
if (! file_exists(self::getKeyPath($id))) {
99+
return false;
100+
}
101+
102+
// Cache is obsolete and was deleted
103+
if (self::isObsoleteKey($id, $ttl)) {
104+
self::deleteKey($id);
105+
return false;
106+
}
107+
108+
// All good, cache is valid
109+
return true;
110+
}
111+
112+
/**
113+
* Delete a cache file thanks to its UID
114+
*
115+
* @param string $id UID of the cached data
116+
* @return boolean True if data was deleted, false if it doesn't exist
117+
*/
118+
private static function deleteKey($id)
119+
{
120+
$file = self::getKeyPath($id);
121+
122+
if (! file_exists($file)) {
123+
return false;
124+
}
125+
126+
unlink($file);
127+
clearstatcache(true, $file);
128+
129+
return true;
130+
}
131+
132+
/**
133+
* Get the path to the cached file
134+
*
135+
* File is of the form a840d513be5240045ccc979208f739a168946332.cache
136+
*
137+
* @param string $id UID of the cached file
138+
* @return string path for the file
139+
*/
140+
public static function getKeyPath($id)
141+
{
142+
return self::getCachePath() . sha1($id) . '.cache';
143+
}
144+
145+
/**
146+
* Get the path to the cache folder
147+
*
148+
* If a CACHE_PATH global constant is defined, use it,
149+
* otherwise write to OS folder for temporary files.
150+
*
151+
* @return string path to Cache
152+
*/
153+
private static function getCachePath()
154+
{
155+
return defined('CACHE_PATH') ? CACHE_PATH : sys_get_temp_dir() . '/';
156+
}
157+
158+
/**
159+
* Check if the data has not expired
160+
* @param string $id UID of the cached file
161+
* @param int $ttl Number of seconds for time to live
162+
* @return boolean True if file is obsolete, False if it is still fresh
163+
*/
164+
private static function isObsoleteKey($id, $ttl)
165+
{
166+
return filemtime(self::getKeyPath($id)) < (time() - $ttl);
167+
}
168+
}

app/inc/constants.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,15 @@
1515
define('VIEWS', APP_ROOT . 'views/');
1616
define('MODELS', APP_ROOT . 'models/');
1717
define('CONTROLLERS', APP_ROOT . 'controllers/');
18-
define('CACHE', INSTALL_ROOT . 'cache/');
18+
define('CACHE_ENABLED', isset($_GET['nocache']) ? false : true);
19+
define('CACHE_PATH', INSTALL_ROOT . 'cache/');
20+
21+
if (file_exists(CACHE_PATH . 'lastdataupdate.txt')) {
22+
define('CACHE_TIME', time() - filemtime(CACHE_PATH . 'lastdataupdate.txt'));
23+
} else {
24+
// 05h20 cache (because we extract data every 6h and extraction lasts 25mn)
25+
define('CACHE_TIME', 19200);
26+
}
1927

2028
// Special modes for the app
2129
define('DEBUG', (strstr(VERSION, 'dev') || isset($_GET['debug'])) ? true : false);

app/models/onestring.php

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,34 @@
1414
$entity = isset($_GET['entity']) ? $_GET['entity'] : false;
1515

1616
// Invalid entity, we don't do any calculation and get back to the view
17-
if (!$entity) {
17+
if (! $entity) {
1818
return $error = 1;
1919
} elseif (!array_key_exists($entity, $strings)) {
2020
return $error = 2;
2121
}
2222

23-
if ($repo != 'mozilla_org') {
24-
$translations = ['en-US' => $strings[$entity]];
25-
}
23+
$cache_id = $repo . $entity . 'alllocales';
24+
25+
if (! $translations = Cache::getKey($cache_id)) {
2626

27-
foreach(Files::getFilenamesInFolder(TMX . $repo . '/', ['ab-CD']) as $localecode) {
28-
$strings = Utils::getRepoStrings($localecode, $repo);
29-
if (array_key_exists($entity, $strings)) {
30-
$translations[$localecode] = $strings[$entity];
31-
} else {
32-
$translations[$localecode] = false;
27+
if ($repo == 'mozilla_org') {
28+
// we always want to have an en-US locale for the Json API
29+
$translations = ['en-US' => $strings[$entity]];
3330
}
31+
32+
foreach (Files::getFilenamesInFolder(TMX . $repo . '/', ['ab-CD']) as $locale_code) {
33+
34+
$strings = Utils::getRepoStrings($locale_code, $repo);
35+
36+
if (array_key_exists($entity, $strings)) {
37+
$translations[$locale_code] = $strings[$entity];
38+
} else {
39+
$translations[$locale_code] = false;
40+
}
41+
}
42+
43+
Cache::setKey($cache_id, $translations);
3444
}
45+
46+
3547
unset($strings);

glossaire.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,3 +295,10 @@ then
295295
echogreen "Create L20N test repo TMX for en-US"
296296
nice -20 python tmxmaker.py $l20n_test/l20ntestdata/en-US/ $l20n_test/l20ntestdata/en-US/ en-US en-US l20n_test
297297
fi
298+
299+
# Create a file to get the timestamp of the last string extraction for caching
300+
echogreen "Creating extraction timestamp for cache system"
301+
touch cache/lastdataupdate.txt
302+
303+
echogreen "Deleting all the old cached files"
304+
rm -f cache/*.cache

tests/units/Transvision/Cache.php

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
namespace Transvision\tests\units;
3+
use atoum;
4+
5+
require_once __DIR__ . '/../bootstrap.php';
6+
7+
class Cache extends atoum\test
8+
{
9+
10+
public function beforeTestMethod($method)
11+
{
12+
// Executed *before each* test method.
13+
switch ($method)
14+
{
15+
case 'testFlush':
16+
// Prepare testing environment for testFlush().
17+
$files = new \Transvision\Cache();
18+
// create a few files to delete
19+
$files->setKey('file_1', 'foobar');
20+
$files->setKey('file_2', 'foobar');
21+
$files->setKey('file_3', 'foobar');
22+
break;
23+
24+
case 'testGetKey':
25+
// Prepare testing environment for testGetKey().
26+
$files = new \Transvision\Cache();
27+
$files->setKey('this_test', 'foobar');
28+
// Change the timestamp to 100 seconds in the past so we can test expiration
29+
touch(CACHE_PATH . sha1('this_test') . '.cache', time()-100);
30+
break;
31+
}
32+
}
33+
34+
public function testSetKey()
35+
{
36+
$obj = new \Transvision\Cache();
37+
$this
38+
->boolean($obj->setKey('this_test', 'foobar'))
39+
->isEqualTo(true)
40+
;
41+
}
42+
43+
public function getKeyDP()
44+
{
45+
return array(
46+
['this_test', 0, 'foobar'], // valid key
47+
['this_test', 2, false], // expired key
48+
['id_that_doesnt_exist', 0, false], // non-existing key
49+
);
50+
}
51+
52+
/**
53+
* @dataProvider getKeyDP
54+
*/
55+
public function testGetKey($a, $b, $c)
56+
{
57+
$obj = new \Transvision\Cache();
58+
$this
59+
->variable($obj->getKey($a, $b))
60+
->isEqualTo($c)
61+
;
62+
}
63+
64+
public function testFlush()
65+
{
66+
$obj = new \Transvision\Cache();
67+
$this
68+
->boolean($obj->flush())
69+
->isEqualTo(true)
70+
;
71+
}
72+
}

tests/units/bootstrap.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<?php
22
$ini_array = parse_ini_file(__DIR__ . '/../../app/config/config.ini');
33
define('TMX', $ini_array['root'] . '/TMX/');
4-
define('CACHE', __DIR__ . '/../testfiles/cache/');
5-
4+
define('CACHE_PATH', realpath(__DIR__ . '/../testfiles/cache/') . '/');
65

76
require __DIR__.'/../../vendor/autoload.php';

0 commit comments

Comments
 (0)