]> git.mxchange.org Git - friendica.git/commitdiff
Introduce Key-Value Pair storage provider
authorPhilipp <admin@philipp.info>
Thu, 29 Dec 2022 16:52:04 +0000 (17:52 +0100)
committerPhilipp <admin@philipp.info>
Thu, 29 Dec 2022 20:53:56 +0000 (21:53 +0100)
src/Core/KeyValueStorage/Capabilities/ICanManageKeyValuePairs.php [new file with mode: 0644]
src/Core/KeyValueStorage/Exceptions/KeyValueStoragePersistenceException.php [new file with mode: 0644]
src/Core/KeyValueStorage/Type/AbstractKeyValueStorage.php [new file with mode: 0644]
src/Core/KeyValueStorage/Type/DBKeyValueStorage.php [new file with mode: 0644]
src/DI.php
static/dependencies.config.php
tests/src/Core/KeyValueStorage/DBKeyValueStorageTest.php [new file with mode: 0644]
tests/src/Core/KeyValueStorage/KeyValueStorageTest.php [new file with mode: 0644]

diff --git a/src/Core/KeyValueStorage/Capabilities/ICanManageKeyValuePairs.php b/src/Core/KeyValueStorage/Capabilities/ICanManageKeyValuePairs.php
new file mode 100644 (file)
index 0000000..11c8b77
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2022, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\KeyValueStorage\Capabilities;
+
+use Friendica\Core\KeyValueStorage\Exceptions\KeyValueStoragePersistenceException;
+
+/**
+ * Interface for Friendica specific Key-Value pair storage
+ */
+interface ICanManageKeyValuePairs extends \ArrayAccess
+{
+       /**
+        * Get a particular value from the KeyValue Storage
+        *
+        * @param string  $key           The key to query
+        *
+        * @return mixed Stored value or null if it does not exist
+        *
+        * @throws KeyValueStoragePersistenceException In case the persistence layer throws errors
+        *
+        */
+       public function get(string $key);
+
+       /**
+        * Sets a value for a given key
+        *
+        * Note: Please do not store booleans - convert to 0/1 integer values!
+        *
+        * @param string $key    The configuration key to set
+        * @param mixed  $value  The value to store
+        *
+        * @throws KeyValueStoragePersistenceException In case the persistence layer throws errors
+        */
+       public function set(string $key, $value): void;
+
+       /**
+        * Deletes the given key.
+        *
+        * @param string $key    The configuration key to delete
+        *
+        * @throws KeyValueStoragePersistenceException In case the persistence layer throws errors
+        *
+        */
+       public function delete(string $key): void;
+}
diff --git a/src/Core/KeyValueStorage/Exceptions/KeyValueStoragePersistenceException.php b/src/Core/KeyValueStorage/Exceptions/KeyValueStoragePersistenceException.php
new file mode 100644 (file)
index 0000000..ad04c33
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2022, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\KeyValueStorage\Exceptions;
+
+class KeyValueStoragePersistenceException extends \RuntimeException
+{
+       public function __construct($message = "", \Throwable $previous = null)
+       {
+               parent::__construct($message, 500, $previous);
+       }
+}
diff --git a/src/Core/KeyValueStorage/Type/AbstractKeyValueStorage.php b/src/Core/KeyValueStorage/Type/AbstractKeyValueStorage.php
new file mode 100644 (file)
index 0000000..cfcebb6
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2022, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\KeyValueStorage\Type;
+
+use Friendica\Core\KeyValueStorage\Capabilities\ICanManageKeyValuePairs;
+
+/**
+ * An abstract helper class for Key-Value storage classes
+ */
+abstract class AbstractKeyValueStorage implements ICanManageKeyValuePairs
+{
+       /** {@inheritDoc} */
+       public function get(string $key)
+       {
+               return $this->offsetGet($key);
+       }
+
+       /** {@inheritDoc} */
+       public function set(string $key, $value): void
+       {
+               $this->offsetSet($key, $value);
+       }
+
+       /** {@inheritDoc} */
+       public function delete(string $key): void
+       {
+               $this->offsetUnset($key);
+       }
+}
diff --git a/src/Core/KeyValueStorage/Type/DBKeyValueStorage.php b/src/Core/KeyValueStorage/Type/DBKeyValueStorage.php
new file mode 100644 (file)
index 0000000..1d7040c
--- /dev/null
@@ -0,0 +1,116 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2022, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\KeyValueStorage\Type;
+
+use Friendica\Core\Config\Util\ValueConversion;
+use Friendica\Core\KeyValueStorage\Exceptions\KeyValueStoragePersistenceException;
+use Friendica\Database\Database;
+
+/**
+ * A Key-Value storage provider with DB as persistence layer
+ */
+class DBKeyValueStorage extends AbstractKeyValueStorage
+{
+       const DB_KEY_VALUE_TABLE = 'key-value';
+
+       /** @var Database */
+       protected $database;
+
+       public function __construct(Database $database)
+       {
+               $this->database = $database;
+       }
+
+       /** {@inheritDoc} */
+       public function offsetExists($offset): bool
+       {
+               try {
+                       return $this->database->exists(self::DB_KEY_VALUE_TABLE, ['k' => $offset]);
+               } catch (\Exception $exception) {
+                       throw new KeyValueStoragePersistenceException(sprintf('Cannot check storage with key %s', $offset), $exception);
+               }
+       }
+
+       /** {@inheritDoc} */
+       #[\ReturnTypeWillChange]
+       public function offsetGet($offset)
+       {
+               try {
+                       $result = $this->database->selectFirst(self::DB_KEY_VALUE_TABLE, ['v'], ['k' => $offset]);
+
+                       if ($this->database->isResult($result)) {
+                               $value = ValueConversion::toConfigValue($result['v']);
+
+                               // just return it in case it is set
+                               if (isset($value)) {
+                                       return $value;
+                               }
+                       }
+               } catch (\Exception $exception) {
+                       throw new KeyValueStoragePersistenceException(sprintf('Cannot get value for key %s', $offset), $exception);
+               }
+
+               return null;
+       }
+
+       /** {@inheritDoc} */
+       #[\ReturnTypeWillChange]
+       public function offsetSet($offset, $value)
+       {
+               try {
+                       // We store our setting values in a string variable.
+                       // So we have to do the conversion here so that the compare below works.
+                       // The exception are array values.
+                       $compare_value = (!is_array($value) ? (string)$value : $value);
+                       $stored_value  = $this->get($offset);
+
+                       if (isset($stored_value) && ($stored_value === $compare_value)) {
+                               return;
+                       }
+
+                       $dbValue = ValueConversion::toDbValue($value);
+
+                       $return = $this->database->update(self::DB_KEY_VALUE_TABLE, ['v' => $dbValue], ['k' => $offset], true);
+
+                       if (!$return) {
+                               throw new \Exception(sprintf('database update failed: %s', $this->database->errorMessage()));
+                       }
+               } catch (\Exception $exception) {
+                       throw new KeyValueStoragePersistenceException(sprintf('Cannot set value for %s for key %s', $value, $offset), $exception);
+               }
+       }
+
+       /** {@inheritDoc} */
+       #[\ReturnTypeWillChange]
+       public function offsetUnset($offset)
+       {
+               try {
+                       $return = $this->database->delete(self::DB_KEY_VALUE_TABLE, ['k' => $offset]);
+
+                       if (!$return) {
+                               throw new \Exception(sprintf('database deletion failed: %s', $this->database->errorMessage()));
+                       }
+               } catch (\Exception $exception) {
+                       throw new KeyValueStoragePersistenceException(sprintf('Cannot delete value with key %s', $offset), $exception);
+               }
+       }
+}
index bf2b5a2371cfb9dc26d9b769888952bcc583e534..31f2511d4bf0c3b5f20df81a1abdb36755587093 100644 (file)
@@ -22,6 +22,7 @@
 namespace Friendica;
 
 use Dice\Dice;
+use Friendica\Core\KeyValueStorage\Capabilities\ICanManageKeyValuePairs;
 use Friendica\Core\Session\Capability\IHandleSessions;
 use Friendica\Core\Session\Capability\IHandleUserSessions;
 use Friendica\Navigation\SystemMessages;
@@ -181,6 +182,11 @@ abstract class DI
                return self::$dice->create(Core\Config\Capability\IManageConfigValues::class);
        }
 
+       public static function keyValue(): ICanManageKeyValuePairs
+       {
+               return self::$dice->create(Core\KeyValueStorage\Capabilities\ICanManageKeyValuePairs::class);
+       }
+
        /**
         * @return Core\PConfig\Capability\IManagePersonalConfigValues
         */
index a4c52e004303a39e409afbc2a5e7febaa04b21bf..645ab968eb6a32d497726e6d704aa5284c19b1e6 100644 (file)
@@ -245,6 +245,9 @@ return [
                        ['getBackend', [], Dice::CHAIN_CALL],
                ],
        ],
+       \Friendica\Core\KeyValueStorage\Capabilities\ICanManageKeyValuePairs::class => [
+               'instanceOf' => \Friendica\Core\KeyValueStorage\Type\DBKeyValueStorage::class,
+       ],
        Network\HTTPClient\Capability\ICanSendHttpRequests::class => [
                'instanceOf' => Network\HTTPClient\Factory\HttpClient::class,
                'call'       => [
diff --git a/tests/src/Core/KeyValueStorage/DBKeyValueStorageTest.php b/tests/src/Core/KeyValueStorage/DBKeyValueStorageTest.php
new file mode 100644 (file)
index 0000000..16fa9ab
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2022, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Test\src\Core\KeyValueStorage;
+
+use Friendica\Core\Config\ValueObject\Cache;
+use Friendica\Core\KeyValueStorage\Capabilities\ICanManageKeyValuePairs;
+use Friendica\Core\KeyValueStorage\Type\DBKeyValueStorage;
+use Friendica\Database\Definition\DbaDefinition;
+use Friendica\Database\Definition\ViewDefinition;
+use Friendica\Test\DatabaseTestTrait;
+use Friendica\Test\Util\Database\StaticDatabase;
+use Friendica\Util\BasePath;
+use Friendica\Util\Profiler;
+
+class DBKeyValueStorageTest extends KeyValueStorageTest
+{
+       use DatabaseTestTrait;
+
+       protected function setUp(): void
+       {
+               parent::setUp();
+
+               $this->setUpDb();
+       }
+
+       protected function tearDown(): void
+       {
+               parent::tearDown();
+
+               $this->tearDownDb();
+       }
+
+       public function getInstance(): ICanManageKeyValuePairs
+       {
+               $cache = new Cache();
+               $cache->set('database', 'disable_pdo', true);
+
+               $basePath = new BasePath(dirname(__FILE__, 5), $_SERVER);
+
+               $database = new StaticDatabase($cache, new Profiler($cache), (new DbaDefinition($basePath->getPath()))->load(), (new ViewDefinition($basePath->getPath()))->load());
+               $database->setTestmode(true);
+
+               return new DBKeyValueStorage($database);
+       }
+}
diff --git a/tests/src/Core/KeyValueStorage/KeyValueStorageTest.php b/tests/src/Core/KeyValueStorage/KeyValueStorageTest.php
new file mode 100644 (file)
index 0000000..6c39331
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2022, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Test\src\Core\KeyValueStorage;
+
+use Friendica\Core\KeyValueStorage\Capabilities\ICanManageKeyValuePairs;
+use Friendica\Test\MockedTest;
+
+abstract class KeyValueStorageTest extends MockedTest
+{
+       abstract public function getInstance(): ICanManageKeyValuePairs;
+
+       public function testInstance()
+       {
+               $instance = $this->getInstance();
+
+               self::assertInstanceOf(ICanManageKeyValuePairs::class, $instance);
+       }
+
+       public function dataTests(): array
+       {
+               return [
+                       'string'       => ['k' => 'data', 'v' => 'it'],
+                       'boolTrue'     => ['k' => 'data', 'v' => true],
+                       'boolFalse'    => ['k' => 'data', 'v' => false],
+                       'integer'      => ['k' => 'data', 'v' => 235],
+                       'decimal'      => ['k' => 'data', 'v' => 2.456],
+                       'array'        => ['k' => 'data', 'v' => ['1', 2, '3', true, false]],
+                       'boolIntTrue'  => ['k' => 'data', 'v' => 1],
+                       'boolIntFalse' => ['k' => 'data', 'v' => 0],
+               ];
+       }
+
+       /**
+        * @dataProvider dataTests
+        */
+       public function testGetSetDelete($k, $v)
+       {
+               $instance = $this->getInstance();
+
+               $instance->set($k, $v);
+
+               self::assertEquals($v, $instance->get($k));
+               self::assertEquals($v, $instance[$k]);
+
+               $instance->delete($k);
+
+               self::assertNull($instance->get($k));
+               self::assertNull($instance[$k]);
+       }
+
+       /**
+        * @dataProvider dataTests
+        */
+       public function testSetOverride($k, $v)
+       {
+               $instance = $this->getInstance();
+
+               $instance->set($k, $v);
+
+               self::assertEquals($v, $instance->get($k));
+               self::assertEquals($v, $instance[$k]);
+
+               $instance->set($k, 'another_value');
+
+               self::assertEquals('another_value', $instance->get($k));
+               self::assertEquals('another_value', $instance[$k]);
+       }
+
+       /**
+        * @dataProvider dataTests
+        */
+       public function testOffsetSetDelete($k, $v)
+       {
+               $instance = $this->getInstance();
+
+               $instance[$k] = $v;
+
+               self::assertEquals($v, $instance->get($k));
+               self::assertEquals($v, $instance[$k]);
+
+               unset($instance[$k]);
+
+               self::assertNull($instance->get($k));
+               self::assertNull($instance[$k]);
+       }
+}