Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SQLite with WAL support is added #20

Merged
merged 3 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/vendor/
/index.php
/.idea
.phpunit.result.cache
.phpunit.result.cache
.DS_Store
123 changes: 123 additions & 0 deletions src/Drivers/Sqlite.php
keskinonur marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?php

declare(strict_types=1);

namespace Ghostff\Session\Drivers;

use Ghostff\Session\Session;
use PDO;
use PDOException;
use RuntimeException;
use SessionHandlerInterface;

class SQLite extends SetGet implements SessionHandlerInterface
{
private PDO $conn;
private string $table;

public function __construct(array $config)
{
if (!extension_loaded('pdo_sqlite')) {
throw new RuntimeException('\'pdo_sqlite\' extension is needed to use this driver.');
}

parent::__construct($config);
$config = $config[Session::CONFIG_SQLITE_DS];
$dsn = "{$config['driver']}:{$config['db_path']}";
$this->table = $table = $config['db_table'];

try {
$this->conn = new PDO($dsn, null, null, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);

// Enable Write-Ahead Logging (WAL) mode
$this->conn->exec('PRAGMA journal_mode = WAL');

$this->conn->query("SELECT 1 FROM `{$table}` LIMIT 1");
} catch (PDOException $e) {
// Debug information
error_log("Error connecting to SQLite database or checking table: " . $e->getMessage());

$this->conn->query('CREATE TABLE `' . $table . '` (
`id` TEXT PRIMARY KEY,
`data` TEXT NOT NULL,
`time` INTEGER NOT NULL
)');
}
}

public function open($path, $name): bool
{
return true;
}

public function close(): bool
{
return true;
}

public function read($id): string
{
$data = '';
$statement = $this->conn->prepare("SELECT `data` FROM `{$this->table}` WHERE `id` = :id");
$statement->bindParam(':id', $id, PDO::PARAM_STR);

if ($statement->execute()) {
$result = $statement->fetch();
$data = $result['data'] ?? '';
} else {
// Debug information
error_log("Failed to execute read statement for ID: {$id}");
keskinonur marked this conversation as resolved.
Show resolved Hide resolved
}

$statement = null; // close
return $this->get($data);
}

public function write($id, $data): bool
{
$statement = $this->conn->prepare("REPLACE INTO `{$this->table}` (`id`, `data`, `time`) VALUES (:id, :data, :time)");
$statement->bindParam(':id', $id, PDO::PARAM_STR);
$statement->bindValue(':data', $this->set($data), PDO::PARAM_STR);
$statement->bindValue(':time', time(), PDO::PARAM_INT);

$completed = $statement->execute();
if (!$completed) {
// Debug information
error_log("Failed to execute write statement for ID: {$id}");
keskinonur marked this conversation as resolved.
Show resolved Hide resolved
}

$statement = null; // close
return $completed;
}

public function destroy($id): bool
{
$statement = $this->conn->prepare("DELETE FROM `{$this->table}` WHERE `id` = :id");
$statement->bindParam(':id', $id, PDO::PARAM_STR);

$completed = $statement->execute();
if (!$completed) {
// Debug information
error_log("Failed to execute destroy statement for ID: {$id}");
keskinonur marked this conversation as resolved.
Show resolved Hide resolved
}

$statement = null; // close
return $completed;
}

#[\ReturnTypeWillChange]
public function gc($max_lifetime)
{
$max_lifetime = time() - $max_lifetime;
$statement = $this->conn->prepare("DELETE FROM `{$this->table}` WHERE `time` < :time");
$statement->bindParam(':time', $max_lifetime, PDO::PARAM_INT);

$statement->execute();
$count = $statement->rowCount();
$statement = null; // close

return $count;
}
}
27 changes: 14 additions & 13 deletions src/Session.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<?php declare(strict_types=1);
<?php

declare(strict_types=1);

namespace Ghostff\Session;

Expand All @@ -17,6 +19,7 @@ class Session
public const CONFIG_MYSQL_DS = 'mysql';
public const CONFIG_MEMCACHED_DS = 'memcached';
public const CONFIG_REDIS_DS = 'redis';
public const CONFIG_SQLITE_DS = 'sqlite';

