From 9d98a4ce3a146e465fe0453aa71c8a84e6f007dc Mon Sep 17 00:00:00 2001
From: Philipp Holzer <admin+github@philipp.info>
Date: Mon, 15 Jul 2019 20:13:53 +0200
Subject: [PATCH] Refactor PConfiguration

---
 .../Adapter/AbstractDbaConfigAdapter.php      |  83 ---
 src/Core/Config/Adapter/IPConfigAdapter.php   |  85 ---
 src/Core/Config/Adapter/JITPConfigAdapter.php | 145 -----
 .../Config/Adapter/PreloadPConfigAdapter.php  | 142 -----
 src/Core/Config/JitPConfiguration.php         | 119 +++++
 src/Core/Config/PConfiguration.php            | 119 ++---
 src/Core/Config/PreloadPConfiguration.php     | 116 ++++
 src/Factory/ConfigFactory.php                 |  12 +-
 src/Factory/DependencyFactory.php             |   3 +-
 src/Model/Config/PConfig.php                  | 136 +++++
 tests/include/ApiTest.php                     |   4 +-
 .../src/Core/Config/JitPConfigurationTest.php | 154 ++++++
 tests/src/Core/Config/PConfigurationTest.php  | 502 +++++++++++++-----
 .../Core/Config/PreloadPConfigurationTest.php | 147 +++++
 tests/src/Database/DBATest.php                |   4 +-
 tests/src/Database/DBStructureTest.php        |   3 +-
 16 files changed, 1084 insertions(+), 690 deletions(-)
 delete mode 100644 src/Core/Config/Adapter/AbstractDbaConfigAdapter.php
 delete mode 100644 src/Core/Config/Adapter/IPConfigAdapter.php
 delete mode 100644 src/Core/Config/Adapter/JITPConfigAdapter.php
 delete mode 100644 src/Core/Config/Adapter/PreloadPConfigAdapter.php
 create mode 100644 src/Core/Config/JitPConfiguration.php
 create mode 100644 src/Core/Config/PreloadPConfiguration.php
 create mode 100644 src/Model/Config/PConfig.php
 create mode 100644 tests/src/Core/Config/JitPConfigurationTest.php
 create mode 100644 tests/src/Core/Config/PreloadPConfigurationTest.php

