diff --git a/src/Caching/BulkWriter.php b/src/Caching/BulkWriter.php
new file mode 100644
index 00000000..371e72f6
--- /dev/null
+++ b/src/Caching/BulkWriter.php
@@ -0,0 +1,36 @@
+Similar to write()
, but instead of a single key/value item, it works on multiple items specified in items
+ *
+ * @param array{string, mixed} $items An array of key/data pairs to store on the server
+ * @param array $dp Global dependencies of each stored value
+ * @return bool Returns true
on success or false
on failure
+ * @throws NotSupportedException
+ * @throws InvalidStateException
+ */
+ function bulkWrite(array $items, array $dp): bool;
+
+ /**
+ * Removes multiple items from cache
+ */
+ function bulkRemove(array $keys): void;
+}
diff --git a/src/Caching/Cache.php b/src/Caching/Cache.php
index 245836b5..bad6f78e 100644
--- a/src/Caching/Cache.php
+++ b/src/Caching/Cache.php
@@ -173,6 +173,56 @@ public function bulkLoad(array $keys, ?callable $generator = null): array
}
+ /**
+ * Writes multiple items into cache
+ *
+ * @param array $items
+ * @param array|null $dependencies
+ * @return array Stored items
+ *
+ * @throws InvalidArgumentException
+ */
+ public function bulkSave(array $items, ?array $dependencies = null): array
+ {
+ $storedItems = [];
+
+ if (!$this->storage instanceof BulkWriter) {
+
+ foreach ($items as $key => $data) {
+ $storedItems[$key] = $this->save($key, $data, $dependencies);
+ }
+ return $storedItems;
+ }
+
+ $dependencies = $this->completeDependencies($dependencies);
+
+ if (isset($dependencies[self::Expire]) && $dependencies[self::Expire] <= 0) {
+ $this->storage->bulkRemove(array_map(fn($key): string => $this->generateKey($key), array_keys($items)));
+ return [];
+ }
+
+ $removals = [];
+ $toCache = [];
+ foreach ($items as $key => $data) {
+ $cKey = $this->generateKey($key);
+
+ if ($data === null) {
+ $removals[] = $cKey;
+ } else {
+ $storedItems[$key] = $toCache[$cKey] = $data;
+ }
+ }
+
+ if (!empty($removals)) {
+ $this->storage->bulkRemove($removals);
+ }
+
+ $this->storage->bulkWrite($toCache, $dependencies);
+
+ return $storedItems;
+ }
+
+
/**
* Writes item into the cache.
* Dependencies are:
diff --git a/src/Caching/Storages/MemcachedStorage.php b/src/Caching/Storages/MemcachedStorage.php
index 03af23b3..295783c5 100644
--- a/src/Caching/Storages/MemcachedStorage.php
+++ b/src/Caching/Storages/MemcachedStorage.php
@@ -16,7 +16,7 @@
/**
* Memcached storage using memcached extension.
*/
-class MemcachedStorage implements Nette\Caching\Storage, Nette\Caching\BulkReader
+class MemcachedStorage implements Nette\Caching\Storage, Nette\Caching\BulkReader, Nette\Caching\BulkWriter
{
/** @internal cache structure */
private const
@@ -25,8 +25,6 @@ class MemcachedStorage implements Nette\Caching\Storage, Nette\Caching\BulkReade
MetaDelta = 'delta';
private \Memcached $memcached;
- private string $prefix;
- private ?Journal $journal;
/**
@@ -41,15 +39,12 @@ public static function isAvailable(): bool
public function __construct(
string $host = 'localhost',
int $port = 11211,
- string $prefix = '',
- ?Journal $journal = null,
+ private string $prefix = '',
+ private ?Journal $journal = null,
) {
if (!static::isAvailable()) {
throw new Nette\NotSupportedException("PHP extension 'memcached' is not loaded.");
}
-
- $this->prefix = $prefix;
- $this->journal = $journal;
$this->memcached = new \Memcached;
if ($host) {
$this->addServer($host, $port);
@@ -168,12 +163,60 @@ public function write(string $key, $data, array $dp): void
}
+ public function bulkWrite(array $items, array $dp): bool
+ {
+ if (isset($dp[Cache::Items])) {
+ throw new Nette\NotSupportedException('Dependent items are not supported by MemcachedStorage.');
+ }
+
+ $records = [];
+
+ $expire = 0;
+ if (isset($dp[Cache::Expire])) {
+ $expire = (int) $dp[Cache::Expire];
+ }
+
+ foreach ($items as $key => $data) {
+ $key = urlencode($this->prefix . $key);
+ $meta = [
+ self::MetaData => $data,
+ ];
+
+ if (!empty($dp[Cache::Sliding])) {
+ $meta[self::MetaDelta] = $expire; // sliding time
+ }
+
+ if (isset($dp[Cache::Callbacks])) {
+ $meta[self::MetaCallbacks] = $dp[Cache::Callbacks];
+ }
+
+ if (isset($dp[Cache::Tags]) || isset($dp[Cache::Priority])) {
+ if (!$this->journal) {
+ throw new Nette\InvalidStateException('CacheJournal has not been provided.');
+ }
+
+ $this->journal->write($key, $dp);
+ }
+
+ $records[$key] = $meta;
+ }
+
+ return $this->memcached->setMulti($records, $expire);
+ }
+
+
public function remove(string $key): void
{
$this->memcached->delete(urlencode($this->prefix . $key), 0);
}
+ public function bulkRemove(array $keys): void
+ {
+ $this->memcached->deleteMulti(array_map(fn($key) => urlencode($this->prefix . $key), $keys), 0);
+ }
+
+
public function clean(array $conditions): void
{
if (!empty($conditions[Cache::All])) {
diff --git a/tests/Caching/Cache.bulkSave.phpt b/tests/Caching/Cache.bulkSave.phpt
new file mode 100644
index 00000000..b5eb295c
--- /dev/null
+++ b/tests/Caching/Cache.bulkSave.phpt
@@ -0,0 +1,53 @@
+bulkSave([1, 2]), 'data');
+ Assert::same([1 => 'value1', 2 => 'value2'], $cache->bulkSave([1 => 'value1', 2 => 'value2']), 'data');
+
+ $data = $cache->bulkLoad([1, 2]);
+ Assert::same('value1', $data[1]['data']);
+ Assert::same('value2', $data[2]['data']);
+});
+
+test('storage with bulk write support', function () {
+ $storage = new BulkWriteTestStorage;
+ $cache = new Cache($storage, 'ns');
+ Assert::same([1, 2], $cache->bulkSave([1, 2]), 'data');
+ Assert::same([1 => 'value1', 2 => 'value2'], $cache->bulkSave([1 => 'value1', 2 => 'value2']), 'data');
+
+ $data = $cache->bulkLoad([1, 2]);
+ Assert::same('value1', $data[1]['data']);
+ Assert::same('value2', $data[2]['data']);
+});
+
+test('dependencies', function () {
+ $storage = new BulkWriteTestStorage;
+ $cache = new Cache($storage, 'ns');
+ $dependencies = [Cache::Tags => ['tag']];
+ $cache->bulkSave([1 => 'value1', 2 => 'value2'], $dependencies);
+
+ $data = $cache->bulkLoad([1, 2]);
+ Assert::same($dependencies, $data[1]['dependencies']);
+ Assert::same($dependencies, $data[2]['dependencies']);
+
+ $cache->clean($dependencies);
+
+ Assert::same([1 => null, 2 => null], $cache->bulkLoad([1, 2]));
+});
diff --git a/tests/Caching/Cache.php b/tests/Caching/Cache.php
index 57320d8c..e918f56a 100644
--- a/tests/Caching/Cache.php
+++ b/tests/Caching/Cache.php
@@ -2,6 +2,7 @@
declare(strict_types=1);
+use Nette\Caching\BulkWriter;
use Nette\Caching\IBulkReader;
use Nette\Caching\IStorage;
@@ -37,6 +38,25 @@ public function remove(string $key): void
public function clean(array $conditions): void
{
+ if (!empty($conditions[Nette\Caching\Cache::All])) {
+ $this->data = [];
+ return;
+ }
+
+ //unset based by tags
+ if (!empty($conditions[Nette\Caching\Cache::Tags])) {
+ $unsets = [];
+ foreach ($this->data as $key => $data) {
+ $tags = $data['dependencies'][Nette\Caching\Cache::Tags] ?? null;
+ if (array_intersect($conditions[Nette\Caching\Cache::Tags], $tags)) {
+ $unsets[$key] = $key;
+ }
+ }
+
+ foreach ($unsets as $unsetKey) {
+ unset($this->data[$unsetKey]);
+ }
+ }
}
}
@@ -55,3 +75,35 @@ public function bulkRead(array $keys): array
return $result;
}
}
+
+class BulkWriteTestStorage extends TestStorage implements BulkWriter
+{
+ public function bulkRead(array $keys): array
+ {
+ $result = [];
+ foreach ($keys as $key) {
+ $data = $this->read($key);
+ if ($data !== null) {
+ $result[$key] = $data;
+ }
+ }
+
+ return $result;
+ }
+
+
+ public function bulkRemove(array $keys): void
+ {
+
+ }
+
+
+ public function bulkWrite($items, array $dp): bool
+ {
+ foreach ($items as $key => $data) {
+ $this->write($key, $data, $dp);
+ }
+
+ return true;
+ }
+}
diff --git a/tests/Storages/Memcached.bulkWrite.phpt b/tests/Storages/Memcached.bulkWrite.phpt
new file mode 100644
index 00000000..49616675
--- /dev/null
+++ b/tests/Storages/Memcached.bulkWrite.phpt
@@ -0,0 +1,37 @@
+bulkSave(["foo" => "bar"]);
+Assert::same(['foo' => 'bar', 'lorem' => null], $cache->bulkLoad(['foo', 'lorem']));
+
+//tags
+$dependencies = [Cache::Tags => ['tag']];
+$cache->bulkSave(["foo" => "bar"], $dependencies);
+Assert::same(['foo' => 'bar'], $cache->bulkLoad(['foo']));
+$cache->clean($dependencies);
+Assert::same(['foo' => null], $cache->bulkLoad(['foo']));
\ No newline at end of file