protected const DEFAULT_SEGMENT = ':';
protected const SESSION_INDEX = 0;
Expand All @@ -38,7 +41,8 @@ public static function setConfigurationFile(string $file_name = __DIR__ . '/defa

public static function updateConfiguration(array $config_override): array
{
return self::$config = self::arrayMergeRecursiveDistinct(self::$config ?: self::setConfigurationFile(), $config_override);
self::$config = self::arrayMergeRecursiveDistinct(self::$config ?: self::setConfigurationFile(), $config_override);
return self::$config;
}

/**
Expand All @@ -52,13 +56,12 @@ public static function updateConfiguration(array $config_override): array
protected static function arrayMergeRecursiveDistinct(array $array1, array &$array2): array
{
$merged = $array1;
foreach ($array2 as $key => &$value)
{
foreach ($array2 as $key => &$value) {
if (\is_array($value) && isset($merged[$key]) && \is_array($merged[$key])) {
$merged[$key] = self::arrayMergeRecursiveDistinct($merged[$key], $value);
} else {
if (\is_numeric($key)) {
if (! \in_array($value, $merged)) {
if (!\in_array($value, $merged)) {
$merged[] = $value;
}
} else {
Expand Down Expand Up @@ -118,8 +121,8 @@ public function id(): string
public function segment(string $name): self
{
$session = new self();
$session->data =& $this->data;
$session->changed =& $this->changed;
$session->data = &$this->data;
$session->changed = &$this->changed;
$session->segment = $name;

return $session;
Expand Down Expand Up @@ -150,8 +153,7 @@ public function set(string $name, $value): self
public function push(string $name, $value): self
{
$values = $this->getOrDefault($name, []);
if ( ! is_array($values))
{
if (!is_array($values)) {
$values = [$values];
}

Expand All @@ -169,7 +171,7 @@ public function push(string $name, $value): self
*/
public function get(string $name)
{
if (! isset($this->data[$this->segment][self::SESSION_INDEX]) || ! array_key_exists($name, $this->data[$this->segment][self::SESSION_INDEX])) {
if (!isset($this->data[$this->segment][self::SESSION_INDEX]) || !array_key_exists($name, $this->data[$this->segment][self::SESSION_INDEX])) {
throw new RuntimeException("\"{$name}\" does not exist in current session segment.");
}

Expand Down Expand Up @@ -263,7 +265,7 @@ public function setFlash(string $name, $value): self
*/
public function getFlash(string $name)
{
if (! isset($this->data[$this->segment][self::FLASH_INDEX]) || ! array_key_exists($name, $this->data[$this->segment][self::FLASH_INDEX])) {
if (!isset($this->data[$this->segment][self::FLASH_INDEX]) || !array_key_exists($name, $this->data[$this->segment][self::FLASH_INDEX])) {
throw new RuntimeException("flash(\"{$name}\") does not exist in current session segment.");
}

Expand Down Expand Up @@ -333,8 +335,7 @@ public function exist(string $name, bool $in_flash = false): bool
*/
public function rotate(bool $delete_old = false): self
{
if (headers_sent($filename, $line_num))
{
if (headers_sent($filename, $line_num)) {
throw new RuntimeException(sprintf('ID must be regenerated before any output is sent to the browser. (file: %s, line: %s)', $filename, $line_num));
}

Expand Down
9 changes: 8 additions & 1 deletion src/default_config.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

use Ghostff\Session\Drivers\File;
use Ghostff\Session\Session;

Expand Down Expand Up @@ -30,7 +31,7 @@
'db_table' => 'session', # Database table
'db_user' => 'root', # Database username
'db_pass' => '', # Database password
'persistent_conn'=> false, # Avoid the overhead of establishing a new connection every time a script needs to talk to a database, resulting in a faster web application. FIND THE BACKSIDE YOURSELF
'persistent_conn' => false, # Avoid the overhead of establishing a new connection every time a script needs to talk to a database, resulting in a faster web application. FIND THE BACKSIDE YOURSELF
],
Session::CONFIG_MEMCACHED_DS => [
'servers' => [
Expand All @@ -46,5 +47,11 @@
'save_path' => 'tcp://127.0.0.1:6379', #comma separated of hostname:port entries to use for session server pool.
'persistent_id' => 'sess_pool',
'timeout' => 2.5
],
Session::CONFIG_SQLITE_DS => [
'driver' => 'sqlite',
'db_path' => 'sessions.db',
'db_table' => 'sessions',
]

];