From 965d7d44c5f0fcffec86d77274c6161105c5e411 Mon Sep 17 00:00:00 2001
From: Dag <me@dvikan.no>
Date: Thu, 6 Jul 2023 15:59:38 +0200
Subject: [PATCH] feat(sqlite cache): add config options (#3499)

* refactor: sqlite cache

* refactor

* feat: add config options to sqlite cache

* refactor
---
 caches/FileCache.php   |  4 +-
 caches/SQLiteCache.php | 93 +++++++++++++++++++-----------------------
 config.default.ini.php |  5 +++
 lib/CacheFactory.php   | 21 +++++++++-
 4 files changed, 69 insertions(+), 54 deletions(-)

diff --git a/caches/FileCache.php b/caches/FileCache.php
index bb7c1b30..adaf458c 100644
--- a/caches/FileCache.php
+++ b/caches/FileCache.php
@@ -9,8 +9,8 @@ class FileCache implements CacheInterface
     public function __construct(array $config = [])
     {
         $default = [
-            'path' => null,
-            'enable_purge' => true,
+            'path'          => null,
+            'enable_purge'  => true,
         ];
         $this->config = array_merge($default, $config);
         if (!$this->config['path']) {
diff --git a/caches/SQLiteCache.php b/caches/SQLiteCache.php
index f8a36ba3..309b86d1 100644
--- a/caches/SQLiteCache.php
+++ b/caches/SQLiteCache.php
@@ -5,51 +5,43 @@
  */
 class SQLiteCache implements CacheInterface
 {
-    protected string $scope;
-    protected string $key;
+    private \SQLite3 $db;
+    private string $scope;
+    private string $key;
+    private array $config;
 
-    private $db = null;
-
-    public function __construct()
+    public function __construct(array $config)
     {
-        if (!extension_loaded('sqlite3')) {
-            throw new \Exception('"sqlite3" extension not loaded. Please check "php.ini"');
+        $default = [
+            'file'          => null,
+            'timeout'       => 5000,
+            'enable_purge'  => true,
+        ];
+        $config = array_merge($default, $config);
+        $this->config = $config;
+
+        if (!$config['file']) {
+            throw new \Exception('sqlite cache needs a file');
         }
 
-        if (!is_writable(PATH_CACHE)) {
-            throw new \Exception('The cache folder is not writable');
-        }
-
-        $section = 'SQLiteCache';
-        $file = Configuration::getConfig($section, 'file');
-        if (!$file) {
-            throw new \Exception(sprintf('Configuration for %s missing.', $section));
-        }
-
-        if (dirname($file) == '.') {
-            $file = PATH_CACHE . $file;
-        } elseif (!is_dir(dirname($file))) {
-            throw new \Exception(sprintf('Invalid configuration for %s', $section));
-        }
-
-        if (!is_file($file)) {
-            // The instantiation creates the file
-            $this->db = new \SQLite3($file);
+        if (is_file($config['file'])) {
+            $this->db = new \SQLite3($config['file']);
+            $this->db->enableExceptions(true);
+        } else {
+            // Create the file and create sql schema
+            $this->db = new \SQLite3($config['file']);
             $this->db->enableExceptions(true);
             $this->db->exec("CREATE TABLE storage ('key' BLOB PRIMARY KEY, 'value' BLOB, 'updated' INTEGER)");
-        } else {
-            $this->db = new \SQLite3($file);
-            $this->db->enableExceptions(true);
         }
-        $this->db->busyTimeout(5000);
+        $this->db->busyTimeout($config['timeout']);
     }
 
     public function loadData()
     {
-        $Qselect = $this->db->prepare('SELECT value FROM storage WHERE key = :key');
-        $Qselect->bindValue(':key', $this->getCacheKey());
-        $result = $Qselect->execute();
-        if ($result instanceof \SQLite3Result) {
+        $stmt = $this->db->prepare('SELECT value FROM storage WHERE key = :key');
+        $stmt->bindValue(':key', $this->getCacheKey());
+        $result = $stmt->execute();
+        if ($result) {
             $data = $result->fetchArray(\SQLITE3_ASSOC);
             if (isset($data['value'])) {
                 return unserialize($data['value']);
@@ -61,20 +53,20 @@ class SQLiteCache implements CacheInterface
 
     public function saveData($data): void
     {
-        $Qupdate = $this->db->prepare('INSERT OR REPLACE INTO storage (key, value, updated) VALUES (:key, :value, :updated)');
-        $Qupdate->bindValue(':key', $this->getCacheKey());
-        $Qupdate->bindValue(':value', serialize($data));
-        $Qupdate->bindValue(':updated', time());
-        $Qupdate->execute();
+        $stmt = $this->db->prepare('INSERT OR REPLACE INTO storage (key, value, updated) VALUES (:key, :value, :updated)');
+        $stmt->bindValue(':key', $this->getCacheKey());
+        $stmt->bindValue(':value', serialize($data));
+        $stmt->bindValue(':updated', time());
+        $stmt->execute();
     }
 
     public function getTime(): ?int
     {
-        $Qselect = $this->db->prepare('SELECT updated FROM storage WHERE key = :key');
-        $Qselect->bindValue(':key', $this->getCacheKey());
-        $result = $Qselect->execute();
-        if ($result instanceof \SQLite3Result) {
-            $data = $result->fetchArray(SQLITE3_ASSOC);
+        $stmt = $this->db->prepare('SELECT updated FROM storage WHERE key = :key');
+        $stmt->bindValue(':key', $this->getCacheKey());
+        $result = $stmt->execute();
+        if ($result) {
+            $data = $result->fetchArray(\SQLITE3_ASSOC);
             if (isset($data['updated'])) {
                 return $data['updated'];
             }
@@ -85,9 +77,12 @@ class SQLiteCache implements CacheInterface
 
     public function purgeCache(int $seconds): void
     {
-        $Qdelete = $this->db->prepare('DELETE FROM storage WHERE updated < :expired');
-        $Qdelete->bindValue(':expired', time() - $seconds);
-        $Qdelete->execute();
+        if (!$this->config['enable_purge']) {
+            return;
+        }
+        $stmt = $this->db->prepare('DELETE FROM storage WHERE updated < :expired');
+        $stmt->bindValue(':expired', time() - $seconds);
+        $stmt->execute();
     }
 
     public function setScope(string $scope): void
@@ -102,10 +97,6 @@ class SQLiteCache implements CacheInterface
 
     private function getCacheKey()
     {
-        if (is_null($this->key)) {
-            throw new \Exception('Call "setKey" first!');
-        }
-
         return hash('sha1', $this->scope . $this->key, true);
     }
 }
diff --git a/config.default.ini.php b/config.default.ini.php
index 217eb7ce..d27d52e8 100644
--- a/config.default.ini.php
+++ b/config.default.ini.php
@@ -125,7 +125,12 @@ path = ""
 enable_purge = true
 
 [SQLiteCache]
+; Filepath of the sqlite db file
 file = "cache.sqlite"
+; Whether to actually delete data when purging
+enable_purge = true
+; Busy wait in ms before timing out
+timeout = 5000
 
 [MemcachedCache]
 host = "localhost"
diff --git a/lib/CacheFactory.php b/lib/CacheFactory.php
index abafa3ba..78a0e83e 100644
--- a/lib/CacheFactory.php
+++ b/lib/CacheFactory.php
@@ -51,7 +51,26 @@ class CacheFactory
                 }
                 return new FileCache($fileCacheConfig);
             case SQLiteCache::class:
-                return new SQLiteCache();
+                if (!extension_loaded('sqlite3')) {
+                    throw new \Exception('"sqlite3" extension not loaded. Please check "php.ini"');
+                }
+                if (!is_writable(PATH_CACHE)) {
+                    throw new \Exception('The cache folder is not writable');
+                }
+                $file = Configuration::getConfig('SQLiteCache', 'file');
+                if (!$file) {
+                    throw new \Exception(sprintf('Configuration for %s missing.', 'SQLiteCache'));
+                }
+                if (dirname($file) == '.') {
+                    $file = PATH_CACHE . $file;
+                } elseif (!is_dir(dirname($file))) {
+                    throw new \Exception(sprintf('Invalid configuration for %s', 'SQLiteCache'));
+                }
+                return new SQLiteCache([
+                    'file'          => $file,
+                    'timeout'       => Configuration::getConfig('SQLiteCache', 'timeout'),
+                    'enable_purge'  => Configuration::getConfig('SQLiteCache', 'enable_purge'),
+                ]);
             case MemcachedCache::class:
                 return new MemcachedCache();
             default: