Skip to content

Commit c25b0c1

Browse files
committed
added bulk read support [closes #2]
1 parent 3129fa9 commit c25b0c1

File tree

8 files changed

+252
-27
lines changed

8 files changed

+252
-27
lines changed

src/Caching/Cache.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,45 @@ public function load($key, $fallback = NULL)
9797
}
9898

9999

100+
/**
101+
* Reads multiple items from the cache
102+
* @param array
103+
* @param callable
104+
* @return array
105+
*/
106+
public function bulkLoad(array $keys, $fallback = NULL)
107+
{
108+
foreach ($keys as $key) {
109+
if (!is_scalar($key)) {
110+
throw new Nette\InvalidArgumentException('Only scalar keys are allowed in a bulkLoad method.');
111+
}
112+
}
113+
$keys = array_combine(array_map([$this, 'generateKey'], $keys), $keys);
114+
$storage = $this->getStorage();
115+
if ($storage instanceof IBulkReadStorage) {
116+
$cacheData = $storage->bulkRead(array_keys($keys));
117+
} else {
118+
$cacheData = [];
119+
foreach ($keys as $storageKey => $key) {
120+
$cacheData[$storageKey] = $this->load($key);
121+
}
122+
}
123+
$result = [];
124+
foreach ($keys as $storageKey => $key) {
125+
if (isset($cacheData[$storageKey])) {
126+
$result[$key] = $cacheData[$storageKey];
127+
} elseif ($fallback) {
128+
$result[$key] = $this->save($key, function (& $dependencies) use ($key, $fallback) {
129+
return call_user_func_array($fallback, [$key, & $dependencies]);
130+
});
131+
} else {
132+
$result[$key] = NULL;
133+
}
134+
}
135+
return $result;
136+
}
137+
138+
100139
/**
101140
* Writes item into the cache.
102141
* Dependencies are:

src/Caching/IBulkReadStorage.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the Nette Framework (https://nette.org)
5+
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6+
*/
7+
8+
namespace Nette\Caching;
9+
10+
11+
/**
12+
* Cache storage with a bulk read support.
13+
*/
14+
interface IBulkReadStorage extends IStorage
15+
{
16+
17+
/**
18+
* Read from cache.
19+
* @param string key
20+
* @return array key => value pairs, missing items are omitted
21+
*/
22+
function bulkRead(array $keys);
23+
24+
}

