Page MenuHomeDevCentral

D3233.id8303.diff
No OneTemporary

D3233.id8303.diff

diff --git a/_utils/templates/generate-compose-json.php b/_utils/templates/generate-compose-json.php
--- a/_utils/templates/generate-compose-json.php
+++ b/_utils/templates/generate-compose-json.php
@@ -140,7 +140,11 @@
"name" => "Keruald contributors",
],
],
+ "provide" => [
+ "psr/simple-cache-implementation" => "1.0|2.0|3.0",
+ ],
"require" => [
+ "psr/simple-cache" => "^1.0|^2.0|^3.0",
"ext-intl" => "*",
],
"require-dev" => [
@@ -153,6 +157,10 @@
"symfony/yaml" => "^6.0.3",
"squizlabs/php_codesniffer" => "^3.6",
],
+ "suggest" => [
+ "ext-memcached" => "*",
+ "ext-redis" => "*",
+ ],
"replace" => getReplace($metadata["packages"]),
"autoload" => getAutoload($metadata["packages_namespaces"]),
"scripts" => [
diff --git a/cache/LICENSE b/cache/LICENSE
new file mode 100644
--- /dev/null
+++ b/cache/LICENSE
@@ -0,0 +1,23 @@
+Copyright (c) 2010, 2023 Sébastien Santoro aka Dereckson
+Some rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/cache/README.md b/cache/README.md
new file mode 100644
--- /dev/null
+++ b/cache/README.md
@@ -0,0 +1,42 @@
+# keruald/cache
+
+This library offers a simple layer of abstraction
+for cache operations, with concrete implementations.
+
+This cache implementation is compatible with PSR-16.
+
+This cache implementation is NOT compatible with PSR-6.
+
+## Configuration
+
+To get a cache instance, you need to pass configuration as an array.
+
+The properties and values depend on the engine you want to use.
+
+### Memcached
+
+| Key | Value | Default |
+|---------------|--------------------------------|:------------|
+| engine | MemcachedCache class reference | |
+| server | The memcached hostname | "localhost" |
+| port | The memcached port | 11211 |
+| sasl_username | The SASL username | |
+| sasl_password | The SASL password | "" |
+
+### Redis
+
+| Key | Value | Default |
+|----------|--------------------------------|:------------|
+| engine | MemcachedCache class reference | |
+| server | The memcached hostname | "localhost" |
+| port | The memcached port | 6379 |
+| database | The redis database number | 0 |
+
+### Void
+
+This cache allows unit tests or to offer a default cache,
+when no other configuration is offered.
+
+| Key | Value |
+|------------|---------------------------|
+| engine | VoidCache class reference |
diff --git a/cache/composer.json b/cache/composer.json
new file mode 100644
--- /dev/null
+++ b/cache/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "keruald/cache",
+ "description": "Abstraction layer for cache. Compatible PSR-16.",
+ "keywords": [
+ "keruald",
+ "cache"
+ ],
+ "minimum-stability": "stable",
+ "license": "BSD-2-Clause",
+ "authors": [
+ {
+ "name": "Sébastien Santoro",
+ "email": "dereckson@espace-win.org"
+ }
+ ],
+ "provide": {
+ "psr/simple-cache-implementation": "1.0|2.0|3.0"
+ },
+ "require": {
+ "keruald/omnitools": "^0.11"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.2"
+ },
+ "suggest": {
+ "ext-memcached": "*",
+ "ext-redis": "*"
+ }
+}
diff --git a/cache/src/Cache.php b/cache/src/Cache.php
new file mode 100644
--- /dev/null
+++ b/cache/src/Cache.php
@@ -0,0 +1,49 @@
+<?php
+declare(strict_types=1);
+
+namespace Keruald\Cache;
+
+use Keruald\OmniTools\Collections\MultipleOperation;
+use Psr\SimpleCache\CacheInterface;
+
+use DateInterval;
+
+abstract class Cache implements CacheInterface {
+
+ ///
+ /// Loader
+ ///
+
+ public abstract static function load (array $config) : Cache;
+
+ ///
+ /// Default implementation for CacheInterface -Multiple methods
+ ///
+
+ public function getMultiple (
+ iterable $keys,
+ mixed $default = null
+ ) : iterable {
+ foreach ($keys as $key) {
+ yield $key => $this->get($key, $default);
+ }
+ }
+
+ public function setMultiple (
+ iterable $values,
+ DateInterval|int|null $ttl = null
+ ) : bool {
+ return MultipleOperation::do(
+ $values,
+ fn($key, $value) => $this->set($key, $value, $ttl)
+ );
+ }
+
+ public function deleteMultiple (iterable $keys) : bool {
+ return MultipleOperation::do(
+ $keys,
+ fn($key) => $this->delete($key)
+ );
+ }
+
+}
diff --git a/cache/src/CacheFactory.php b/cache/src/CacheFactory.php
new file mode 100644
--- /dev/null
+++ b/cache/src/CacheFactory.php
@@ -0,0 +1,44 @@
+<?php
+declare(strict_types=1);
+
+namespace Keruald\Cache;
+
+use Keruald\Cache\Engines\CacheVoid;
+use Keruald\Cache\Exceptions\CacheException;
+
+/**
+ * Cache caller
+ */
+class CacheFactory {
+
+ const DEFAULT_ENGINE = CacheVoid::class;
+
+ /**
+ * Loads the cache instance, building it according a configuration array.
+ *
+ * The correct cache instance to initialize will be determined from the
+ * 'engine' key. It should match the name of a Cache class.
+ *
+ * This method will create an instance of the specified object,
+ * calling the load static method from this object class.
+ *
+ * Example:
+ * <code>
+ * $config['engine'] = CacheQuux::class;
+ * $cache = Cache::load($config); //Cache:load() will call CacheQuux:load();
+ * </code>
+ *
+ * @return Cache the cache instance
+ * @throws CacheException
+ */
+ static function load (array $config) : Cache {
+ $engine = $config["engine"] ?? self::DEFAULT_ENGINE;
+
+ if (!class_exists($engine)) {
+ throw new CacheException("Can't initialize $engine cache engine. The class can't be found.");
+ }
+
+ return call_user_func([$engine, 'load'], $config);
+ }
+
+}
diff --git a/cache/src/Engines/CacheMemcached.php b/cache/src/Engines/CacheMemcached.php
new file mode 100644
--- /dev/null
+++ b/cache/src/Engines/CacheMemcached.php
@@ -0,0 +1,161 @@
+<?php
+declare(strict_types=1);
+
+namespace Keruald\Cache\Engines;
+
+use Keruald\Cache\Cache;
+use Keruald\Cache\Exceptions\CacheException;
+use Keruald\Cache\Features\WithPrefix;
+use Keruald\OmniTools\Collections\HashMap;
+use Keruald\OmniTools\Collections\Vector;
+
+use Memcached;
+
+/**
+ * Memcached cache
+ *
+ * /!\ This class uses the Memcached extension AND NOT Memcache.
+ *
+ * References:
+ *
+ * @link https://www.php.net/manual/en/book.memcached.php
+ * @link https://memcached.org
+ */
+class CacheMemcached extends Cache {
+
+ use WithPrefix;
+
+ ///
+ /// Constants - default value
+ ///
+
+ const DEFAULT_SERVER = "localhost";
+
+ const DEFAULT_PORT = 11211;
+
+ ///
+ /// Properties
+ ///
+
+ private Memcached $memcached;
+
+ ///
+ /// Constructors
+ ///
+
+ public function __construct (Memcached $memcached) {
+ $this->memcached = $memcached;
+ }
+
+ public static function load (array $config) : self {
+ //Checks extension is okay
+ if (!extension_loaded('memcached')) {
+ if (extension_loaded('memcache')) {
+ throw new CacheException("Can't initialize Memcached cache engine: PHP extension memcached not loaded. This class uses the Memcached extension AND NOT the Memcache extension (this one is loaded).</strong>");
+ } else {
+ throw new CacheException("Can't initialize Memcached cache engine: PHP extension memcached not loaded.");
+ }
+ }
+
+ $memcached = new Memcached;
+ $memcached->addServer(
+ $config["server"] ?? self::DEFAULT_SERVER,
+ $config["port"] ?? self::DEFAULT_PORT,
+ );
+
+ // SASL authentication
+ if (array_key_exists("sasl_username", $config)) {
+ $memcached->setOption(Memcached::OPT_BINARY_PROTOCOL, true);
+ $memcached->setSaslAuthData(
+ $config["sasl_username"],
+ $config["sasl_password"] ?? "",
+ );
+ }
+
+ return new self($memcached);
+ }
+
+ ///
+ /// Cache operations
+ ///
+
+ /**
+ * Gets the specified key's data
+ */
+ function get (string $key, mixed $default = null) : mixed {
+ $key = $this->getUnsafePrefix() . $key;
+
+ $result = $this->memcached->get($key);
+
+ return match ($result) {
+ false => $default,
+ default => unserialize($result),
+ };
+ }
+
+ /**
+ * Sets the specified data at the specified key
+ */
+ function set (
+ string $key,
+ mixed $value,
+ null|int|\DateInterval $ttl = null
+ ) : bool {
+ $key = $this->getUnsafePrefix() . $key;
+
+ return $this->memcached->set($key, serialize($value));
+ }
+
+ /**
+ * Deletes the specified key's data
+ *
+ * @param string $key the key to delete
+ */
+ function delete (string $key) : bool {
+ $key = $this->getUnsafePrefix() . $key;
+
+ return $this->memcached->delete($key);
+ }
+
+ public function clear () : bool {
+ $keys = $this->memcached->getAllKeys();
+
+ if ($keys === false) {
+ return false;
+ }
+
+ if ($this->hasPrefix()) {
+ // Restrict to our keys, as we don't use Memcached::OPT_PREFIX_KEY
+ $prefix = $this->getUnsafePrefix();
+ $keys = Vector::from($keys)
+ ->filter(fn($key) => str_starts_with($key, $prefix))
+ ->toArray();
+ }
+
+ $result = $this->memcached->deleteMulti($keys);
+ return self::areAllOperationsSuccessful($result);
+ }
+
+ public function has (string $key) : bool {
+ $key = $this->getUnsafePrefix() . $key;
+
+ $this->memcached->get($key);
+
+ return match ($this->memcached->getResultCode()) {
+ Memcached::RES_NOTFOUND => false,
+ default => true,
+ };
+ }
+
+ ///
+ /// Helper methods
+ ///
+
+ private static function areAllOperationsSuccessful (array $result) : bool {
+ return HashMap::from($result)
+ ->all(function ($key, $value) {
+ return $value === true; // can be true or Memcached::RES_*
+ });
+ }
+
+}
diff --git a/cache/src/Engines/CacheRedis.php b/cache/src/Engines/CacheRedis.php
new file mode 100644
--- /dev/null
+++ b/cache/src/Engines/CacheRedis.php
@@ -0,0 +1,163 @@
+<?php
+
+namespace Keruald\Cache\Engines;
+
+use Keruald\Cache\Cache;
+use Keruald\Cache\Exceptions\CacheException;
+
+use DateInterval;
+use DateTimeImmutable;
+
+use Redis;
+use RedisException;
+
+class CacheRedis extends Cache {
+
+ ///
+ /// Constants - default value
+ ///
+
+ const DEFAULT_SERVER = "localhost";
+
+ const DEFAULT_PORT = 6379;
+
+ ///
+ /// Properties
+ ///
+
+ private Redis $redis;
+
+ ///
+ /// Constructors
+ ///
+
+ public function __construct (Redis $client) {
+ $this->redis = $client;
+ }
+
+ public static function load (array $config) : Cache {
+ //Checks extension is okay
+ if (!extension_loaded("redis")) {
+ throw new CacheException("Can't initialize Redis cache engine: PHP extension redis not loaded.");
+ }
+
+ $client = new Redis();
+ try {
+ $client->connect(
+ $config["server"] ?? self::DEFAULT_SERVER,
+ $config["port"] ?? self::DEFAULT_PORT,
+ );
+
+ if (array_key_exists("database", $config)) {
+ $client->select($config["database"]);
+ }
+
+ } catch (RedisException $ex) {
+ throw new CacheException(
+ "Can't initialize Redis cache engine: Can't connect to Redis server",
+ 0,
+ $ex
+ );
+ }
+
+ return new self($client);
+ }
+
+ ///
+ /// Cache operations
+ ///
+
+ public function get (string $key, mixed $default = null) : mixed {
+ try {
+ $value = $this->redis->get($key);
+ } catch (RedisException $ex) {
+ throw new CacheException("Can't get item", 0, $ex);
+ }
+
+ return match ($value) {
+ false => $default,
+ default => unserialize($value),
+ };
+ }
+
+ function set (
+ string $key,
+ mixed $value,
+ null|int|DateInterval $ttl = null
+ ) : bool {
+ try {
+ if ($ttl === null) {
+ $this->redis->set($key, serialize($value));
+ } else {
+ $this->redis->setex($key, self::parse_interval($ttl), $value);
+ }
+ } catch (RedisException $ex) {
+ throw new CacheException("Can't set item", 0, $ex);
+ }
+
+ return true;
+ }
+
+ public function delete (string $key) : bool {
+ try {
+ $countDeleted = $this->redis->del($key);
+ } catch (RedisException $e) {
+ throw new CacheException("Can't delete item", 0, $ex);
+ }
+
+ return $countDeleted === 1;
+ }
+
+ public function clear () : bool {
+ try {
+ $this->redis->flushDB();
+ } catch (RedisException $e) {
+ throw new CacheException("Can't clear cache", 0, $ex);
+ }
+
+ return true;
+ }
+
+ public function has (string $key) : bool {
+ try {
+ $count = $this->redis->exists($key);
+ } catch (RedisException $e) {
+ throw new CacheException("Can't check item", 0, $ex);
+ }
+
+ return $count === 1;
+ }
+
+ ///
+ /// Overrides for multiple operations
+ ///
+
+ public function deleteMultiple (iterable $keys) : bool {
+ $keys = [...$keys];
+ $expectedCount = count($keys);
+
+ try {
+ $countDeleted = $this->redis->del($keys);
+ } catch (RedisException $e) {
+ throw new CacheException("Can't delete items", 0, $ex);
+ }
+
+ return $countDeleted === $expectedCount;
+ }
+
+ ///
+ /// Helper methods
+ ///
+
+ private static function parse_interval (DateInterval|int $ttl) : int {
+ if (is_integer($ttl)) {
+ return $ttl;
+ }
+
+ $start = new DateTimeImmutable;
+ $end = $start->add($ttl);
+
+ return $end->getTimestamp() - $start->getTimestamp();
+ }
+
+}
diff --git a/cache/src/Engines/CacheVoid.php b/cache/src/Engines/CacheVoid.php
new file mode 100644
--- /dev/null
+++ b/cache/src/Engines/CacheVoid.php
@@ -0,0 +1,69 @@
+<?php
+declare(strict_types=1);
+
+namespace Keruald\Cache\Engines;
+
+use Keruald\Cache\Cache;
+
+use DateInterval;
+
+/**
+ * "blackhole" void cache
+ *
+ * This class doesn't cache information, it's void wrapper
+ * get will always return null
+ * set and delete do nothing
+ *
+ * It will be used by default if no cache is specified.
+ */
+class CacheVoid extends Cache {
+
+ static function load ($config) : self {
+ return new static;
+ }
+
+ function get (string $key, mixed $default = null) : mixed {
+ return $default;
+ }
+
+ function set (
+ string $key,
+ mixed $value,
+ null|int|DateInterval $ttl = null,
+ ) : bool {
+ return true;
+ }
+
+ function delete (string $key) : bool {
+ return true;
+ }
+
+ public function clear () : bool {
+ return true;
+ }
+
+ public function has (string $key) : bool {
+ return false;
+ }
+
+ public function getMultiple (
+ iterable $keys,
+ mixed $default = null
+ ) : iterable {
+ foreach ($keys as $key) {
+ yield $key => $default;
+ }
+ }
+
+ public function setMultiple (
+ iterable $values,
+ null|int|DateInterval $ttl = null,
+ ) : bool {
+ return true;
+ }
+
+ public function deleteMultiple (iterable $keys) : bool {
+ return true;
+ }
+
+}
diff --git a/cache/src/Exceptions/CacheException.php b/cache/src/Exceptions/CacheException.php
new file mode 100644
--- /dev/null
+++ b/cache/src/Exceptions/CacheException.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Keruald\Cache\Exceptions;
+
+use Psr\SimpleCache\CacheException as CacheExceptionInterface;
+
+use RuntimeException;
+
+class CacheException extends RuntimeException implements CacheExceptionInterface {
+
+}
diff --git a/cache/src/Features/WithPrefix.php b/cache/src/Features/WithPrefix.php
new file mode 100644
--- /dev/null
+++ b/cache/src/Features/WithPrefix.php
@@ -0,0 +1,58 @@
+<?php
+declare(strict_types=1);
+
+namespace Keruald\Cache\Features;
+
+use InvalidArgumentException;
+
+trait WithPrefix {
+
+ ///
+ /// Properties
+ ///
+
+ private string $prefix = "";
+
+ ///
+ /// Getters and setters
+ ///
+
+ public function getPrefix () : string {
+ if ($this->prefix === "") {
+ throw new InvalidArgumentException("This cache doesn't use prefix");
+ }
+
+ return $this->prefix;
+ }
+
+ protected function getUnsafePrefix () : string {
+ return $this->prefix;
+ }
+
+ public function hasPrefix () : bool {
+ return $this->prefix !== "";
+ }
+
+ /**
+ * Allows to share ab instance with several applications
+ * by prefixing the keys.
+ *
+ * @throws InvalidArgumentException
+ */
+ public function setPrefix (string $prefix) : self {
+ if ($prefix === "") {
+ throw new InvalidArgumentException("Prefix must be a non-empty string");
+ }
+
+ $this->prefix = $prefix;
+
+ return $this;
+ }
+
+ public function clearPrefix () : self {
+ $this->prefix = "";
+
+ return $this;
+ }
+
+}
diff --git a/cache/tests/CacheDummy.php b/cache/tests/CacheDummy.php
new file mode 100644
--- /dev/null
+++ b/cache/tests/CacheDummy.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Keruald\Cache\Tests;
+
+use Keruald\Cache\Engines\CacheVoid;
+
+class CacheDummy extends CacheVoid {
+
+}
diff --git a/cache/tests/CacheFactoryTest.php b/cache/tests/CacheFactoryTest.php
new file mode 100644
--- /dev/null
+++ b/cache/tests/CacheFactoryTest.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Keruald\Cache\Tests;
+
+use Keruald\Cache\CacheFactory;
+use Keruald\Cache\Engines\CacheVoid;
+
+use PHPUnit\Framework\TestCase;
+use Psr\SimpleCache\CacheException;
+
+class CacheFactoryTest extends TestCase {
+
+ public function testLoad () {
+ $config = [
+ "engine" => CacheDummy::class,
+ ];
+ $cache = CacheFactory::load($config);
+
+ $this->assertInstanceOf(CacheDummy::class, $cache);
+ }
+
+ public function testLoadDefaultsToVoid () {
+ $cache = CacheFactory::load([]);
+
+ $this->assertInstanceOf(CacheVoid::class, $cache);
+ }
+
+ public function testLoadWithNonExistentClass () {
+ $config = [
+ "engine" => "Acme\\Nonexistent",
+ ];
+
+ $this->expectException(CacheException::class);
+ CacheFactory::load($config);
+ }
+
+}
diff --git a/cache/tests/Engines/CacheMemcachedTest.php b/cache/tests/Engines/CacheMemcachedTest.php
new file mode 100644
--- /dev/null
+++ b/cache/tests/Engines/CacheMemcachedTest.php
@@ -0,0 +1,102 @@
+<?php
+
+namespace Keruald\Cache\Tests\Engines;
+
+use Keruald\Cache\Engines\CacheMemcached;
+use Keruald\OmniTools\Collections\HashMap;
+
+use Keruald\OmniTools\Network\SocketAddress;
+use PHPUnit\Framework\TestCase;
+
+use Memcached;
+
+class CacheMemcachedTest extends TestCase {
+
+ private CacheMemcached $cache;
+
+ protected function setUp () : void {
+
+ if (!extension_loaded("memcached")) {
+ $this->markTestSkipped("Memcached extension is required to test.");
+ }
+
+ if (!SocketAddress::from("127.0.0.1", 11211)->isOpen()) {
+ $this->markTestSkipped("Memcached server can't be reached.");
+ }
+
+ $memcached = new Memcached();
+ $memcached->addServer("127.0.0.1", 11211);
+
+ $this->cache = new CacheMemcached($memcached);
+ }
+
+ public function testSet () {
+ $result = $this->cache->set("foo", "bar");
+
+ $this->assertTrue($result);
+ }
+
+ public function testGet () {
+ $this->cache->set("foo", "bar");
+
+ $this->assertEquals("bar", $this->cache->get("foo"));
+ }
+
+ public function testHas () {
+ $result = $this->cache->set("foo", "bar");
+
+ $this->assertTrue($this->cache->has("foo"));
+ }
+
+ public function testDelete () {
+ $this->cache->set("foo", "bar");
+ $result = $this->cache->delete("foo");
+
+ $this->assertTrue($result);
+ }
+
+ public function testClear () {
+ $result = $this->cache->clear();
+
+ $this->assertTrue($result);
+ }
+
+ public function testGetMultiple () {
+ $expected = [
+ "foo" => "bar",
+ "bar" => "baz",
+ ];
+
+ $this->cache->set("foo", "bar");
+ $this->cache->set("bar", "baz");
+
+ $results = $this->cache->getMultiple(["foo", "bar"]);
+ $results = HashMap::from($results)->toArray();
+
+ $this->assertEquals($expected, $results);
+ }
+
+ public function testDeleteMultiple () {
+ $this->cache->set("foo", "bar");
+ $this->cache->set("bar", "baz");
+
+ $result = $this->cache->deleteMultiple(["foo", "bar"]);
+
+ $this->assertTrue($result);
+ }
+
+ public function testSetMultiple () {
+ $result = $this->cache->setMultiple([
+ "foo" => "bar",
+ "bar" => "baz",
+ ]);
+
+ $this->assertTrue($result);
+ }
+
+ public function testLoad () {
+ $cache = CacheMemcached::load([]);
+
+ $this->assertInstanceOf(CacheMemcached::class, $cache);
+ }
+}
diff --git a/cache/tests/Engines/CacheRedisTest.php b/cache/tests/Engines/CacheRedisTest.php
new file mode 100644
--- /dev/null
+++ b/cache/tests/Engines/CacheRedisTest.php
@@ -0,0 +1,102 @@
+<?php
+
+namespace Keruald\Cache\Tests\Engines;
+
+use Keruald\Cache\Engines\CacheRedis;
+use Keruald\OmniTools\Collections\HashMap;
+
+use Keruald\OmniTools\Network\SocketAddress;
+use PHPUnit\Framework\TestCase;
+
+use Redis;
+
+class CacheRedisTest extends TestCase {
+
+ private CacheRedis $cache;
+
+ protected function setUp () : void {
+
+ if (!extension_loaded("redis")) {
+ $this->markTestSkipped("Redis extension is required to test.");
+ }
+
+ if (!SocketAddress::from("127.0.0.1", 6379)->isOpen()) {
+ $this->markTestSkipped("Redis server can't be reached.");
+ }
+
+ $Redis = new Redis();
+ $Redis->connect("127.0.0.1", 6379);
+
+ $this->cache = new CacheRedis($Redis);
+ }
+
+ public function testSet () {
+ $result = $this->cache->set("foo", "bar");
+
+ $this->assertTrue($result);
+ }
+
+ public function testGet () {
+ $this->cache->set("foo", "bar");
+
+ $this->assertEquals("bar", $this->cache->get("foo"));
+ }
+
+ public function testHas () {
+ $result = $this->cache->set("foo", "bar");
+
+ $this->assertTrue($this->cache->has("foo"));
+ }
+
+ public function testDelete () {
+ $this->cache->set("foo", "bar");
+ $result = $this->cache->delete("foo");
+
+ $this->assertTrue($result);
+ }
+
+ public function testClear () {
+ $result = $this->cache->clear();
+
+ $this->assertTrue($result);
+ }
+
+ public function testGetMultiple () {
+ $expected = [
+ "foo" => "bar",
+ "bar" => "baz",
+ ];
+
+ $this->cache->set("foo", "bar");
+ $this->cache->set("bar", "baz");
+
+ $results = $this->cache->getMultiple(["foo", "bar"]);
+ $results = HashMap::from($results)->toArray();
+
+ $this->assertEquals($expected, $results);
+ }
+
+ public function testDeleteMultiple () {
+ $this->cache->set("foo", "bar");
+ $this->cache->set("bar", "baz");
+
+ $result = $this->cache->deleteMultiple(["foo", "bar"]);
+
+ $this->assertTrue($result);
+ }
+
+ public function testSetMultiple () {
+ $result = $this->cache->setMultiple([
+ "foo" => "bar",
+ "bar" => "baz",
+ ]);
+
+ $this->assertTrue($result);
+ }
+
+ public function testLoad () {
+ $cache = CacheRedis::load([]);
+
+ $this->assertInstanceOf(CacheRedis::class, $cache);
+ }
+}
diff --git a/cache/tests/Engines/CacheVoidTest.php b/cache/tests/Engines/CacheVoidTest.php
new file mode 100644
--- /dev/null
+++ b/cache/tests/Engines/CacheVoidTest.php
@@ -0,0 +1,78 @@
+<?php
+
+namespace Keruald\Cache\Tests\Engines;
+
+use Keruald\Cache\Engines\CacheVoid;
+use Keruald\OmniTools\Collections\HashMap;
+
+use PHPUnit\Framework\TestCase;
+
+class CacheVoidTest extends TestCase {
+
+ private CacheVoid $cache;
+
+ protected function setUp () : void {
+ $this->cache = new CacheVoid;
+ }
+
+ public function testSet () {
+ $result = $this->cache->set("foo", "bar");
+
+ $this->assertTrue($result);
+ }
+
+ public function testDelete () {
+ $result = $this->cache->delete("foo");
+
+ $this->assertTrue($result);
+ }
+
+ public function testClear () {
+ $result = $this->cache->clear();
+
+ $this->assertTrue($result);
+ }
+
+ public function testGetMultiple () {
+ $expected = [
+ "foo" => null,
+ "bar" => null,
+ ];
+
+ $results = $this->cache->getMultiple(["foo", "bar"]);
+ $results = HashMap::from($results)->toArray();
+
+ $this->assertEquals($expected, $results);
+ }
+
+ public function testDeleteMultiple () {
+ $result = $this->cache->deleteMultiple(["foo", "bar"]);
+
+ $this->assertTrue($result);
+ }
+
+ public function testGet () {
+ $this->assertNull($this->cache->get("foo"));
+ }
+
+ public function testSetMultiple () {
+ $result = $this->cache->setMultiple([
+ "foo" => "bar",
+ "bar" => "baz",
+ ]);
+
+ $this->assertTrue($result);
+ }
+
+ public function testHas () {
+ $result = $this->cache->has("foo");
+
+ $this->assertFalse($result);
+ }
+
+ public function testLoad () {
+ $cache = CacheVoid::load([]);
+
+ $this->assertInstanceOf(CacheVoid::class, $cache);
+ }
+}
diff --git a/composer.json b/composer.json
--- a/composer.json
+++ b/composer.json
@@ -17,7 +17,11 @@
"name": "Keruald contributors"
}
],
+ "provide": {
+ "psr/simple-cache-implementation": "1.0|2.0|3.0"
+ },
"require": {
+ "psr/simple-cache": "^1.0|^2.0|^3.0",
"ext-intl": "*"
},
"require-dev": {
@@ -30,7 +34,12 @@
"symfony/yaml": "^6.0.3",
"squizlabs/php_codesniffer": "^3.6"
},
+ "suggest": {
+ "ext-memcached": "*",
+ "ext-redis": "*"
+ },
"replace": {
+ "keruald/cache": "0.1.0",
"keruald/commands": "0.0.1",
"keruald/database": "0.4.0",
"keruald/omnitools": "0.11.0",
@@ -38,6 +47,8 @@
},
"autoload": {
"psr-4": {
+ "Keruald\\Cache\\": "cache/src/",
+ "Keruald\\Cache\\Tests\\": "cache/tests/",
"Keruald\\Commands\\": "commands/src/",
"Keruald\\Commands\\Tests\\": "commands/tests/",
"Keruald\\Database\\": "database/src/",
diff --git a/metadata.yml b/metadata.yml
--- a/metadata.yml
+++ b/metadata.yml
@@ -22,12 +22,14 @@
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
packages:
+ - cache
- commands
- database
- omnitools
- report
packages_namespaces:
+ cache: Keruald\Cache
commands: Keruald\Commands
database: Keruald\Database
omnitools: Keruald\OmniTools

File Metadata

Mime Type
text/plain
Expires
Sat, Nov 23, 18:15 (15 h, 42 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2257976
Default Alt Text
D3233.id8303.diff (28 KB)

Event Timeline