diff --git a/CHANGELOG.md b/CHANGELOG.md
index f5d6af74..b8bb2822 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1367,3 +1367,27 @@ Example:
tcp://localhost:6379?db=3
```
+## New Feature: `SqliteSessionHandler`
+
+A new **`SqliteSessionHandler`** class has been introduced under `MagicObject\Session`.
+This provides a **persistent session storage** mechanism using **SQLite** as the backend.
+
+### Features
+
+* Stores sessions in a **SQLite database file** instead of filesystem or memory.
+* Automatically **creates the session table** if it does not exist.
+* Implements the full session lifecycle:
+
+ * **open** — Initializes session.
+ * **read** — Reads serialized session data.
+ * **write** — Writes or updates session data.
+ * **destroy** — Removes a session by ID.
+ * **gc** — Garbage collects expired sessions.
+* Ensures **safe storage** even when multiple PHP processes are running.
+
+### Why It Matters?
+
+* **Portability:** No dependency on Redis or Memcached — only requires SQLite.
+* **Lightweight:** Suitable for shared hosting or small applications.
+* **Reliability:** Prevents session loss when PHP restarts, unlike file-based sessions.
+
diff --git a/docs/doc.html b/docs/doc.html
index 7d045753..57a70e4a 100644
--- a/docs/doc.html
+++ b/docs/doc.html
@@ -11,7 +11,7 @@
MagicObject\DataTable
@@ -18335,6 +18335,50 @@ Return
+
+
MagicObject\Exceptions\InvalidFileAccessException
+
Declaration
+
class InvalidFileAccessException extends Exception implements Throwable, Stringable
+{
+}
+
+
Package
+MagicObject\Exceptions
Authors
+
+- Kamshory
+
+
Links
+
+- https://github.com/Planetbiru/MagicObject
+
+
Description
+
Class InvalidFileAccessException
+
Custom exception class for handling errors related to invalid file access.
+
+
Properties
+
+
1. previous
+
Declaration
+
private Throwable $previous;
+
+
Description
+
Previous exception
+
+
+
Methods
+
+
2. getPreviousException
+
Declaration
+
public function getPreviousException() : Throwable|null
{
}
+
+
Description
+
Get the previous exception.
+
Return
+
Throwable|null
+
+
+
+
+
MagicObject\Session\SqliteSessionHandler
+
Declaration
+
class SqliteSessionHandler
+{
+}
+
+
Description
+
Class SqliteSessionHandler
+
A custom session handler implementation using SQLite as the storage backend.
+This class manages session lifecycle including create, read, update,
+destroy, and garbage collection.
+
+
Properties
+
+
1. pdo
+
Declaration
+
private PDO $pdo;
+
+
Description
+
PDO instance for SQLite connection.
+
+
+
+
2. table
+
Declaration
+
private string $table = 'sessions';
+
+
Description
+
Table name used for storing session data.
+
+
+
Methods
+
+
1. __construct
+
Declaration
+
public function __construct(
+ string $path
+)
{
}
+
+
Description
+
Constructor.
+
Ensures the database file and its parent directory exist,
+then initializes the SQLite connection and creates the
+sessions table if it does not exist.
+
Parameters
+
$path
+
Absolute path to the SQLite database file.
+
Throws
+
\RuntimeException
+
If the target directory is not writable.
+
+
+
+
2. open
+
Declaration
+
public function open(
+ string $savePath,
+ string $sessionName
+) : bool
{
}
+
+
Description
+
Open the session.
+
Parameters
+
$savePath
+
+
$sessionName
+
+
Return
+
bool
+
+
+
+
+
3. close
+
Declaration
+
public function close() : bool
{
}
+
+
Description
+
Close the session.
+
Return
+
bool
+
+
+
+
+
4. read
+
Declaration
+
public function read(
+ string $id
+) : string
{
}
+
+
Description
+
Read session data by session ID.
+
Parameters
+
$id
+
+
Return
+
string
+
Serialized session data, or empty string if not found.
+
+
+
+
5. write
+
Declaration
+
public function write(
+ string $id,
+ string $data
+) : bool
{
}
+
+
Description
+
Write session data.
+
Parameters
+
$id
+
+
$data
+
+
Return
+
bool
+
+
+
+
+
6. destroy
+
Declaration
+
public function destroy(
+ string $id
+) : bool
{
}
+
+
Description
+
Destroy a session by ID.
+
Parameters
+
$id
+
+
Return
+
bool
+
+
+
+
+
7. gc
+
Declaration
+
public function gc(
+ int $maxlifetime
+) : bool
{
}
+
+
Description
+
Perform garbage collection.
+
Removes expired sessions older than max lifetime.
+
Parameters
+
$maxlifetime
+
Maximum lifetime in seconds.
+
Return
+
bool
+
True if query executed successfully.
+
+
+
MagicObject\Util\AttrUtil
Declaration
@@ -41273,6 +41458,155 @@
Return
The encoded WebSocket frame on success, or false on failure.
+
+
+
MagicObject\Util\XmlToJsonParser
+
Declaration
+
class XmlToJsonParser
+{
+}
+
+
Description
+
XmlToJsonParser
+
Utility class for converting between XML and JSON-friendly PHP arrays.
+
+- Converts XML strings to PHP arrays, supporting flattening specified child elements into arrays,
+preserving attributes, and casting values to appropriate types.
+- Converts PHP arrays back to indented XML strings, supporting custom root and item names.
+
+
+
Properties
+
+
1. arrayItemNames
+
Declaration
+
private array List of element names to be treated as array items. $arrayItemNames;
+
+
+
+
Methods
+
+
1. __construct
+
Declaration
+
public function __construct(
+ array $arrayItemNames = array (
+ 0 => 'item',
+)
+)
{
}
+
+
Description
+
Constructor
+
Parameters
+
$arrayItemNames
+
Names of XML elements to be flattened as arrays.
+
+
+
+
2. parse
+
Declaration
+
public function parse(
+ string $xmlString
+) : mixed
{
}
+
+
Description
+
Parse XML string into PHP array.
+
Parameters
+
$xmlString
+
+
Return
+
mixed
+
+
+
+
+
3. convertElement
+
Declaration
+
private function convertElement(
+ SimpleXMLElement $element
+) : mixed
{
}
+
+
Description
+
Recursively convert SimpleXMLElement to array.
+
Parameters
+
$element
+
+
Return
+
mixed
+
+
+
+
+
4. castValue
+
Declaration
+
private function castValue(
+ string $value
+) : mixed
{
}
+
+
Description
+
Cast string value to appropriate PHP type.
+
Parameters
+
$value
+
+
Return
+
mixed
+
+
+
+
+
5. toXml
+
Declaration
+
public function toXml(
+ array $array,
+ string $rootName = 'root',
+ string $itemName = 'item'
+) : string
{
}
+
+
Description
+
Converts a PHP array to an XML string.
+
Parameters
+
$array
+
The input array to convert.
+
$rootName
+
The name of the root XML element.
+
$itemName
+
The name to use for array items.
+
Return
+
string
+
The resulting XML string.
+
+
+
+
6. arrayToXml
+
Declaration
+
private function arrayToXml(
+ mixed $data,
+ SimpleXMLElement,
+ string $currentName,
+ string $itemName
+) : void
{
}
+
+
Description
+
Recursively adds array data to a SimpleXMLElement.
+
Parameters
+
$data
+
The data to convert (array or scalar).
+
&$xmlElement
+
The XML element to append to.
+
$currentName
+
The current element name.
+
$itemName
+
The name to use for array items.
+
Return
+
void
+
This function traverses the input array or scalar value and appends it to the given XML element.
+
+- If the value is an array, it checks if it is associative or sequential.
+- Sequential arrays are wrapped using the provided itemName.
+- Associative arrays are processed by key, handling text nodes ("#text") and attributes ("@attr").
+- Scalar values are added as text nodes.
+- Boolean false is converted to string "false" instead of null.
+
+
+
diff --git a/src/Exceptions/InvalidFileAccessException.php b/src/Exceptions/InvalidFileAccessException.php
new file mode 100644
index 00000000..220a3a8e
--- /dev/null
+++ b/src/Exceptions/InvalidFileAccessException.php
@@ -0,0 +1,47 @@
+previous = $previous;
+ }
+
+ /**
+ * Get the previous exception.
+ *
+ * @return Throwable|null The previous exception
+ */
+ public function getPreviousException()
+ {
+ return $this->previous;
+ }
+}
diff --git a/src/Session/PicoSession.php b/src/Session/PicoSession.php
index e512398b..619e4a00 100644
--- a/src/Session/PicoSession.php
+++ b/src/Session/PicoSession.php
@@ -16,7 +16,7 @@
* @package MagicObject\Session
* @link https://github.com/Planetbiru/MagicObject
*/
-class PicoSession
+class PicoSession // NOSONAR
{
const SESSION_STARTED = true;
const SESSION_NOT_STARTED = false;
@@ -55,6 +55,7 @@ class PicoSession
*/
public function __construct($sessConf = null)
{
+ error_log(json_encode($sessConf));
if ($sessConf && $sessConf->getName() != "") {
$this->setSessionName($sessConf->getName());
}
@@ -66,6 +67,18 @@ public function __construct($sessConf = null)
$this->saveToRedis($redisParams->host, $redisParams->port, $redisParams->auth, $redisParams->db);
} elseif ($sessConf && $sessConf->getSaveHandler() == "files" && $sessConf->getSavePath() != "") {
$this->saveToFiles($sessConf->getSavePath());
+ } elseif ($sessConf && $sessConf->getSaveHandler() == "sqlite" && $sessConf->getSavePath() != "") {
+ error_log($sessConf->getSavePath());
+ $handler = new SqliteSessionHandler($sessConf->getSavePath());
+ session_set_save_handler(
+ [$handler, 'open'],
+ [$handler, 'close'],
+ [$handler, 'read'],
+ [$handler, 'write'],
+ [$handler, 'destroy'],
+ [$handler, 'gc']
+ );
+ register_shutdown_function('session_write_close');
}
}
@@ -73,29 +86,15 @@ public function __construct($sessConf = null)
* Extracts Redis connection parameters from a session configuration object.
*
* Parses the Redis `save_path` (in URL format) from the given SecretObject instance
- * and returns a stdClass object containing the Redis connection details.
+ * and returns a stdClass object containing the Redis host, port, and optional authentication.
*
- * Supported save path formats:
+ * Example save path formats:
* - tcp://127.0.0.1:6379
* - tcp://[::1]:6379
* - tcp://localhost:6379?auth=yourpassword
- * - tcp://localhost:6379?password=yourpassword
- * - tcp://localhost:6379?db=3
- * - tcp://localhost:6379?dbindex=3
- * - tcp://localhost:6379?database=3
- *
- * Recognized query parameters:
- * - `auth` or `password` : Redis authentication password
- * - `db`, `dbindex`, or `database` : Redis logical database index (default: 0)
- * - Any other query parameters are preserved in `options`
*
* @param SecretObject $sessConf Session configuration object containing the Redis save path.
- * @return stdClass An object with the properties:
- * - `host` (string) : Redis hostname or IP address
- * - `port` (int) : Redis port number
- * - `auth` (string|null) : Authentication password (if any)
- * - `db` (int) : Redis database index (default: 0)
- * - `options` (array) : All parsed query parameters
+ * @return stdClass An object with the properties: `host` (string), `port` (int), and `auth` (string|null).
*/
private function getRedisParams($sessConf) // NOSONAR
{
diff --git a/src/Session/SqliteSessionHandler.php b/src/Session/SqliteSessionHandler.php
new file mode 100644
index 00000000..3fa68e98
--- /dev/null
+++ b/src/Session/SqliteSessionHandler.php
@@ -0,0 +1,167 @@
+pdo = new PDO($dsn);
+ $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+
+ // Create session table if it does not exist
+ $this->pdo->exec("
+ CREATE TABLE IF NOT EXISTS {$this->table} (
+ id TEXT PRIMARY KEY,
+ data TEXT,
+ timestamp INTEGER
+ )
+ ");
+ }
+
+ /**
+ * Open the session.
+ *
+ * @param string $savePath Session save path.
+ * @param string $sessionName Session name.
+ *
+ * @return bool Always true.
+ */
+ public function open($savePath, $sessionName) // NOSONAR
+ {
+ return true;
+ }
+
+ /**
+ * Close the session.
+ *
+ * @return bool Always true.
+ */
+ public function close()
+ {
+ return true;
+ }
+
+ /**
+ * Read session data by session ID.
+ *
+ * @param string $id Session ID.
+ *
+ * @return string Serialized session data, or empty string if not found.
+ */
+ public function read($id)
+ {
+ $stmt = $this->pdo->prepare("SELECT data FROM {$this->table} WHERE id = :id");
+ $stmt->execute(array(':id' => $id));
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
+
+ return isset($row['data']) ? $row['data'] : '';
+ }
+
+ /**
+ * Write session data.
+ *
+ * @param string $id Session ID.
+ * @param string $data Serialized session data.
+ *
+ * @return bool True on success.
+ */
+ public function write($id, $data)
+ {
+ $time = time();
+ $stmt = $this->pdo->prepare("
+ INSERT INTO {$this->table} (id, data, timestamp)
+ VALUES (:id, :data, :time)
+ ON CONFLICT(id) DO UPDATE SET data = :data, timestamp = :time
+ ");
+
+ return $stmt->execute(array(
+ ':id' => $id,
+ ':data' => $data,
+ ':time' => $time
+ ));
+ }
+
+ /**
+ * Destroy a session by ID.
+ *
+ * @param string $id Session ID.
+ *
+ * @return bool True on success.
+ */
+ public function destroy($id)
+ {
+ $stmt = $this->pdo->prepare("DELETE FROM {$this->table} WHERE id = :id");
+ return $stmt->execute(array(':id' => $id));
+ }
+
+ /**
+ * Perform garbage collection.
+ *
+ * Removes expired sessions older than max lifetime.
+ *
+ * @param int $maxlifetime Maximum lifetime in seconds.
+ *
+ * @return bool True if query executed successfully.
+ */
+ public function gc($maxlifetime)
+ {
+ $old = time() - $maxlifetime;
+ return $this->pdo->exec("DELETE FROM {$this->table} WHERE timestamp < $old") !== false;
+ }
+}
diff --git a/src/Util/Database/PicoDatabaseUtilBase.php b/src/Util/Database/PicoDatabaseUtilBase.php
index 86c2faec..4323e04d 100644
--- a/src/Util/Database/PicoDatabaseUtilBase.php
+++ b/src/Util/Database/PicoDatabaseUtilBase.php
@@ -457,7 +457,7 @@ public function importDataTable(
* @return bool True if there may still be more data remaining; false if the import
* process has finished and post-import scripts have been executed.
*/
- public function importData($config, $callbackFunction, $tableName = null, $limit = null, $offset = null)
+ public function importData($config, $callbackFunction, $tableName = null, $limit = null, $offset = null) // NOSONAR
{
$databaseConfigSource = $config->getDatabaseSource();
$databaseConfigTarget = $config->getDatabaseTarget();