src/Caching/Storages/NewMemcachedStorage.php

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
/**
1515
* Memcached storage using memcached extension.
1616
*/
17-
class NewMemcachedStorage implements Nette\Caching\IStorage
17+
class NewMemcachedStorage implements Nette\Caching\IBulkReadStorage
1818
{
1919
use Nette\SmartObject;
2020

@@ -83,30 +83,46 @@ public function getConnection()
8383
*/
8484
public function read($key)
8585
{
86-
$key = urlencode($this->prefix . $key);
87-
$meta = $this->memcached->get($key);
88-
if (!$meta) {
89-
return NULL;
90-
}
86+
$result = $this->bulkRead([$key]);
87+
return isset($result[$key]) ? $result[$key] : NULL;
88+
}
9189

92-
// meta structure:
93-
// array(
94-
// data => stored data
95-
// delta => relative (sliding) expiration
96-
// callbacks => array of callbacks (function, args)
97-
// )
98-
99-
// verify dependencies
100-
if (!empty($meta[self::META_CALLBACKS]) && !Cache::checkCallbacks($meta[self::META_CALLBACKS])) {
101-
$this->memcached->delete($key, 0);
102-
return NULL;
103-
}
10490

105-
if (!empty($meta[self::META_DELTA])) {
106-
$this->memcached->replace($key, $meta, $meta[self::META_DELTA] + time());
91+
/**
92+
* Read from cache.
93+
* @param string key
94+
* @return array key => value pairs, missing items are omitted
95+
*/
96+
public function bulkRead(array $keys)
97+
{
98+
$keys = array_combine(array_map(function ($key) {
99+
return urlencode($this->prefix . $key);
100+
}, $keys), $keys);
101+
$metas = $this->memcached->getMulti(array_keys($keys));
102+
$result = [];
103+
foreach ($metas as $key => $meta) {
104+
105+
// meta structure:
106+
// array(
107+
// data => stored data
108+
// delta => relative (sliding) expiration
109+
// callbacks => array of callbacks (function, args)
110+
// )
111+
112+
// verify dependencies
113+
if (!empty($meta[self::META_CALLBACKS]) && !Cache::checkCallbacks($meta[self::META_CALLBACKS])) {
114+
$this->memcached->delete($key, 0);
115+
116+
return NULL;
117+
}
118+
119+
if (!empty($meta[self::META_DELTA])) {
120+
$this->memcached->replace($key, $meta, $meta[self::META_DELTA] + time());
121+
}
122+
$result[$keys[$key]] = $meta[self::META_DATA];
107123
}
108124

109-
return $meta[self::META_DATA];
125+
return $result;
110126
}
111127

112128

src/Caching/Storages/SQLiteStorage.php

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
/**
1515
* SQLite storage.
1616
*/
17-
class SQLiteStorage implements Nette\Caching\IStorage
17+
class SQLiteStorage implements Nette\Caching\IBulkReadStorage
1818
{
1919
use Nette\SmartObject;
2020

@@ -53,14 +53,28 @@ public function __construct($path)
5353
*/
5454
public function read($key)
5555
{
56-
$stmt = $this->pdo->prepare('SELECT data, slide FROM cache WHERE key=? AND (expire IS NULL OR expire >= ?)');
57-
$stmt->execute([$key, time()]);
58-
if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
56+
$result = $this->bulkRead([$key]);
57+
return isset($result[$key]) ? $result[$key] : NULL;
58+
}
59+
60+
61+
/**
62+
* Read from cache.
63+
* @param string key
64+
* @return array key => value pairs, missing items are omitted
65+
*/
66+
public function bulkRead(array $keys)
67+
{
68+
$stmt = $this->pdo->prepare('SELECT key, data, slide FROM cache WHERE key IN (?' . str_repeat(',?', count($keys) - 1) . ') AND (expire IS NULL OR expire >= ?)');
69+
$stmt->execute(array_merge($keys, [time()]));
70+
$result = [];
71+
foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
5972
if ($row['slide'] !== NULL) {
60-
$this->pdo->prepare('UPDATE cache SET expire = ? + slide WHERE key=?')->execute([time(), $key]);
73+
$this->pdo->prepare('UPDATE cache SET expire = ? + slide WHERE key=?')->execute([time(), $row['key']]);
6174
}
62-
return unserialize($row['data']);
75+
$result[$row['key']] = unserialize($row['data']);
6376
}
77+
return $result;
6478
}
6579

6680

tests/Caching/Cache.bulkLoad.phpt

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
/**
4+
* Test: Nette\Caching\Cache load().
5+
*/
6+
7+
use Nette\Caching\Cache;
8+
use Tester\Assert;
9+
10+
11+
require __DIR__ . '/../bootstrap.php';
12+
13+
require __DIR__ . '/Cache.php';
14+
15+
//storage without bulk load support
16+
test(function () {
17+
$storage = new TestStorage();
18+
$cache = new Cache($storage, 'ns');
19+
Assert::same([1 => NULL, 2 => NULL], $cache->bulkLoad([1, 2]), 'data');
20+
21+
Assert::same([1 => 1, 2 => 2], $cache->bulkLoad([1, 2], function ($key) {
22+
return $key;
23+
}));
24+
25+
$data = $cache->bulkLoad([1, 2]);
26+
Assert::same(1, $data[1]['data']);
27+
Assert::same(2, $data[2]['data']);
28+
});
29+
30+
//storage with bulk load support
31+
test(function () {
32+
$storage = new BulkReadTestStorage();
33+
$cache = new Cache($storage, 'ns');
34+
Assert::same([1 => NULL, 2 => NULL], $cache->bulkLoad([1, 2]));
35+
36+
Assert::same([1 => 1, 2 => 2], $cache->bulkLoad([1, 2], function ($key) {
37+
return $key;
38+
}));
39+
40+
$data = $cache->bulkLoad([1, 2]);
41+
Assert::same(1, $data[1]['data']);
42+
Assert::same(2, $data[2]['data']);
43+
});
44+
45+
//dependencies
46+
test(function () {
47+
$storage = new BulkReadTestStorage();
48+
$cache = new Cache($storage, 'ns');
49+
$dependencies = [Cache::TAGS => 'tag'];
50+
$cache->bulkLoad([1], function ($key, & $deps) use ($dependencies) {
51+
$deps = $dependencies;
52+
return $key;
53+
});
54+
55+
$data = $cache->bulkLoad([1, 2]);
56+
Assert::same($dependencies, $data[1]['dependencies']);
57+
});
58+
59+
test(function () {
60+
Assert::exception(function () {
61+
$cache = new Cache(new BulkReadTestStorage());
62+
$cache->bulkLoad([[1]]);
63+
}, Nette\InvalidArgumentException::class, 'Only scalar keys are allowed in a bulkLoad method.');
64+
});

tests/Caching/Cache.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?php
22

3+
use Nette\Caching\IBulkReadStorage;
34
use Nette\Caching\IStorage;
45

56
class TestStorage implements IStorage
@@ -25,3 +26,20 @@ public function remove($key) {}
2526

2627
public function clean(array $conditions) {}
2728
}
29+
30+
class BulkReadTestStorage extends TestStorage implements IBulkReadStorage
31+
{
32+
function bulkRead(array $keys)
33+
{
34+
$result = [];
35+
foreach ($keys as $key) {
36+
$data = $this->read($key);
37+
if ($data !== NULL) {
38+
$result[$key] = $data;
39+
}
40+
}
41+
42+
return $result;
43+
}
44+
45+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
/**
4+
* Test: Nette\Caching\Storages\NewMemcachedStorage and bulkRead
5+
*/
6+
7+
use Nette\Caching\Storages\NewMemcachedStorage;
8+
use Nette\Caching\Cache;
9+
use Tester\Assert;
10+
11+
12+
require __DIR__ . '/../bootstrap.php';
13+
14+
15+
if (!NewMemcachedStorage::isAvailable()) {
16+
Tester\Environment::skip('Requires PHP extension Memcached.');
17+
}
18+
19+
Tester\Environment::lock('memcached-files', TEMP_DIR);
20+
21+
22+
23+
$cache = new Cache(new NewMemcachedStorage('localhost'));
24+
25+
$cache->save('foo', 'bar');
26+
27+
Assert::same(['foo' => 'bar', 'lorem' => NULL], $cache->bulkLoad(['foo', 'lorem']));
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
/**
4+
* Test: Nette\Caching\Storages\SQLiteStorage and bulk read.
5+
*/
6+
7+
use Nette\Caching\Cache;
8+
use Nette\Caching\Storages\SQLiteStorage;
9+
use Tester\Assert;
10+
11+
12+
require __DIR__ . '/../bootstrap.php';
13+
14+
15+
if (!extension_loaded('pdo_sqlite')) {
16+
Tester\Environment::skip('Requires PHP extension pdo_sqlite.');
17+
}
18+
19+
20+
$cache = new Cache(new SQLiteStorage(':memory:'));
21+
$cache->save('foo', 'bar');
22+
23+
Assert::same(['foo' => 'bar', 'lorem' => NULL], $cache->bulkLoad(['foo', 'lorem']));

0 commit comments

Comments
 (0)