diff --git a/src/Core/Config/Adapter/AbstractDbaConfigAdapter.php b/src/Core/Config/Adapter/AbstractDbaConfigAdapter.php
deleted file mode 100644
index 38caf35cac..0000000000
--- a/src/Core/Config/Adapter/AbstractDbaConfigAdapter.php
+++ /dev/null
@@ -1,83 +0,0 @@
-<?php
-
-namespace Friendica\Core\Config\Adapter;
-
-use Friendica\Database\DBA;
-
-abstract class AbstractDbaConfigAdapter
-{
-	/**
-	 * The connection state of the adapter
-	 *
-	 * @var bool
-	 */
-	protected $connected = true;
-
-	public function __construct()
-	{
-		$this->connected = DBA::connected();
-	}
-
-	/**
-	 * Checks if the adapter is currently connected
-	 *
-	 * @return bool
-	 */
-	public function isConnected()
-	{
-		return $this->connected;
-	}
-
-	/**
-	 * Formats a DB value to a config value
-	 * - null   = The db-value isn't set
-	 * - bool   = The db-value is either '0' or '1'
-	 * - array  = The db-value is a serialized array
-	 * - string = The db-value is a string
-	 *
-	 * Keep in mind that there aren't any numeric/integer config values in the database
-	 *
-	 * @param null|string $value
-	 *
-	 * @return null|array|string
-	 */
-	protected function toConfigValue($value)
-	{
-		if (!isset($value)) {
-			return null;
-		}
-
-		switch (true) {
-			// manage array value
-			case preg_match("|^a:[0-9]+:{.*}$|s", $value):
-				return unserialize($value);
-
-			default:
-				return $value;
-		}
-	}
-
-	/**
-	 * Formats a config value to a DB value (string)
-	 *
-	 * @param mixed $value
-	 *
-	 * @return string
-	 */
-	protected function toDbValue($value)
-	{
-		// if not set, save an empty string
-		if (!isset($value)) {
-			return '';
-		}
-
-		switch (true) {
-			// manage arrays
-			case is_array($value):
-				return serialize($value);
-
-			default:
-				return (string)$value;
-		}
-	}
-}
diff --git a/src/Core/Config/Adapter/IPConfigAdapter.php b/src/Core/Config/Adapter/IPConfigAdapter.php
deleted file mode 100644
index c505532c59..0000000000
--- a/src/Core/Config/Adapter/IPConfigAdapter.php
+++ /dev/null
@@ -1,85 +0,0 @@
-<?php
-
-/*
- * To change this license header, choose License Headers in Project Properties.
- * To change this template file, choose Tools | Templates
- * and open the template in the editor.
- */
-
-namespace Friendica\Core\Config\Adapter;
-
-/**
- *
- * @author benlo
- */
-interface IPConfigAdapter
-{
-	/**
-	 * Loads all configuration values of a user's config family and returns the loaded category as an array.
-	 *
-	 * @param string $uid The user_id
-	 * @param string $cat The category of the configuration value
-	 *
-	 * @return array
-	 */
-	public function load($uid, $cat);
-
-	/**
-	 * Get a particular user's config variable given the category name
-	 * ($family) and a key.
-	 *
-	 * Note: Boolean variables are defined as 0/1 in the database
-	 *
-	 * @param string  $uid           The user_id
-	 * @param string  $cat           The category of the configuration value
-	 * @param string  $key           The configuration key to query
-	 *
-	 * @return null|mixed Stored value or null if it does not exist
-	 */
-	public function get($uid, $cat, $key);
-
-	/**
-	 * Stores a config value ($value) in the category ($family) under the key ($key)
-	 * for the user_id $uid.
-	 *
-	 * @note Please do not store booleans - convert to 0/1 integer values!
-	 *
-	 * @param string $uid   The user_id
-	 * @param string $cat   The category of the configuration value
-	 * @param string $key   The configuration key to set
-	 * @param string $value The value to store
-	 *
-	 * @return bool Operation success
-	 */
-	public function set($uid, $cat, $key, $value);
-
-	/**
-	 * Removes the configured value from the stored cache
-	 * and removes it from the database.
-	 *
-	 * @param string $uid The user_id
-	 * @param string $cat The category of the configuration value
-	 * @param string $key The configuration key to delete
-	 *
-	 * @return bool Operation success
-	 */
-	public function delete($uid, $cat, $key);
-
-	/**
-	 * Checks, if the current adapter is connected to the backend
-	 *
-	 * @return bool
-	 */
-	public function isConnected();
-
-	/**
-	 * Checks, if a config key ($key) in the category ($cat) is already loaded for the user_id $uid.
-	 *
-	 * @param string $uid The user_id
-	 * @param string $cat The configuration category
-	 * @param string $key The configuration key
-	 *
-	 * @return bool
-	 */
-	public function isLoaded($uid, $cat, $key);
-}
diff --git a/src/Core/Config/Adapter/JITPConfigAdapter.php b/src/Core/Config/Adapter/JITPConfigAdapter.php
deleted file mode 100644
index a0c6a9547f..0000000000
--- a/src/Core/Config/Adapter/JITPConfigAdapter.php
+++ /dev/null
@@ -1,145 +0,0 @@
-<?php
-namespace Friendica\Core\Config\Adapter;
-
-use Friendica\Database\DBA;
-
-/**
- * JustInTime User Configuration Adapter
- *
- * Default PConfig Adapter. Provides the best performance for pages loading few configuration variables.
- *
- * @author Hypolite Petovan <hypolite@mrpetovan.com>
- */
-class JITPConfigAdapter extends AbstractDbaConfigAdapter implements IPConfigAdapter
-{
-	private $in_db;
-
-	/**
-	 * {@inheritdoc}
-	 */
-	public function load($uid, $cat)
-	{
-		$return = [];
-
-		if (!$this->isConnected()) {
-			return $return;
-		}
-
-		$pconfigs = DBA::select('pconfig', ['v', 'k'], ['cat' => $cat, 'uid' => $uid]);
-		if (DBA::isResult($pconfigs)) {
-			while ($pconfig = DBA::fetch($pconfigs)) {
-				$key = $pconfig['k'];
-				$value = $this->toConfigValue($pconfig['v']);
-
-				// The value was in the db, so don't check it again (unless you have to)
-				$this->in_db[$uid][$cat][$key] = true;
-
-				if (isset($value)) {
-					$return[$key] = $value;
-				}
-			}
-		} else if ($cat != 'config') {
-			// Negative caching
-			$return = null;
-		}
-		DBA::close($pconfigs);
-
-		return [$cat => $return];
-	}
-
-	/**
-	 * {@inheritdoc}
-	 *
-	 * @param bool $mark if true, mark the selection of the current cat/key pair
-	 */
-	public function get($uid, $cat, $key, $mark = true)
-	{
-		if (!$this->isConnected()) {
-			return null;
-		}
-
-		// The value was in the db, so don't check it again (unless you have to)
-		if ($mark) {
-			$this->in_db[$uid][$cat][$key] = true;
-		}
-
-		$pconfig = DBA::selectFirst('pconfig', ['v'], ['uid' => $uid, 'cat' => $cat, 'k' => $key]);
-		if (DBA::isResult($pconfig)) {
-			$value = $this->toConfigValue($pconfig['v']);
-
-			if (isset($value)) {
-				return $value;
-			}
-		}
-
-		$this->in_db[$uid][$cat][$key] = false;
-		return null;
-	}
-
-	/**
-	 * {@inheritdoc}
-	 */
-	public function set($uid, $cat, $key, $value)
-	{
-		if (!$this->isConnected()) {
-			return false;
-		}
-
-		// 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($uid, $cat, $key, false);
-
-		if (!isset($this->in_db[$uid])) {
-			$this->in_db[$uid] = [];
-		}
-		if (!isset($this->in_db[$uid][$cat])) {
-			$this->in_db[$uid][$cat] = [];
-		}
-		if (!isset($this->in_db[$uid][$cat][$key])) {
-			$this->in_db[$uid][$cat][$key] = false;
-		}
-
-		if (isset($stored_value) && ($stored_value === $compare_value) && $this->in_db[$uid][$cat][$key]) {
-			return true;
-		}
-
-		// manage array value
-		$dbvalue = (is_array($value) ? serialize($value) : $value);
-
-		$result = DBA::update('pconfig', ['v' => $dbvalue], ['uid' => $uid, 'cat' => $cat, 'k' => $key], true);
-
-		$this->in_db[$uid][$cat][$key] = $result;
-
-		return $result;
-	}
-
-	/**
-	 * {@inheritdoc}
-	 */
-	public function delete($uid, $cat, $key)
-	{
-		if (!$this->isConnected()) {
-			return false;
-		}
-
-		if (isset($this->in_db[$uid][$cat][$key])) {
-			unset($this->in_db[$uid][$cat][$key]);
-		}
-
-		return DBA::delete('pconfig', ['uid' => $uid, 'cat' => $cat, 'k' => $key]);
-	}
-
-	/**
-	 * {@inheritdoc}
-	 */
-	public function isLoaded($uid, $cat, $key)
-	{
-		if (!$this->isConnected()) {
-			return false;
-		}
-
-		return (isset($this->in_db[$uid][$cat][$key])) && $this->in_db[$uid][$cat][$key];
-	}
-}
diff --git a/src/Core/Config/Adapter/PreloadPConfigAdapter.php b/src/Core/Config/Adapter/PreloadPConfigAdapter.php
deleted file mode 100644
index 838f3763df..0000000000
--- a/src/Core/Config/Adapter/PreloadPConfigAdapter.php
+++ /dev/null
@@ -1,142 +0,0 @@
-<?php
-
-namespace Friendica\Core\Config\Adapter;
-
-use Friendica\Database\DBA;
-
-/**
- * Preload User Configuration Adapter
- *
- * Minimizes the number of database queries to retrieve configuration values at the cost of memory.
- *
- * @author Hypolite Petovan <hypolite@mrpetovan.com>
- */
-class PreloadPConfigAdapter extends AbstractDbaConfigAdapter implements IPConfigAdapter
-{
-	/**
-	 * @var array true if config for user is loaded
-	 */
-	private $config_loaded;
-
-	/**
-	 * @param int $uid The UID of the current user
-	 */
-	public function __construct($uid = null)
-	{
-		parent::__construct();
-
-		$this->config_loaded = [];
-
-		if (isset($uid)) {
-			$this->load($uid, 'config');
-		}
-	}
-
-	/**
-	 * {@inheritdoc}
-	 */
-	public function load($uid, $cat)
-	{
-		$return = [];
-
-		if (empty($uid)) {
-			return $return;
-		}
-
-		if (!$this->isLoaded($uid, $cat, null)) {
-			return $return;
-		}
-
-		$pconfigs = DBA::select('pconfig', ['cat', 'v', 'k'], ['uid' => $uid]);
-		while ($pconfig = DBA::fetch($pconfigs)) {
-			$value = $this->toConfigValue($pconfig['v']);
-			if (isset($value)) {
-				$return[$pconfig['cat']][$pconfig['k']] = $value;
-			}
-		}
-		DBA::close($pconfigs);
-
-		$this->config_loaded[$uid] = true;
-
-		return $return;
-	}
-
-	/**
-	 * {@inheritdoc}
-	 */
-	public function get($uid, $cat, $key)
-	{
-		if (!$this->isConnected()) {
-			return null;
-		}
-
-		if (!$this->isLoaded($uid, $cat, $key)) {
-			$this->load($uid, $cat);
-		}
-
-		$config = DBA::selectFirst('pconfig', ['v'], ['uid' => $uid, 'cat' => $cat, 'k' => $key]);
-		if (DBA::isResult($config)) {
-			$value = $this->toConfigValue($config['v']);
-
-			if (isset($value)) {
-				return $value;
-			}
-		}
-		return null;
-	}
-
-	/**
-	 * {@inheritdoc}
-	 */
-	public function set($uid, $cat, $key, $value)
-	{
-		if (!$this->isConnected()) {
-			return false;
-		}
-
-		if (!$this->isLoaded($uid, $cat, $key)) {
-			$this->load($uid, $cat);
-		}
-		// We store our setting values as strings.
-		// 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($uid, $cat, $key);
-
-		if (isset($stored_value) && $stored_value === $compare_value) {
-			return true;
-		}
-
-		$dbvalue = $this->toDbValue($value);
-
-		return DBA::update('pconfig', ['v' => $dbvalue], ['uid' => $uid, 'cat' => $cat, 'k' => $key], true);
-	}
-
-	/**
-	 * {@inheritdoc}
-	 */
-	public function delete($uid, $cat, $key)
-	{
-		if (!$this->isConnected()) {
-			return false;
-		}
-
-		if (!$this->isLoaded($uid, $cat, $key)) {
-			$this->load($uid, $cat);
-		}
-
-		return DBA::delete('pconfig', ['uid' => $uid, 'cat' => $cat, 'k' => $key]);
-	}
-
-	/**
-	 * {@inheritdoc}
-	 */
-	public function isLoaded($uid, $cat, $key)
-	{
-		if (!$this->isConnected()) {
-			return false;
-		}
-
-		return isset($this->config_loaded[$uid]) && $this->config_loaded[$uid];
-	}
-}
diff --git a/src/Core/Config/JitPConfiguration.php b/src/Core/Config/JitPConfiguration.php
new file mode 100644
index 0000000000..8ad65eff1a
--- /dev/null
+++ b/src/Core/Config/JitPConfiguration.php
@@ -0,0 +1,119 @@
+<?php
+
+namespace Friendica\Core\Config;
+
+use Friendica\Model;
+
+/**
+ * This class implements the Just-In-Time configuration, which will cache
+ * user config values in a cache, once they are retrieved.
+ *
+ * Default Configuration type.
+ * Provides the best performance for pages loading few configuration variables.
+ */
+class JitPConfiguration extends PConfiguration
+{
+	/**
+	 * @var array Array of already loaded db values (even if there was no value)
+	 */
+	private $db_loaded;
+
+	/**
+	 * @param Cache\PConfigCache   $configCache The configuration cache
+	 * @param Model\Config\PConfig $configModel The configuration model
+	 */
+	public function __construct(Cache\PConfigCache $configCache, Model\Config\PConfig $configModel)
+	{
+		parent::__construct($configCache, $configModel);
+		$this->db_loaded = [];
+	}
+
+	/**
+	 * {@inheritDoc}
+	 *
+	 */
+	public function load(int $uid, string $cat = 'config')
+	{
+		// If not connected, do nothing
+		if (!$this->configModel->isConnected()) {
+			return;
+		}
+
+		$config = $this->configModel->load($uid, $cat);
+
+		if (!empty($config[$cat])) {
+			foreach ($config[$cat] as $key => $value) {
+				$this->db_loaded[$uid][$cat][$key] = true;
+			}
+		}
+
+		// load the whole category out of the DB into the cache
+		$this->configCache->load($uid, $config);
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public function get(int $uid, string $cat, string $key, $default_value = null, bool $refresh = false)
+	{
+		// if the value isn't loaded or refresh is needed, load it to the cache
+		if ($this->configModel->isConnected() &&
+		    (empty($this->db_loaded[$uid][$cat][$key]) ||
+		     $refresh)) {
+
+			$dbvalue = $this->configModel->get($uid, $cat, $key);
+
+			if (isset($dbvalue)) {
+				$this->configCache->set($uid, $cat, $key, $dbvalue);
+				unset($dbvalue);
+			}
+
+			$this->db_loaded[$uid][$cat][$key] = true;
+		}
+
+		// use the config cache for return
+		$result = $this->configCache->get($uid, $cat, $key);
+
+		return (isset($result)) ? $result : $default_value;
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public function set(int $uid, string $cat, string $key, $value)
+	{
+		// set the cache first
+		$cached = $this->configCache->set($uid, $cat, $key, $value);
+
+		// If there is no connected adapter, we're finished
+		if (!$this->configModel->isConnected()) {
+			return $cached;
+		}
+
+		$stored = $this->configModel->set($uid, $cat, $key, $value);
+
+		$this->db_loaded[$uid][$cat][$key] = $stored;
+
+		return $cached && $stored;
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public function delete(int $uid, string $cat, string $key)
+	{
+		$cacheRemoved = $this->configCache->delete($uid, $cat, $key);
+
+		if (isset($this->db_loaded[$uid][$cat][$key])) {
+			unset($this->db_loaded[$uid][$cat][$key]);
+		}
+
+		if (!$this->configModel->isConnected()) {
+			return $cacheRemoved;
+		}
+
+		$storeRemoved = $this->configModel->delete($uid, $cat, $key);
+
+		return $cacheRemoved || $storeRemoved;
+	}
+}
diff --git a/src/Core/Config/PConfiguration.php b/src/Core/Config/PConfiguration.php
index e69981c08c..0dd3a8d367 100644
--- a/src/Core/Config/PConfiguration.php
+++ b/src/Core/Config/PConfiguration.php
@@ -2,6 +2,8 @@
 
 namespace Friendica\Core\Config;
 
+use Friendica\Model;
+
 /**
  * This class is responsible for the user-specific configuration values in Friendica
  * The values are set through the Config-DB-Table (per Config-DB-adapter @see Adapter\IPConfigAdapter )
@@ -9,138 +11,101 @@ namespace Friendica\Core\Config;
  * The configuration cache (@see Cache\PConfigCache ) is used for temporary caching of database calls. This will
  * increase the performance.
  */
-class PConfiguration
+abstract class PConfiguration
 {
 	/**
 	 * @var Cache\PConfigCache
 	 */
-	private $configCache;
+	protected $configCache;
 
 	/**
-	 * @var Adapter\IPConfigAdapter
+	 * @var Model\Config\PConfig
 	 */
-	private $configAdapter;
+	protected $configModel;
 
 	/**
-	 * @param Cache\PConfigCache     $configCache   The configuration cache
-	 * @param Adapter\IPConfigAdapter $configAdapter The configuration DB-backend
+	 * @param Cache\PConfigCache   $configCache The configuration cache
+	 * @param Model\Config\PConfig $configModel The configuration model
 	 */
-	public function __construct(Cache\PConfigCache $configCache, Adapter\IPConfigAdapter $configAdapter)
+	public function __construct(Cache\PConfigCache $configCache, Model\Config\PConfig $configModel)
 	{
 		$this->configCache = $configCache;
-		$this->configAdapter = $configAdapter;
+		$this->configModel = $configModel;
 	}
 
 	/**
-	 * @brief Loads all configuration values of a user's config family into a cached storage.
+	 * Returns the Config Cache
+	 *
+	 * @return Cache\PConfigCache
+	 */
+	public function getCache()
+	{
+		return $this->configCache;
+	}
+
+	/**
+	 * Loads all configuration values of a user's config family into a cached storage.
 	 *
 	 * All configuration values of the given user are stored with the $uid in
-	 * the cache ( @see PConfigCache )
+	 * the cache ( @param int $uid The user_id
 	 *
-	 * @param string $uid The user_id
 	 * @param string $cat The category of the configuration value
 	 *
 	 * @return void
+	 * @see PConfigCache )
+	 *
 	 */
-	public function load($uid, $cat = 'config')
-	{
-		// If not connected, do nothing
-		if (!$this->configAdapter->isConnected()) {
-			return;
-		}
-
-		// load the whole category out of the DB into the cache
-		$this->configCache->load($uid, $this->configAdapter->load($uid, $cat));
-	}
+	abstract public function load(int $uid, string $cat = 'config');
 
 	/**
-	 * @brief Get a particular user's config variable given the category name
+	 * Get a particular user's config variable given the category name
 	 * ($cat) and a key.
 	 *
 	 * Get a particular user's config value from the given category ($cat)
 	 * and the $key with the $uid from a cached storage either from the $this->configAdapter
-	 * (@see IConfigAdapter ) or from the $this->configCache (@see PConfigCache ).
+	 * (@param int $uid The user_id
 	 *
-	 * @param string  $uid           The user_id
 	 * @param string  $cat           The category of the configuration value
 	 * @param string  $key           The configuration key to query
 	 * @param mixed   $default_value optional, The value to return if key is not set (default: null)
 	 * @param boolean $refresh       optional, If true the config is loaded from the db and not from the cache (default: false)
 	 *
 	 * @return mixed Stored value or null if it does not exist
+	 * @see IConfigAdapter ) or from the $this->configCache (@see PConfigCache ).
+	 *
 	 */
-	public function get($uid, $cat, $key, $default_value = null, $refresh = false)
-	{
-		// if the value isn't loaded or refresh is needed, load it to the cache
-		if ($this->configAdapter->isConnected() &&
-			(!$this->configAdapter->isLoaded($uid, $cat, $key) ||
-				$refresh)) {
-			$dbValue = $this->configAdapter->get($uid, $cat, $key);
-
-			if (isset($dbValue)) {
-				$this->configCache->set($uid, $cat, $key, $dbValue);
-				return $dbValue;
-			}
-		}
-
-		// use the config cache for return
-		$result = $this->configCache->get($uid, $cat, $key);
-		return (isset($result)) ? $result : $default_value;
-	}
+	abstract public function get(int $uid, string $cat, string $key, $default_value = null, bool $refresh = false);
 
 	/**
-	 * @brief Sets a configuration value for a user
+	 * Sets a configuration value for a user
 	 *
 	 * Stores a config value ($value) in the category ($family) under the key ($key)
 	 * for the user_id $uid.
 	 *
 	 * @note  Please do not store booleans - convert to 0/1 integer values!
 	 *
-	 * @param string $uid    The user_id
-	 * @param string $cat    The category of the configuration value
-	 * @param string $key    The configuration key to set
-	 * @param mixed  $value  The value to store
+	 * @param int    $uid   The user_id
+	 * @param string $cat   The category of the configuration value
+	 * @param string $key   The configuration key to set
+	 * @param mixed  $value The value to store
 	 *
 	 * @return bool Operation success
 	 */
-	public function set($uid, $cat, $key, $value)
-	{
-		// set the cache first
-		$cached = $this->configCache->set($uid, $cat, $key, $value);
-
-		// If there is no connected adapter, we're finished
-		if (!$this->configAdapter->isConnected()) {
-			return $cached;
-		}
-
-		$stored = $this->configAdapter->set($uid, $cat, $key, $value);
-
-		return $cached && $stored;
-	}
+	abstract public function set(int $uid, string $cat, string $key, $value);
 
 	/**
-	 * @brief Deletes the given key from the users's configuration.
+	 * Deletes the given key from the users's configuration.
 	 *
 	 * Removes the configured value from the stored cache in $this->configCache
-	 * (@see ConfigCache ) and removes it from the database (@see IConfigAdapter )
-	 * with the given $uid.
+	 * (@param int $uid The user_id
 	 *
-	 * @param string $uid The user_id
 	 * @param string $cat The category of the configuration value
 	 * @param string $key The configuration key to delete
 	 *
 	 * @return bool
+	 * @see ConfigCache ) and removes it from the database (@see IConfigAdapter )
+	 *      with the given $uid.
+	 *
 	 */
-	public function delete($uid, $cat, $key)
-	{
-		$cacheRemoved = $this->configCache->delete($uid, $cat, $key);
-
-		if (!$this->configAdapter->isConnected()) {
-			return $cacheRemoved;
-		}
-
-		$storeRemoved = $this->configAdapter->delete($uid, $cat, $key);
-
-		return $cacheRemoved || $storeRemoved;
-	}
+	abstract public function delete(int $uid, string $cat, string $key);
 }
diff --git a/src/Core/Config/PreloadPConfiguration.php b/src/Core/Config/PreloadPConfiguration.php
new file mode 100644
index 0000000000..1682b7e21b
--- /dev/null
+++ b/src/Core/Config/PreloadPConfiguration.php
@@ -0,0 +1,116 @@
+<?php
+
+namespace Friendica\Core\Config;
+
+use Friendica\Model;
+
+/**
+ * This class implements the preload Time configuration, which will cache
+ * all user config values per call in a cache.
+ *
+ * Minimizes the number of database queries to retrieve configuration values at the cost of memory.
+ */
+class PreloadPConfiguration extends PConfiguration
+{
+	/** @var array */
+	private $config_loaded;
+
+	/**
+	 * @param Cache\PConfigCache   $configCache The configuration cache
+	 * @param Model\Config\PConfig $configModel The configuration model
+	 */
+	public function __construct(Cache\PConfigCache $configCache, Model\Config\PConfig $configModel)
+	{
+		parent::__construct($configCache, $configModel);
+		$this->config_loaded = [];
+	}
+
+	/**
+	 * {@inheritDoc}
+	 *
+	 * This loads all config values everytime load is called
+	 *
+	 */
+	public function load(int $uid, string $cat = 'config')
+	{
+		// Don't load the whole configuration twice
+		if (!empty($this->config_loaded[$uid])) {
+			return;
+		}
+
+		// If not connected, do nothing
+		if (!$this->configModel->isConnected()) {
+			return;
+		}
+
+		$config                    = $this->configModel->load($uid);
+		$this->config_loaded[$uid] = true;
+
+		// load the whole category out of the DB into the cache
+		$this->configCache->load($uid, $config);
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public function get(int $uid, string $cat, string $key, $default_value = null, bool $refresh = false)
+	{
+		if (empty($this->config_loaded[$uid])) {
+			$this->load($uid);
+		} elseif ($refresh) {
+			if ($this->configModel->isConnected()) {
+				$config = $this->configModel->get($uid, $cat, $key);
+				if (isset($config)) {
+					$this->configCache->set($uid, $cat, $key, $config);
+				}
+			}
+		}
+
+		// use the config cache for return
+		$result = $this->configCache->get($uid, $cat, $key);
+
+		return (isset($result)) ? $result : $default_value;
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public function set(int $uid, string $cat, string $key, $value)
+	{
+		if (empty($this->config_loaded[$uid])) {
+			$this->load($uid);
+		}
+
+		// set the cache first
+		$cached = $this->configCache->set($uid, $cat, $key, $value);
+
+		// If there is no connected adapter, we're finished
+		if (!$this->configModel->isConnected()) {
+			return $cached;
+		}
+
+		$stored = $this->configModel->set($uid, $cat, $key, $value);
+
+		return $cached && $stored;
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public function delete(int $uid, string $cat, string $key)
+	{
+		if (empty($this->config_loaded[$uid])) {
+			$this->load($uid);
+		}
+
+		$cacheRemoved = $this->configCache->delete($uid, $cat, $key);
+
+		if (!$this->configModel->isConnected()) {
+			return $cacheRemoved;
+		}
+
+		$storeRemoved = $this->configModel->delete($uid, $cat, $key);
+
+		return $cacheRemoved || $storeRemoved;
+	}
+}
diff --git a/src/Factory/ConfigFactory.php b/src/Factory/ConfigFactory.php
index 5a24f86283..559411d623 100644
--- a/src/Factory/ConfigFactory.php
+++ b/src/Factory/ConfigFactory.php
@@ -4,9 +4,9 @@ namespace Friendica\Factory;
 
 use Friendica\Core;
 use Friendica\Core\Config;
-use Friendica\Core\Config\Adapter;
 use Friendica\Core\Config\Cache;
 use Friendica\Model\Config\Config as ConfigModel;
+use Friendica\Model\Config\PConfig as PConfigModel;
 use Friendica\Util\Config\ConfigFileLoader;
 
 class ConfigFactory
@@ -48,20 +48,18 @@ class ConfigFactory
 	/**
 	 * @param Cache\ConfigCache $configCache The config cache
 	 * @param Cache\PConfigCache  $pConfigCache The personal config cache
-	 * @param int                $uid         The UID of the current user
+	 * @param PConfigModel $configModel The configuration model
 	 *
 	 * @return Config\PConfiguration
 	 */
-	public static function createPConfig(Cache\ConfigCache $configCache, Cache\PConfigCache $pConfigCache, $uid = null)
+	public static function createPConfig(Cache\ConfigCache $configCache, Cache\PConfigCache $pConfigCache, PConfigModel $configModel)
 	{
 		if ($configCache->get('system', 'config_adapter') === 'preload') {
-			$configAdapter = new Adapter\PreloadPConfigAdapter($uid);
+			$configuration = new Config\PreloadPConfiguration($pConfigCache, $configModel);
 		} else {
-			$configAdapter = new Adapter\JITPConfigAdapter();
+			$configuration = new Config\JitPConfiguration($pConfigCache, $configModel);
 		}
 
-		$configuration = new Config\PConfiguration($pConfigCache, $configAdapter);
-
 		// Set the config in the static container for legacy usage
 		Core\PConfig::init($configuration);
 
diff --git a/src/Factory/DependencyFactory.php b/src/Factory/DependencyFactory.php
index 36ab20a013..d444f5d2f3 100644
--- a/src/Factory/DependencyFactory.php
+++ b/src/Factory/DependencyFactory.php
@@ -34,7 +34,8 @@ class DependencyFactory
 		$configModel = new \Friendica\Model\Config\Config($database);
 		$config = Factory\ConfigFactory::createConfig($configCache, $configModel);
 		// needed to call PConfig::init()
-		Factory\ConfigFactory::createPConfig($configCache, new PConfigCache());
+		$pconfigModel = new \Friendica\Model\Config\PConfig($database);
+		Factory\ConfigFactory::createPConfig($configCache, new PConfigCache(), $pconfigModel);
 		$logger = Factory\LoggerFactory::create($channel, $database, $config, $profiler);
 		Factory\LoggerFactory::createDev($channel, $config, $profiler);
 		$baseURL = new BaseURL($config, $_SERVER);
diff --git a/src/Model/Config/PConfig.php b/src/Model/Config/PConfig.php
new file mode 100644
index 0000000000..c76e41cb6c
--- /dev/null
+++ b/src/Model/Config/PConfig.php
@@ -0,0 +1,136 @@
+<?php
+
+namespace Friendica\Model\Config;
+
+
+/**
+ * The Config model backend for users, which is using the general DB-model backend for user-configs
+ */
+class PConfig extends DbaConfig
+{
+	/**
+	 * Loads all configuration values and returns the loaded category as an array.
+	 *
+	 * @param int         $uid The id of the user to load
+	 * @param string|null $cat The category of the configuration values to load
+	 *
+	 * @return array The config array
+	 *
+	 * @throws \Exception In case DB calls are invalid
+	 */
+	public function load(int $uid, string $cat = null)
+	{
+		$return = [];
+
+		if (empty($cat)) {
+			$configs = $this->dba->select('pconfig', ['cat', 'v', 'k'], ['uid' => $uid]);
+		} else {
+			$configs = $this->dba->select('üconfig', ['cat', 'v', 'k'], ['cat' => $cat, 'uid' => $uid]);
+		}
+
+		while ($config = $this->dba->fetch($configs)) {
+
+			$key   = $config['k'];
+			$value = $this->toConfigValue($config['v']);
+
+			// just save it in case it is set
+			if (isset($value)) {
+				$return[$config['cat']][$key] = $value;
+			}
+		}
+		$this->dba->close($configs);
+
+		return $return;
+	}
+
+	/**
+	 * Get a particular user config variable out of the DB with the
+	 * given category name ($cat) and a key ($key).
+	 *
+	 * Note: Boolean variables are defined as 0/1 in the database
+	 *
+	 * @param int         $uid The id of the user to load
+	 * @param string $cat The category of the configuration value
+	 * @param string $key The configuration key to query
+	 *
+	 * @return array|string|null Stored value or null if it does not exist
+	 *
+	 * @throws \Exception In case DB calls are invalid
+	 */
+	public function get(int $uid, string $cat, string $key)
+	{
+		if (!$this->isConnected()) {
+			return null;
+		}
+
+		$config = $this->dba->selectFirst('pconfig', ['v'], ['uid' => $uid, 'cat' => $cat, 'k' => $key]);
+		if ($this->dba->isResult($config)) {
+			$value = $this->toConfigValue($config['v']);
+
+			// just return it in case it is set
+			if (isset($value)) {
+				return $value;
+			}
+		}
+
+		return null;
+	}
+
+	/**
+	 * Stores a config value ($value) in the category ($cat) under the key ($key) for a
+	 * given user ($uid).
+	 *
+	 * Note: Please do not store booleans - convert to 0/1 integer values!
+	 *
+	 * @param int    $uid   The id of the user to load
+	 * @param string $cat   The category of the configuration value
+	 * @param string $key   The configuration key to set
+	 * @param mixed  $value The value to store
+	 *
+	 * @return bool Operation success
+	 *
+	 * @throws \Exception In case DB calls are invalid
+	 */
+	public function set(int $uid, string $cat, string $key, $value)
+	{
+		if (!$this->isConnected()) {
+			return false;
+		}
+
+		// 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($uid, $cat, $key);
+
+		if (isset($stored_value) && ($stored_value === $compare_value)) {
+			return true;
+		}
+
+		$dbvalue = $this->toDbValue($value);
+
+		$result = $this->dba->update('pconfig', ['v' => $dbvalue], ['uid' => $uid, 'cat' => $cat, 'k' => $key], true);
+
+		return $result;
+	}
+
+	/**
+	 * Removes the configured value of the given user.
+	 *
+	 * @param int    $uid The id of the user to load
+	 * @param string $cat The category of the configuration value
+	 * @param string $key The configuration key to delete
+	 *
+	 * @return bool Operation success
+	 *
+	 * @throws \Exception In case DB calls are invalid
+	 */
+	public function delete(int $uid, string $cat, string $key)
+	{
+		if (!$this->isConnected()) {
+			return false;
+		}
+
+		return $this->dba->delete('pconfig', ['uid' => $uid, 'cat' => $cat, 'k' => $key]);
+	}
+}
diff --git a/tests/include/ApiTest.php b/tests/include/ApiTest.php
index 5ae4bdff50..902445baa2 100644
--- a/tests/include/ApiTest.php
+++ b/tests/include/ApiTest.php
@@ -7,6 +7,7 @@ namespace Friendica\Test;
 
 use Friendica\App;
 use Friendica\Core\Config;
+use Friendica\Core\Config\Cache\PConfigCache;
 use Friendica\Core\PConfig;
 use Friendica\Core\Protocol;
 use Friendica\Core\System;
@@ -49,7 +50,8 @@ class ApiTest extends DatabaseTest
 	{
 		$configModel = new \Friendica\Model\Config\Config(self::$dba);
 		$config = Factory\ConfigFactory::createConfig(self::$configCache, $configModel);
-		Factory\ConfigFactory::createPConfig(self::$configCache, new Config\Cache\PConfigCache());
+		$pconfigModel = new \Friendica\Model\Config\PConfig(self::$dba);
+		Factory\ConfigFactory::createPConfig(self::$configCache, new PConfigCache(), $pconfigModel);
 		$logger = Factory\LoggerFactory::create('test', self::$dba, $config, self::$profiler);
 		$baseUrl = new BaseURL($config, $_SERVER);
 		$router = new App\Router();
diff --git a/tests/src/Core/Config/JitPConfigurationTest.php b/tests/src/Core/Config/JitPConfigurationTest.php
new file mode 100644
index 0000000000..4eafb43b3d
--- /dev/null
+++ b/tests/src/Core/Config/JitPConfigurationTest.php
@@ -0,0 +1,154 @@
+<?php
+
+namespace Friendica\Test\src\Core\Config;
+
+use Friendica\Core\Config\JitPConfiguration;
+
+class JitPConfigurationTest extends PConfigurationTest
+{
+	public function getInstance()
+	{
+		return new JitPConfiguration($this->configCache, $this->configModel);
+	}
+
+	/**
+	 * @dataProvider dataConfigLoad
+	 */
+	public function testLoad(int $uid, array $data, array $possibleCats, array $load)
+	{
+		$this->configModel->shouldReceive('isConnected')
+		                  ->andReturn(true)
+		                  ->times(count($load));
+
+		foreach ($load as $loadCat) {
+			$this->configModel->shouldReceive('load')
+			                  ->with($uid, $loadCat)
+			                  ->andReturn([$loadCat => $data[$loadCat]])
+			                  ->once();
+		}
+
+		parent::testLoad($uid, $data, $possibleCats, $load);
+	}
+
+	/**
+	 * @dataProvider dataDoubleLoad
+	 */
+	public function testCacheLoadDouble(int $uid, array $data1, array $data2, array $expect)
+	{
+		$this->configModel->shouldReceive('isConnected')
+		                  ->andReturn(true)
+		                  ->times(count($data1) + count($data2));
+
+		foreach ($data1 as $cat => $data) {
+			$this->configModel->shouldReceive('load')
+			                  ->with($uid, $cat)
+			                  ->andReturn([$cat => $data])
+			                  ->once();
+		}
+
+
+		foreach ($data2 as $cat => $data) {
+			$this->configModel->shouldReceive('load')
+			                  ->with($uid, $cat)
+			                  ->andReturn([$cat => $data])
+			                  ->once();
+		}
+
+		parent::testCacheLoadDouble($uid, $data1, $data2, $expect);
+
+		// Assert the expected categories
+		foreach ($data2 as $cat => $data) {
+			$this->assertConfig($uid, $cat, $expect[$cat]);
+		}
+	}
+
+	/**
+	 * @dataProvider dataTests
+	 */
+	public function testSetGetWithoutDB(int $uid, $data)
+	{
+		$this->configModel->shouldReceive('isConnected')
+		                  ->andReturn(false)
+		                  ->times(2);
+
+		parent::testSetGetWithoutDB($uid, $data);
+	}
+
+	/**
+	 * @dataProvider dataTests
+	 */
+	public function testSetGetWithDB(int $uid, $data)
+	{
+		$this->configModel->shouldReceive('isConnected')
+		                  ->andReturn(true)
+		                  ->times(2);
+
+		parent::testSetGetWithDB($uid, $data);
+	}
+
+	/**
+	 * @dataProvider dataTests
+	 */
+	public function testGetWithRefresh(int $uid, $data)
+	{
+		$this->configModel->shouldReceive('isConnected')
+		                  ->andReturn(true)
+		                  ->times(3);
+
+		// mocking one get without result
+		$this->configModel->shouldReceive('get')
+		                  ->with($uid, 'test', 'it')
+		                  ->andReturn(null)
+		                  ->once();
+
+		// mocking the data get
+		$this->configModel->shouldReceive('get')
+		                  ->with($uid, 'test', 'it')
+		                  ->andReturn($data)
+		                  ->once();
+
+		// mocking second get
+		$this->configModel->shouldReceive('get')
+		                  ->with($uid, 'test', 'not')
+		                  ->andReturn(null)
+		                  ->once();
+
+		parent::testGetWithRefresh($uid, $data);
+	}
+
+	public function testGetWrongWithoutDB()
+	{
+		$this->configModel->shouldReceive('isConnected')
+		                  ->andReturn(false)
+		                  ->times(3);
+
+		parent::testGetWrongWithoutDB();
+	}
+
+	/**
+	 * @dataProvider dataTests
+	 */
+	public function testDeleteWithoutDB(int $uid, $data)
+	{
+		$this->configModel->shouldReceive('isConnected')
+		                  ->andReturn(false)
+		                  ->times(3);
+
+		parent::testDeleteWithoutDB($uid, $data);
+	}
+
+	public function testDeleteWithDB()
+	{
+		$this->configModel->shouldReceive('isConnected')
+		                  ->andReturn(true)
+		                  ->times(5);
+
+		// mocking one get without result
+		$this->configModel->shouldReceive('get')
+		                  ->with(0, 'test', 'it')
+		                  ->andReturn(null)
+		                  ->once();
+
+		parent::testDeleteWithDB();
+	}
+}
diff --git a/tests/src/Core/Config/PConfigurationTest.php b/tests/src/Core/Config/PConfigurationTest.php
index f2f1857a69..adc9f267a1 100644
--- a/tests/src/Core/Config/PConfigurationTest.php
+++ b/tests/src/Core/Config/PConfigurationTest.php
@@ -2,110 +2,294 @@
 
 namespace Friendica\Test\src\Core\Config;
 
-use Friendica\Core\Config\Adapter\IPConfigAdapter;
 use Friendica\Core\Config\Cache\PConfigCache;
 use Friendica\Core\Config\PConfiguration;
+use Friendica\Model\Config\PConfig as PConfigModel;
 use Friendica\Test\MockedTest;
+use Mockery;
+use Mockery\MockInterface;
 
-class PConfigurationTest extends MockedTest
+abstract class PConfigurationTest extends MockedTest
 {
+	/** @var PConfigModel|MockInterface */
+	protected $configModel;
+
+	/** @var PConfigCache */
+	protected $configCache;
+
+	/** @var PConfiguration */
+	protected $testedConfig;
+
+	/**
+	 * Assert a config tree
+	 *
+	 * @param int    $uid  The uid to assert
+	 * @param string $cat  The category to assert
+	 * @param array  $data The result data array
+	 */
+	protected function assertConfig(int $uid, string $cat, array $data)
+	{
+		$result = $this->testedConfig->getCache()->getAll();
+
+		$this->assertNotEmpty($result);
+		$this->assertArrayHasKey($uid, $result);
+		$this->assertArrayHasKey($cat, $result[$uid]);
+		$this->assertArraySubset($data, $result[$uid][$cat]);
+	}
+
+
+	protected function setUp()
+	{
+		parent::setUp();
+
+		// Create the config model
+		$this->configModel = Mockery::mock(PConfigModel::class);
+		$this->configCache = new PConfigCache();
+	}
+
+	/**
+	 * @return PConfiguration
+	 */
+	public abstract function getInstance();
+
 	public function dataTests()
 	{
 		return [
-			'string'       => ['data' => 'it'],
-			'boolTrue'     => ['data' => true],
-			'boolFalse'    => ['data' => false],
-			'integer'      => ['data' => 235],
-			'decimal'      => ['data' => 2.456],
-			'array'        => ['data' => ['1', 2, '3', true, false]],
-			'boolIntTrue'  => ['data' => 1],
-			'boolIntFalse' => ['Data' => 0],
+			'string'       => ['uid' => 1, 'data' => 'it'],
+			'boolTrue'     => ['uid' => 2, 'data' => true],
+			'boolFalse'    => ['uid' => 3, 'data' => false],
+			'integer'      => ['uid' => 4, 'data' => 235],
+			'decimal'      => ['uid' => 5, 'data' => 2.456],
+			'array'        => ['uid' => 6, 'data' => ['1', 2, '3', true, false]],
+			'boolIntTrue'  => ['uid' => 7, 'data' => 1],
+			'boolIntFalse' => ['uid' => 8, 'data' => 0],
 		];
 	}
 
+	public function dataConfigLoad()
+	{
+		$data = [
+			'system' => [
+				'key1' => 'value1',
+				'key2' => 'value2',
+				'key3' => 'value3',
+			],
+			'config' => [
+				'key1' => 'value1a',
+				'key4' => 'value4',
+			],
+			'other'  => [
+				'key5' => 'value5',
+				'key6' => 'value6',
+			],
+		];
+
+		return [
+			'system' => [
+				'uid' => 1,
+				'data'         => $data,
+				'possibleCats' => [
+					'system',
+					'config',
+					'other'
+				],
+				'load'         => [
+					'system',
+				],
+			],
+			'other'  => [
+				'uid' => 2,
+				'data'         => $data,
+				'possibleCats' => [
+					'system',
+					'config',
+					'other'
+				],
+				'load'         => [
+					'other',
+				],
+			],
+			'config' => [
+				'uid' => 3,
+				'data'         => $data,
+				'possibleCats' => [
+					'system',
+					'config',
+					'other'
+				],
+				'load'         => [
+					'config',
+				],
+			],
+			'all'    => [
+				'uid' => 4,
+				'data'         => $data,
+				'possibleCats' => [
+					'system',
+					'config',
+					'other'
+				],
+				'load'         => [
+					'system',
+					'config',
+					'other'
+				],
+			],
+		];
+	}
+
+	/**
+	 * Test the configuration initialization
+	 * @dataProvider dataConfigLoad
+	 */
+	public function testSetUp(int $uid, array $data)
+	{
+		$this->testedConfig = $this->getInstance();
+		$this->assertInstanceOf(PConfigCache::class, $this->testedConfig->getCache());
+
+		$this->assertEmpty($this->testedConfig->getCache()->getAll());
+	}
+
 	/**
 	 * Test the configuration load() method
 	 */
-	public function testCacheLoad()
+	public function testLoad(int $uid, array $data, array $possibleCats, array $load)
 	{
-		$uid = 234;
-		$configCache = new PConfigCache();
-		$configAdapter = \Mockery::mock(IPConfigAdapter::class);
-		$configAdapter->shouldReceive('isConnected')->andReturn(true)->twice();
-		// expected loading
-		$configAdapter->shouldReceive('load')
-			->with($uid, 'testing')
-			->andReturn(['testing' => ['test' => 'it']])
-			->once();
-		$configAdapter->shouldReceive('isLoaded')->with($uid, 'testing', 'test')->andReturn(true)->once();
+		$this->testedConfig = $this->getInstance();
+		$this->assertInstanceOf(PConfigCache::class, $this->testedConfig->getCache());
 
-		$configuration = new PConfiguration($configCache, $configAdapter);
-		$configuration->load($uid, 'testing');
+		foreach ($load as $loadedCats) {
+			$this->testedConfig->load($uid, $loadedCats);
+		}
 
-		$this->assertEquals('it', $configuration->get($uid, 'testing', 'test'));
+		// Assert at least loaded cats are loaded
+		foreach ($load as $loadedCats) {
+			$this->assertConfig($uid, $loadedCats, $data[$loadedCats]);
+		}
+	}
+
+	public function dataDoubleLoad()
+	{
+		return [
+			'config' => [
+				'uid' => 1,
+				'data1'  => [
+					'config' => [
+						'key1' => 'value1',
+						'key2' => 'value2',
+					],
+				],
+				'data2'  => [
+					'config' => [
+						'key1' => 'overwritten!',
+						'key3' => 'value3',
+					],
+				],
+				'expect' => [
+					'config' => [
+						// load should overwrite values everytime!
+						'key1' => 'overwritten!',
+						'key2' => 'value2',
+						'key3' => 'value3',
+					],
+				],
+			],
+			'other'  => [
+				'uid' => 1,
+				'data1'  => [
+					'config' => [
+						'key12' => 'data4',
+						'key45' => 7,
+					],
+					'other'  => [
+						'key1' => 'value1',
+						'key2' => 'value2',
+					],
+				],
+				'data2'  => [
+					'other'  => [
+						'key1' => 'overwritten!',
+						'key3' => 'value3',
+					],
+					'config' => [
+						'key45' => 45,
+						'key52' => true,
+					]
+				],
+				'expect' => [
+					'other'  => [
+						// load should overwrite values everytime!
+						'key1' => 'overwritten!',
+						'key2' => 'value2',
+						'key3' => 'value3',
+					],
+					'config' => [
+						'key12' => 'data4',
+						'key45' => 45,
+						'key52' => true,
+					],
+				],
+			],
+		];
 	}
 
 	/**
 	 * Test the configuration load() method with overwrite
 	 */
-	public function testCacheLoadDouble()
+	public function testCacheLoadDouble(int $uid, array $data1, array $data2, array $expect)
 	{
-		$uid = 234;
-		$configCache = new PConfigCache();
-		$configAdapter = \Mockery::mock(IPConfigAdapter::class);
-		$configAdapter->shouldReceive('isConnected')->andReturn(true)->times(4);
-		// expected loading
-		$configAdapter->shouldReceive('load')->with($uid, 'testing')->andReturn(['testing' => ['test' => 'it']])->once();
-		$configAdapter->shouldReceive('isLoaded')->with($uid, 'testing', 'test')->andReturn(true)->twice();
-		// expected next loading
-		$configAdapter->shouldReceive('load')->andReturn(['testing' => ['test' => 'again']])->once();
+		$this->testedConfig = $this->getInstance();
+		$this->assertInstanceOf(PConfigCache::class, $this->testedConfig->getCache());
 
-		$configuration = new PConfiguration($configCache, $configAdapter);
-		$configuration->load($uid, 'testing');
+		foreach ($data1 as $cat => $data) {
+			$this->testedConfig->load($uid, $cat);
+		}
 
-		$this->assertEquals('it', $configuration->get($uid, 'testing', 'test'));
+		// Assert at least loaded cats are loaded
+		foreach ($data1 as $cat => $data) {
+			$this->assertConfig($uid, $cat, $data);
+		}
 
-		$configuration->load($uid, 'testing');
-
-		$this->assertEquals('again', $configuration->get($uid, 'testing', 'test'));
+		foreach ($data2 as $cat => $data) {
+			$this->testedConfig->load($uid, $cat);
+		}
 	}
 
 	/**
 	 * Test the configuration get() and set() methods without adapter
+	 *
 	 * @dataProvider dataTests
 	 */
-	public function testSetGetWithoutDB($data)
+	public function testSetGetWithoutDB(int $uid, $data)
 	{
-		$uid = 234;
-		$configCache = new PConfigCache();
-		$configAdapter = \Mockery::mock(IPConfigAdapter::class);
-		$configAdapter->shouldReceive('isConnected')->andReturn(false)->times(2);
+		$this->testedConfig = $this->getInstance();
+		$this->assertInstanceOf(PConfigCache::class, $this->testedConfig->getCache());
 
-		$configuration = new PConfiguration($configCache, $configAdapter);
+		$this->assertTrue($this->testedConfig->set($uid, 'test', 'it', $data));
 
-		$this->assertTrue($configuration->set($uid, 'test', 'it', $data));
-
-		$this->assertEquals($data, $configuration->get($uid, 'test', 'it'));
+		$this->assertEquals($data, $this->testedConfig->get($uid, 'test', 'it'));
+		$this->assertEquals($data, $this->testedConfig->getCache()->get($uid, 'test', 'it'));
 	}
 
 	/**
-	 * Test the configuration get() and set() methods with adapter
+	 * Test the configuration get() and set() methods with a model/db
+	 *
 	 * @dataProvider dataTests
 	 */
-	public function testSetGetWithDB($data)
+	public function testSetGetWithDB(int $uid, $data)
 	{
-		$uid = 234;
-		$configCache = new PConfigCache();
-		$configAdapter = \Mockery::mock(IPConfigAdapter::class);
-		$configAdapter->shouldReceive('isConnected')->andReturn(true)->times(2);
-		$configAdapter->shouldReceive('isLoaded')->with($uid, 'test', 'it')->andReturn(true)->once();
-		$configAdapter->shouldReceive('set')->with($uid, 'test', 'it', $data)->andReturn(true)->once();
+		$this->configModel->shouldReceive('set')
+		                  ->with($uid, 'test', 'it', $data)
+		                  ->andReturn(true)
+		                  ->once();
 
-		$configuration = new PConfiguration($configCache, $configAdapter);
+		$this->testedConfig = $this->getInstance();
+		$this->assertInstanceOf(PConfigCache::class, $this->testedConfig->getCache());
 
-		$this->assertTrue($configuration->set($uid, 'test', 'it', $data));
+		$this->assertTrue($this->testedConfig->set($uid, 'test', 'it', $data));
 
-		$this->assertEquals($data, $configuration->get($uid, 'test', 'it'));
+		$this->assertEquals($data, $this->testedConfig->get($uid, 'test', 'it'));
+		$this->assertEquals($data, $this->testedConfig->getCache()->get($uid, 'test', 'it'));
 	}
 
 	/**
@@ -113,135 +297,159 @@ class PConfigurationTest extends MockedTest
 	 */
 	public function testGetWrongWithoutDB()
 	{
-		$uid = 234;
-		$configCache = new PConfigCache();
-		$configAdapter = \Mockery::mock(IPConfigAdapter::class);
-		$configAdapter->shouldReceive('isConnected')->andReturn(false)->times(3);
-
-		$configuration = new PConfiguration($configCache, $configAdapter);
+		$this->testedConfig = $this->getInstance();
+		$this->assertInstanceOf(PConfigCache::class, $this->testedConfig->getCache());
 
 		// without refresh
-		$this->assertNull($configuration->get($uid, 'test', 'it'));
+		$this->assertNull($this->testedConfig->get(0, 'test', 'it'));
+
+		/// beware that the cache returns '!<unset>!' and not null for a non existing value
+		$this->assertNull($this->testedConfig->getCache()->get(0, 'test', 'it'));
 
 		// with default value
-		$this->assertEquals('default', $configuration->get($uid, 'test', 'it', 'default'));
+		$this->assertEquals('default', $this->testedConfig->get(0, 'test', 'it', 'default'));
 
 		// with default value and refresh
-		$this->assertEquals('default', $configuration->get($uid, 'test', 'it', 'default', true));
+		$this->assertEquals('default', $this->testedConfig->get(0, 'test', 'it', 'default', true));
 	}
 
 	/**
 	 * Test the configuration get() method with refresh
+	 *
 	 * @dataProvider dataTests
 	 */
-	public function testGetWithRefresh($data)
+	public function testGetWithRefresh(int $uid, $data)
 	{
-		$uid = 234;
-		$configCache = new PConfigCache();
-		$configAdapter = \Mockery::mock(IPConfigAdapter::class);
-		$configAdapter->shouldReceive('isConnected')->andReturn(true)->times(4);
-		$configAdapter->shouldReceive('isLoaded')->with($uid, 'test', 'it')->andReturn(false)->once();
-		$configAdapter->shouldReceive('get')->with($uid, 'test', 'it')->andReturn('now')->once();
-		$configAdapter->shouldReceive('isLoaded')->with($uid, 'test', 'it')->andReturn(true)->twice();
-		$configAdapter->shouldReceive('get')->with($uid, 'test', 'it')->andReturn($data)->once();
-		$configAdapter->shouldReceive('isLoaded')->with($uid, 'test', 'not')->andReturn(false)->once();
-		$configAdapter->shouldReceive('get')->with($uid, 'test', 'not')->andReturn(null)->once();
+		$this->configCache->load($uid, ['test' => ['it' => 'now']]);
 
-		$configuration = new PConfiguration($configCache, $configAdapter);
+		$this->testedConfig = $this->getInstance();
+		$this->assertInstanceOf(PConfigCache::class, $this->testedConfig->getCache());
 
 		// without refresh
-		$this->assertEquals('now', $configuration->get($uid, 'test', 'it'));
-		// use the cache again
-		$this->assertEquals('now', $configuration->get($uid, 'test', 'it'));
+		$this->assertEquals('now', $this->testedConfig->get($uid, 'test', 'it'));
+		$this->assertEquals('now', $this->testedConfig->getCache()->get($uid, 'test', 'it'));
 
-		// with refresh (and load the second value out of the db)
-		$this->assertEquals($data, $configuration->get($uid, 'test', 'it', null, true));
+		// with refresh
+		$this->assertEquals($data, $this->testedConfig->get($uid, 'test', 'it', null, true));
+		$this->assertEquals($data, $this->testedConfig->getCache()->get($uid, 'test', 'it'));
 
 		// without refresh and wrong value and default
-		$this->assertEquals('default', $configuration->get($uid, 'test', 'not', 'default'));
+		$this->assertEquals('default', $this->testedConfig->get($uid, 'test', 'not', 'default'));
+		$this->assertNull($this->testedConfig->getCache()->get($uid, 'test', 'not'));
 	}
 
 	/**
-	 * Test the configuration get() method with different isLoaded settings
+	 * Test the configuration delete() method without a model/db
+	 *
 	 * @dataProvider dataTests
 	 */
-	public function testGetWithoutLoaded($data)
+	public function testDeleteWithoutDB(int $uid, $data)
 	{
-		$uid = 234;
-		$configCache = new PConfigCache();
-		$configAdapter = \Mockery::mock(IPConfigAdapter::class);
-		$configAdapter->shouldReceive('isConnected')->andReturn(true)->times(3);
+		$this->configCache->load($uid, ['test' => ['it' => $data]]);
 
-		$configAdapter->shouldReceive('isLoaded')->with($uid, 'test', 'it')->andReturn(false)->once();
-		$configAdapter->shouldReceive('get')->with($uid, 'test', 'it')->andReturn(null)->once();
+		$this->testedConfig = $this->getInstance();
+		$this->assertInstanceOf(PConfigCache::class, $this->testedConfig->getCache());
 
-		$configAdapter->shouldReceive('isLoaded')->with($uid, 'test', 'it')->andReturn(false)->once();
-		$configAdapter->shouldReceive('get')->with($uid, 'test', 'it')->andReturn($data)->once();
+		$this->assertEquals($data, $this->testedConfig->get($uid, 'test', 'it'));
+		$this->assertEquals($data, $this->testedConfig->getCache()->get($uid, 'test', 'it'));
 
-		$configAdapter->shouldReceive('isLoaded')->with($uid, 'test', 'it')->andReturn(true)->once();
+		$this->assertTrue($this->testedConfig->delete($uid, 'test', 'it'));
+		$this->assertNull($this->testedConfig->get($uid, 'test', 'it'));
+		$this->assertNull($this->testedConfig->getCache()->get($uid, 'test', 'it'));
 
-		$configuration = new PConfiguration($configCache, $configAdapter);
-
-		// first run is not loaded and no data is found in the DB
-		$this->assertNull($configuration->get($uid, 'test', 'it'));
-
-		// second run is not loaded, but now data is found in the db (overwrote cache)
-		$this->assertEquals($data, $configuration->get($uid,'test', 'it'));
-
-		// third run is loaded and therefore cache is used
-		$this->assertEquals($data, $configuration->get($uid,'test', 'it'));
+		$this->assertEmpty($this->testedConfig->getCache()->getAll());
 	}
 
 	/**
-	 * Test the configuration delete() method without adapter
-	 * @dataProvider dataTests
-	 */
-	public function testDeleteWithoutDB($data)
-	{
-		$uid = 234;
-		$configCache = new PConfigCache();
-		$configAdapter = \Mockery::mock(IPConfigAdapter::class);
-		$configAdapter->shouldReceive('isConnected')->andReturn(false)->times(4);
-
-		$configuration = new PConfiguration($configCache, $configAdapter);
-
-		$this->assertTrue($configuration->set($uid, 'test', 'it', $data));
-		$this->assertEquals($data, $configuration->get($uid, 'test', 'it'));
-
-		$this->assertTrue($configuration->delete($uid, 'test', 'it'));
-		$this->assertNull($configuration->get($uid, 'test', 'it'));
-	}
-
-	/**
-	 * Test the configuration delete() method with adapter
+	 * Test the configuration delete() method with a model/db
 	 */
 	public function testDeleteWithDB()
 	{
-		$uid = 234;
-		$configCache = new PConfigCache();
-		$configAdapter = \Mockery::mock(IPConfigAdapter::class);
-		$configAdapter->shouldReceive('isConnected')->andReturn(true)->times(6);
-		$configAdapter->shouldReceive('set')->with($uid, 'test', 'it', 'now')->andReturn(false)->once();
-		$configAdapter->shouldReceive('isLoaded')->with($uid, 'test', 'it')->andReturn(true)->once();
+		$this->configCache->load(0, ['test' => ['it' => 'now', 'quarter' => 'true']]);
 
-		$configAdapter->shouldReceive('delete')->with($uid, 'test', 'it')->andReturn(false)->once();
+		$this->configModel->shouldReceive('delete')
+		                  ->with(0, 'test', 'it')
+		                  ->andReturn(false)
+		                  ->once();
+		$this->configModel->shouldReceive('delete')
+		                  ->with(0, 'test', 'second')
+		                  ->andReturn(true)
+		                  ->once();
+		$this->configModel->shouldReceive('delete')
+		                  ->with(0, 'test', 'third')
+		                  ->andReturn(false)
+		                  ->once();
+		$this->configModel->shouldReceive('delete')
+		                  ->with(0, 'test', 'quarter')
+		                  ->andReturn(true)
+		                  ->once();
 
-		$configAdapter->shouldReceive('delete')->with($uid, 'test', 'second')->andReturn(true)->once();
-		$configAdapter->shouldReceive('delete')->with($uid, 'test', 'third')->andReturn(false)->once();
-		$configAdapter->shouldReceive('delete')->with($uid, 'test', 'quarter')->andReturn(true)->once();
+		$this->testedConfig = $this->getInstance();
+		$this->assertInstanceOf(PConfigCache::class, $this->testedConfig->getCache());
 
-		$configuration = new PConfiguration($configCache, $configAdapter);
+		// directly set the value to the cache
+		$this->testedConfig->getCache()->set(0, 'test', 'it', 'now');
 
-		$this->assertFalse($configuration->set($uid, 'test', 'it', 'now'));
-		$this->assertEquals('now', $configuration->get($uid, 'test', 'it'));
+		$this->assertEquals('now', $this->testedConfig->get(0, 'test', 'it'));
+		$this->assertEquals('now', $this->testedConfig->getCache()->get(0, 'test', 'it'));
 
-		// delete from set
-		$this->assertTrue($configuration->delete($uid, 'test', 'it'));
+		// delete from cache only
+		$this->assertTrue($this->testedConfig->delete(0, 'test', 'it'));
 		// delete from db only
-		$this->assertTrue($configuration->delete($uid, 'test', 'second'));
+		$this->assertTrue($this->testedConfig->delete(0, 'test', 'second'));
 		// no delete
-		$this->assertFalse($configuration->delete($uid, 'test', 'third'));
+		$this->assertFalse($this->testedConfig->delete(0, 'test', 'third'));
 		// delete both
-		$this->assertTrue($configuration->delete($uid, 'test', 'quarter'));
+		$this->assertTrue($this->testedConfig->delete(0, 'test', 'quarter'));
+
+		$this->assertEmpty($this->testedConfig->getCache()->getAll());
+	}
+
+	public function dataMultiUid()
+	{
+		return [
+			'normal' => [
+				'data1' => [
+					'uid'  => 1,
+					'data' => [
+						'cat1' => [
+							'key1' => 'value1',
+						],
+						'cat2' => [
+							'key2' => 'value2',
+						]
+					],
+				],
+				'data2' => [
+					'uid' => 2,
+					'data' => [
+						'cat1' => [
+							'key1' => 'value1a',
+						],
+						'cat2' => [
+							'key2' => 'value2',
+						],
+					],
+				],
+			],
+		];
+	}
+
+	/**
+	 * Test if multiple uids for caching are usable without errors
+	 * @dataProvider dataMultiUid
+	 */
+	public function testMultipleUidsWithCache(array $data1, array $data2)
+	{
+		$this->configCache->load($data1['uid'], $data1['data']);
+		$this->configCache->load($data2['uid'], $data2['data']);
+
+		$this->testedConfig = $this->getInstance();
+		$this->assertInstanceOf(PConfigCache::class, $this->testedConfig->getCache());
+
+		$this->assertConfig($data1['uid'], 'cat1', $data1['data']['cat1']);
+		$this->assertConfig($data1['uid'], 'cat2', $data1['data']['cat2']);
+		$this->assertConfig($data2['uid'], 'cat1', $data2['data']['cat1']);
+		$this->assertConfig($data2['uid'], 'cat2', $data2['data']['cat2']);
 	}
 }
diff --git a/tests/src/Core/Config/PreloadPConfigurationTest.php b/tests/src/Core/Config/PreloadPConfigurationTest.php
new file mode 100644
index 0000000000..ca916d4404
--- /dev/null
+++ b/tests/src/Core/Config/PreloadPConfigurationTest.php
@@ -0,0 +1,147 @@
+<?php
+
+namespace Friendica\Test\src\Core\Config;
+
+use Friendica\Core\Config\PreloadPConfiguration;
+
+class PreloadPConfigurationTest extends PConfigurationTest
+{
+	public function getInstance()
+	{
+		return new PreloadPConfiguration($this->configCache, $this->configModel);
+	}
+
+	/**
+	 * @dataProvider dataConfigLoad
+	 */
+	public function testLoad(int $uid, array $data, array $possibleCats, array $load)
+	{
+		$this->configModel->shouldReceive('isConnected')
+		                  ->andReturn(true)
+		                  ->once();
+
+		$this->configModel->shouldReceive('load')
+		                  ->with($uid)
+		                  ->andReturn($data)
+		                  ->once();
+
+		parent::testLoad($uid, $data, $possibleCats, $load);
+
+		// Assert that every category is loaded everytime
+		foreach ($data as $cat => $values) {
+			$this->assertConfig($uid, $cat, $values);
+		}
+	}
+
+	/**
+	 * @dataProvider dataDoubleLoad
+	 */
+	public function testCacheLoadDouble(int $uid, array $data1, array $data2, array $expect)
+	{
+		$this->configModel->shouldReceive('isConnected')
+		                  ->andReturn(true)
+		                  ->once();
+
+		$this->configModel->shouldReceive('load')
+		                  ->with($uid)
+		                  ->andReturn($data1)
+		                  ->once();
+
+		parent::testCacheLoadDouble($uid, $data1, $data2, $expect);
+
+		// Assert that every category is loaded everytime and is NOT overwritten
+		foreach ($data1 as $cat => $values) {
+			$this->assertConfig($uid, $cat, $values);
+		}
+	}
+
+	/**
+	 * @dataProvider dataTests
+	 */
+	public function testSetGetWithoutDB(int $uid, $data)
+	{
+		$this->configModel->shouldReceive('isConnected')
+		                  ->andReturn(false)
+		                  ->times(3);
+
+		parent::testSetGetWithoutDB($uid, $data);
+	}
+
+	/**
+	 * @dataProvider dataTests
+	 */
+	public function testSetGetWithDB(int $uid, $data)
+	{
+		$this->configModel->shouldReceive('isConnected')
+		                  ->andReturn(true)
+		                  ->twice();
+
+		$this->configModel->shouldReceive('load')
+		                  ->with($uid)
+		                  ->andReturn(['config' => []])
+		                  ->once();
+
+		parent::testSetGetWithDB($uid, $data);
+	}
+
+	/**
+	 * @dataProvider dataTests
+	 */
+	public function testGetWithRefresh(int $uid, $data)
+	{
+		$this->configModel->shouldReceive('isConnected')
+		                  ->andReturn(true)
+		                  ->times(2);
+
+		// constructor loading
+		$this->configModel->shouldReceive('load')
+		                  ->with($uid)
+		                  ->andReturn(['config' => []])
+		                  ->once();
+
+		// mocking one get
+		$this->configModel->shouldReceive('get')
+		                  ->with($uid, 'test', 'it')
+		                  ->andReturn($data)
+		                  ->once();
+
+		parent::testGetWithRefresh($uid, $data);
+	}
+
+
+	public function testGetWrongWithoutDB()
+	{
+		$this->configModel->shouldReceive('isConnected')
+		                  ->andReturn(false)
+		                  ->times(3);
+
+		parent::testGetWrongWithoutDB();
+	}
+
+	/**
+	 * @dataProvider dataTests
+	 */
+	public function testDeleteWithoutDB(int $uid, $data)
+	{
+		$this->configModel->shouldReceive('isConnected')
+		                  ->andReturn(false)
+		                  ->times(4);
+
+		parent::testDeleteWithoutDB($uid, $data);
+	}
+
+	public function testDeleteWithDB()
+	{
+		$this->configModel->shouldReceive('isConnected')
+		                  ->andReturn(true)
+		                  ->times(5);
+
+		// constructor loading
+		$this->configModel->shouldReceive('load')
+		                  ->with(0)
+		                  ->andReturn(['config' => []])
+		                  ->once();
+
+		parent::testDeleteWithDB();
+	}
+}
diff --git a/tests/src/Database/DBATest.php b/tests/src/Database/DBATest.php
index 852db4498f..530430feb1 100644
--- a/tests/src/Database/DBATest.php
+++ b/tests/src/Database/DBATest.php
@@ -3,6 +3,7 @@ namespace Friendica\Test\src\Database;
 
 use Friendica\App;
 use Friendica\Core\Config;
+use Friendica\Core\Config\Cache\PConfigCache;
 use Friendica\Database\DBA;
 use Friendica\Factory;
 use Friendica\Test\DatabaseTest;
@@ -14,7 +15,8 @@ class DBATest extends DatabaseTest
 	{
 		$configModel = new \Friendica\Model\Config\Config(self::$dba);
 		$config = Factory\ConfigFactory::createConfig(self::$configCache, $configModel);
-		Factory\ConfigFactory::createPConfig(self::$configCache, new Config\Cache\PConfigCache());
+		$pconfigModel = new \Friendica\Model\Config\PConfig(self::$dba);
+		Factory\ConfigFactory::createPConfig(self::$configCache, new PConfigCache(), $pconfigModel);
 		$logger = Factory\LoggerFactory::create('test', self::$dba, $config, self::$profiler);
 		$baseUrl = new BaseURL($config, $_SERVER);
 		$router = new App\Router();
diff --git a/tests/src/Database/DBStructureTest.php b/tests/src/Database/DBStructureTest.php
index d10f07493b..64d249f4c0 100644
--- a/tests/src/Database/DBStructureTest.php
+++ b/tests/src/Database/DBStructureTest.php
@@ -19,7 +19,8 @@ class DBStructureTest extends DatabaseTest
 	{
 		$configModel = new Config(self::$dba);
 		$config = Factory\ConfigFactory::createConfig(self::$configCache, $configModel);
-		Factory\ConfigFactory::createPConfig(self::$configCache, new PConfigCache());
+		$pconfigModel = new \Friendica\Model\Config\PConfig(self::$dba);
+		Factory\ConfigFactory::createPConfig(self::$configCache, new PConfigCache(), $pconfigModel);
 		$logger = Factory\LoggerFactory::create('test', self::$dba, $config, self::$profiler);
 		$baseUrl = new BaseURL($config, $_SERVER);
 		$router = new App\Router();