I'm creating my own custom SessionHandler to store my session information in a postgresql 9.3 database and I'm having a problem where the session data passed to the write() method isn't being written to the database, but the session name is??
Things that I know for a fact
My custom class is handling the sessions when session_start() is called - as tested with echoing output from the various methods and no session files are being created in /tmp
The $session_data arg in write() contains the proper serialized string as shown by echoing the contents in the write() method.
$session_name is being written to the database just fine and so is a BLANK serialized string a:0:{}.
Things I'm confused about:
Echoing the contents of $_SESSION['test_var1'] shows the correct value stored, even if read() is empty or returning no value??
If the session name is saved in the DB just fine, why isn't the session data?
Server Configuration
OS: Gentoo
Database: Postgresql 9.3
Web Server: NGINX 1.7.6
PHP 5.5.18 connected to NGINX via FPM
PHP ini session settings
session.save_handler = user
session.use_strict_mode = 1
session.use_cookies = 1
session.cookie_secure = 1
session.use_only_cookies = 1
session.name = _g
session.auto_start = 0
session.serialize_handler = php_serialize
class SessionManagement implements SessionHandlerInterface {
private $_id = '';
private $_link = null;
public function __construct() {
session_set_save_handler(
[$this, 'open'],
[$this, 'close'],
[$this, 'read'],
[$this, 'write'],
[$this, 'destroy'],
[$this, 'gc']
);
}
public function open($save_path, $session_id) {
echo 'open called<br/>';
$this->_id = $session_id;
$this->_link = new PDO('pgsql:host=' . $_SERVER['DB_HOST'] . ';dbname=' . $_SERVER['DB_DB'],
$_SERVER['DB_USER'],
$_SERVER['DB_PASS']);
}
public function close() {
echo 'close called<br/>';
}
public function destroy($session_id) {
echo 'destroying '.$session_id, '<br/>';
}
public function gc($maxlifetime) {
echo 'GC called<br/>';
}
public function read($session_name) {
$name = $this->_id.'_'.$session_name;
$sql = 'SELECT session_data FROM sessions WHERE session_name = :name';
if ($rel = $this->_link->prepare($sql)) {
if ($rel->execute([':name' => $name])) {
return $rel->fetch(PDO::FETCH_ASSOC)['session_data'];
} else {
return false;
}
} else {
return false;
}
return '';
}
public function write($session_name, $session_data) {
echo 'Session data: '.$session_data.'<br/>';
$name = $this->_id . '_' . $session_name;
$data = $session_data;
$sql = "SELECT 1 FROM sessions WHERE session_name = :name";
if ($rel = $this->_link->prepare($sql)) {
if ($rel->execute([':name' => $name])) {
if ($rel->rowCount()) {
echo 'Updating...<br/>';
$sql = 'UPDATE sessions SET session_data = :data WHERE session_name = :name';
if ($rel = $this->_link->prepare($sql)) {
if ($rel->execute([':name' => $name, ':data' => $data])) {
echo 'Update success...<br/>';
} else {
echo 'Update failed...<br/>';
var_dump($rel->errorInfo());
}
}
} else {
echo 'Inserting...<br/>';
$sql = 'INSERT INTO sessions (session_name, session_data) ';
$sql .= 'VALUES(:name, :data)';
if ($rel = $this->_link->prepare($sql)) {
if ($rel->execute([':name' => $name, ':data' => $data])) {
echo 'Insert success...<br/>';
} else {
echo 'Insert failed...<br/>';
var_dump($rel->errorInfo());
}
}
}
}
}
}
}
Test code:
new SessionManagement();
session_start();
$_SESSION['test_var1'] = 'some test data';
session_write_close(); // Making sure writing is finished
echo $_SESSION['test_var1'];
Output via test page
open called
Session data: a:1:{s:9:"test_var1";s:14:"some test data";}
Inserting...
Insert success...
close called
some test data
Relevant database fields
session_name: _g_h8m64bsb7a72dpj56vgojn6f4k3ncdf97leihcqfupg2qtvpbo20
session_data: a:0:{}
I'm not sure if this is a database issue or a PHP issue. I've been messing with this for a few days now and decided it was time to ask the community. Hopefully someone has some insight as to what the problem is.
I think you must initialize PDO object outside of the Open function handler and the class itself
try to access to your PDO Object with a Global value or through a static variable.
This is my implementation with MYSQL for my project :
class Core_DB_SessionHandler implements SessionHandlerInterface
{
protected $options = array(); // Options de la session
protected static $db = NULL; // Acceder a la BDD
public function __construct($options, $pdo) {
$this->options = $options;
self::$db = $pdo;
}
public function open($savePath, $sessionName) {
$now = time();
$req = self::$db->prepare("DELETE FROM tbl_sessions WHERE expire < '{1}' ");
$req->execute(array($now));
return TRUE;
}
public function close() {
$this->gc(ini_get('session.gc_maxlifetime'));
}
public function read($id) {
$now = time();
$stmt = self::$db->query("SELECT data FROM tbl_sessions WHERE sid = '$id AND expire < '$now'");
$result = $stmt->fetchColumn();
return $result;
}
public function write($id, $data) {
if (array_key_exists('TIMEOUT', $_SESSION)) {
$newExp = $_SESSION['TIMEOUT'];
}
else {
$newExp = time() + $this->options['time_limiter'];
}
try {
$req = self::$db->prepare('INSERT INTO tbl_sessions (sid, data, expire) VALUES (:sid, :data, :expire)
ON DUPLICATE KEY UPDATE data = :data, expire = :expire');
$vals = array('sid' => $id, 'data' => $data, 'expire' => $newExp);
$req->execute($vals);
return TRUE;
}
catch (PDOException $e) {
throw new Core_Exception(sprintf('PDOException was thrown when trying to write the session data: %s', $e->getMessage()), 0, $e);
}
}
public function destroy($id) {
$stmt = self::$db->prepare("DELETE FROM tbl_sessions WHERE sid = '{1}'");
$stmt->execute(array($id));
//return ($stmt->rowCount() === 1) ? true : false;
return TRUE;
}
public function gc($maxlifetime) {
$now = time();
$req = self::$db->prepare("DELETE FROM tbl_sessions WHERE expire < '{1}' ");
$req->execute(array($now));
return TRUE;
}
}
and i initialize handler like this :
$handler = new Core_DB_SessionHandler($MyOptions, $MyPDO);
if (PHP5) {
if (!session_set_save_handler($handler, TRUE)) {
throw new Core_Exception('Erreur lors de l\'init des sessions !');
}
}
nb : In your Table structure don't use autoincrement for ID
Well, I've solved my problem, but it's hard to say if this fix was actually the problem in the first place.
In the read method, I changed the follow:
return $rel->fetch(PDO::FETCH_ASSOC)['session_data'];
to
$data $rel->fetch(PDO::FETCH_ASSOC)['session_data'];
return $data;
After this the session was writing $session_data to the database without any problem. That's all well and dandy, but it doesn't explain why it didn't work in the first place. I mainly say this because upon switching the statement back everything continued to work. As in, I can't reproduce the issue now. So it's hard for me to even say that this was the problem in the first place.
Hopefully this helps someone. I've been unable to find more information about it, but it something does show up I'll be sure to add it here.
Related
I am writing my own session handler as below to make Aerospike as the session manager. However, it is throwing warnings about session handler.
Warning: session_start(): Cannot find save handler '' - session startup failed in /var/www/session.php on line 165
I have set the values of session.save_path and session.save_handler to "" in php.ini as I want to manage the session data storage and retrieval myself with the below class.
NOTE: I can use the default aerospike session handling but it saves session data as bytes(hexadecimal format), I can't use it as it is as other applications need to read this data as well, so I am trying to save data as a json encoded string.
A weird behavior is that the close method always gets called at script shutdown even though the session_set_save_handler's return value is false!
This works sometimes without error and sometimes throws a warning shown above. Not sure what is missing.
Code:
<?php
ini_set( 'display_errors', 1 );
ini_set( 'error_reporting', E_ALL );
define('SESS_ID', '66ac548234f96b48b42e18b2d3d7b73a3f1aceb01fa4c20647d3dcaa055b5099');
class MySessionHandler implements SessionHandlerInterface {
private $database = null;
private $recordKey = null;
public function __construct(){
$this->init();
}
private function init() {
$this->database = new \Aerospike(
[
"hosts" => [
0 => [
"addr" => "IP_HERE",
"port" => 3000
]
]
], false);
$this->recordKey = $this->database->initKey( 'cache', 'data', SESS_ID);
}
private function isConnected() : bool {
return ( $this->database instanceof \Aerospike ) && $this->database->isConnected();
}
public function open($savepath = '', $id = ''){
// If successful
if( is_null($this->database) ) {
$this->init();
}
$status = $this->database->get($this->recordKey, $data);
if ($status == \Aerospike::OK) {
$data = json_decode($data['bins']['PHP_SESSION'], 1);
if( !is_array($data) ) {
$data = [];
}
} else {
$data = [];
}
return true;
}
public function read($id)
{
if( is_null($this->database) ) {
$this->init();
}
$status = $this->database->get($this->recordKey, $data);
if ($status == \Aerospike::OK) {
$data = json_decode($data['bins']['PHP_SESSION'], 1);
if( !is_array($data) ) {
$data = [];
}
} else {
$data = [];
}
$_SESSION = $data;
return json_encode($data);
}
public function write($id, $dataNode)
{
if( is_null($this->database) ) {
$this->init();
}
$data = false;
if( $this->isConnected() ) {
$bins = [
"PHP_SESSION" => json_encode($_SESSION)
];
$status = $this->database->put( $this->recordKey, $bins );
if ($status == \Aerospike::OK) {
$data = true;
} else {
// error while saving data, log it
}
}
return $data;
}
public function destroy($id)
{
$removeStatus = false;
if( $this->isConnected() ) {
$status = $this->database->remove( $this->recordKey );
if ($status == \Aerospike::OK) {
$removeStatus = true;
} else {
// error while saving data, log it
}
}
return $removeStatus;
}
public function close(){
// Close the database connection
if($this->isConnected() && $this->database->close()){
// Return True
return true;
}
// Return False
return false;
}
public function gc($max)
{
return 0;
}
public function __destruct()
{
$this->close();
}
}
$s = new MySessionHandler();
// Set handler to overide SESSION
$newSession = session_set_save_handler($s, true);
var_dump($newSession); // this returns false sometimes and throws a warning
register_shutdown_function('session_write_close');
session_id(SESS_ID);
session_start();
$_SESSION['dfc'] = 'xdc1';
//unset($_SESSION['dfc']);
pr($_SESSION);
unset($s);
function pr($data) {
if( is_object($data) ) {
// $data = serialize($data);
}
echo '<pre>' . var_export($data, 1) . '</pre>';
}
These warnings are thrown intermittently and I am not sure what might be causing this. Any help would be appreciated.
The PHP client for Aerospike comes with a session handler. Set session.save_handler=aerospike. See the php.ini options of the module.
If you're writing your own session handler
Don't set session.save_handler=''. You want to comment it out in your php.ini instead, because that gets loaded and executed first (before your script) and it's non-sensical. There is no such handler. That should suppress the warning.
Watch out for null bytes. PHP strings can have those in the middle, but Aerospike strings will terminate there, so it'll get truncated at that point. Read the documentation on Handling Unsupported Types, which is why you're provided the \Aerospike\Bytes wrapper class.
I am developing an e-commerce website on which i need to store sessions inside database.I did that by implementing SessionHandlerInterface Class that is provided by the php itself.However it works totally fine and did store sessions inside the database , as well as read them properly.
However I am facing problem when I am using unset to unset a session variable.Sometimes it does work.Sometimes it doesn't.
For example:If i have a session variable by the name ABC unset might delete it from the database or it doesn't deletes the variable.
<?php
//inc.session.php
require_once 'RemoteAddress.php';
class SysSession implements SessionHandlerInterface
{
private $remote_write;
private $remote_read;
private $link;
private $ip_address_write;
private $ip_address_read;
public function open($savePath, $sessionName)
{
$link = new mysqli("localhost","root","","cakenbake");
if($link){
$this->link = $link;
return true;
}else{
return false;
}
}
public function close()
{
mysqli_close($this->link);
return true;
}
public function read($id)
{
$this->remote_read=new RemoteAddress();
$this->ip_address_read=$this->remote_read->getIpAddress();
$stmt=$this->link->prepare("SELECT `Session_Data`,`ip_address` FROM Session WHERE `Session_Id` = ? AND `Session_Expires` > '".date('Y-m-d H:i:s')."'");
$stmt->bind_param("s",$id);
$stmt->execute();
//$result = mysqli_query($this->link,"SELECT Session_Data FROM Session WHERE Session_Id = '".$id."' AND Session_Expires > '".date('Y-m-d H:i:s')."'");
/*$result=$this->link->prepare("Some query inside")
* This shows up an error stating prepare method not found
*
*/
$res=$stmt->get_result();
if($row=$res->fetch_assoc()){
if($this->ip_address_read==$row['ip_address'])
return $row['Session_Data'];
else return "";
}else{
return "";
}
}
public function write($id, $data)
{
$this->remote_write=new RemoteAddress();
$this->ip_address_write=$this->remote_write->getIpAddress();
if(!empty($data))
{
$DateTime = date('Y-m-d H:i:s');
$NewDateTime = date('Y-m-d H:i:s',strtotime($DateTime.' + 1 hour'));
$stmt=$this->link->prepare("REPLACE INTO Session SET Session_Id = ?, Session_Expires = '".$NewDateTime."', Session_Data = '".$data."', ip_address = '".$this->ip_address_write."'");
$stmt->bind_param("s",$id);
// $result = mysqli_query($this->link,"REPLACE INTO Session SET Session_Id = '".$id."', Session_Expires = '".$NewDateTime."', Session_Data = '".$data."'");
if($stmt->execute()){
return true;
}else{
return false;
}
}
}
public function destroy($id)
{
$stmt = $this->link->prepare("DELETE FROM Session WHERE Session_Id =?");
$stmt->bind_param("s",$id);
if($stmt->execute()){
return true;
}else{
return false;
}
}
public function gc($maxlifetime)
{
$result = $this->link->query("DELETE FROM Session WHERE ((UNIX_TIMESTAMP(Session_Expires) + ".$maxlifetime.") < UNIX_TIMESTAMP(NOW()))");
if($result){
return true;
}else{
return false;
}
}
}
$handler = new SysSession();
session_set_save_handler($handler, true);
?>
The above code stores and read sessions from the database.
Structure of the session table.
What could be the possible reason for this weird behaviour. Do i have to implement unset function as well?.
How should i resolve this problem?
If you could suggest me someother already written code for storing in database.That would work as well but i dont need any frameworks such as codeigniter and Yii2.
If you need more information regarding this problem.I will update my question.
Thanks in advance!
The problem is not with the unset function but with your write function.The write function is responsible for any updates that are made to the specific session id.
The wierd behiviour is not with the unset but it is with the write funciton you have implemented.
See ,the !empty constraint checks if your data is empty or not.What i can guess is that your database for that specific id must be empty after the removal of the specific variable .So the write tries to update your row with an empty value but with that constraint it isn't able to do so.
Just remove the !empty tag and it will work like a charm.
I have to subscribe to all the channels of my Redis db and simultaneously read data from another hash in the same db node. The following is the code I have written for this using phpredis:
$notif = new Worker;
try {
// connect redis
$notif->connectRedisServer();
// start the worker processing
$notif->startWorkerProcess();
}
catch(Exception $e) {
exit('Exception: '.$e->getMessage());
}
class Worker {
private $redis;
public $recentHash = 'tracker.data.recent';
public $notifHash = 'tracker.settings.data';
public $serverConnectionDetails = { //Redis server connection details
};
private $redis;
// redis database used for getting recent record of every imei
const RECENTDATA_DB = 1;
public function connectRedisServer() {
if(!empty($this->redis))
return $this->redis; // already connected
$this->redis = new Redis();
if(!$this->redis->pconnect(
$this->serverConnectionDetails['redis']['host'],
$this->serverConnectionDetails['redis']['port']
))
return 'Redis connection failed.';
if(isset($this->serverConnectionDetails['redis']['password'])
&&
!$this->redis->auth($this->serverConnectionDetails['redis']['password'])
)
return 'Redis authentication failed.';
return $this->redis;
}
public function startWorkerProcess() {
$this->redis->select(self::RECENTDATA_DB);
$this->redis->pSubscribe(array('*'), array($this, 'processRedisData'));
}
public function processRedisData($redis, $pattern, $chan, $msg) {
$message = rtrim((string) $msg,",");
$tData = json_decode($message, true);
$tId = (int) $tData['tID'];
echo "Recent Published Data:";
var_dump($tData);
$data = $this->getNotifSettings($tId);
if(!empty($data)) {
echo "Redis Settings: ";
var_dump($trackerSettings);
}
}
public function getNotifSettings($tId) {
$data = $this->redis->hGet($this->notifHash, $tId); //This command doesn't return any data even if it exists for the $tId
if($data === false)
return null;
$data = json_decode($data, true);
return $data; // Always comes up as null
}
}
The problem here is that once I get subscribed to all the channels on db1 in Redis. I don't get any result if I try to run HGET, even though the data for the given key exists in the db. I have put additional comments in the code above to explain where the problem is. Check getNotifSettings() function.
Any help will be appreciated.
I have an odd issue. The first time a visitor comes to the site and I set anything is the session, it doesn't stick. The second and all the following times I try to set something it sticks. After the initial try I can destroy the session and set something and it sticks. Its just the initial attempt to save something fails. I'm trying to save something to the session with $_SESSION['uid'] = $row["Id"];. I know the $row["Id"] is valid and holds data (I echoed it).
I am not using standard sessions. I am saving the session into a database. My session class is below. Is there anything I'm missing or doing wrong to explain this behavior?
Update:
Well I tested the session class on its own and it seems to be working :-/ But when I use it in my larger application _write never gets called, though __destruct does get called. Any idea why that may be?
<?php
include_once('db.php');
class PDOSession
{
protected $pdo;
protected $table = 'SessionData';
public function __construct()
{
// Get a database connection
$db = new PDOConnectionFactory();
$this->pdo = $db->getConnection(true);
// Start session
session_set_save_handler(array($this, '_open'),
array($this, '_close'),
array($this, '_read'),
array($this, '_write'),
array($this, '_destroy'),
array($this, '_gc'));
session_start();
}
public function __destruct()
{
session_write_close();
}
protected function fetchSession($id)
{
$stmt = $this->pdo->prepare('SELECT id, data FROM '.$this->table.' WHERE id = :id AND unixtime > :unixtime');
$stmt->execute(array(':id' => $id, ':unixtime' => (time() - (int)ini_get('session.gc_maxlifetime'))));
$sessions = $stmt->fetchAll();
return empty($sessions) ? false : $sessions[0];
}
public function _open($savePath, $sessionName)
{
return true;
}
public function _close()
{
return true;
}
public function _read($id)
{
$session = $this->fetchSession($id);
return ($session === false) ? false : $session['data'];
}
public function _write($id, $sessionData)
{
$session = $this->fetchSession($id);
if($session === false) {
$stmt = $this->pdo->prepare('INSERT INTO '.$this->table.' (id, data, unixtime) VALUES (:id, :data, :time)');
} else {
$stmt = $this->pdo->prepare('UPDATE '.$this->table.' SET data = :data, unixtime = :time WHERE id = :id');
}
$stmt->execute(array(
':id' => $id,
':data' => $sessionData,
':time' => time()
));
}
public function _destroy($id)
{
$stmt = $this->pdo->prepare('DELETE FROM '.$this->table.' WHERE id = :id');
$stmt->execute(array(':id' => $id));
}
public function _gc($maxlifetime)
{
$stmt = $this->pdo->prepare('DELETE FROM '.$this->table.' WHERE unixtime < :time');
$stmt->execute(array(':time' => (time() - (int) $maxlifetime)));
}
}
$newPDOSessionStartHere = new PDOSession();
I'm a bit of an idiot I guess. I was calling session_destroy() rather than session_unset() to clear things out at the top of my authentication script. The class works fine.
I think that you should start a session after you define your class. Not inside.
When storing sessions in a database, I have noticed that updating a session does not necessarily invoke the write function that was set in session_set_save_handler(). This problem seems to be solved when you call session_write_close() at the end of the script.
Am I missing something or is this indeed what needs to be done when storing sessions in a database using session_set_save_handler()?
The code that I am currently using.
// EXECUTE AT START OF SCRIPT
$session = new Session();
session_start();
Session Class
public function __construct() {
$database = Database::instance();
$this->dbh = $database->dbh();
$this->lifetime = get_cfg_var('session.gc_maxlifetime');
session_set_save_handler(
array(&$this, 'open'),
array(&$this, 'close'),
array(&$this, 'read'),
array(&$this, 'write'),
array(&$this, 'destroy'),
array(&$this, 'clean')
);
}
// SESSION HANDLER METHODS
function open($savePath, $sessionName) {
return true;
}
function close() {
return true;
}
function read($sessionUuid) {
logMessage('READ SESSION DATA > ' . $sessionUuid);
$data = array();
$time = time();
$sth = $this->dbh->prepare("SELECT sessionData FROM phpSession WHERE sessionUuid = :sessionUuid AND expires > " . $time);
$sth->setFetchMode(PDO::FETCH_ASSOC);
try {
$sth->execute(array('sessionUuid' => $sessionUuid));
if ($sth->rowCount() == 1) {
$row = $sth->fetch();
$data = $row['sessionData'];
}
} catch (PDOException $exception) {
// HANDLE EXCEPTION
}
return $data;
}
function write($sessionUuid, $sessionData) {
logMessage('WRITE SESSION DATA > ' . $sessionUuid);
$time = time() + $this->lifetime;
$sth1 = $this->dbh->prepare("SELECT sessionUuid FROM phpSession WHERE sessionUuid = :sessionUuid");
try {
$sth1->execute(array('sessionUuid' => $sessionUuid));
$query = '';
$data = array(
'sessionUuid' => $sessionUuid,
'sessionData' => $sessionData,
'expires' => $time
);
if ($sth1->rowCount() == 1) {
// UPDATE
$query = "UPDATE phpSession SET sessionUuid = :sessionUuid, sessionData = :sessionData, expires = :expires";
} else {
// INSERT
$query = "INSERT INTO phpSession (sessionUuid, sessionData, expires) VALUES (:sessionUuid, :sessionData, :expires)";
}
$sth2 = $this->dbh->prepare($query);
try {
$sth2->execute($data);
} catch (PDOException $exception) {
// HANDLE EXCEPTION
}
} catch (PDOException $exception) {
// HANDLE EXCEPTION
}
return true;
}
function destroy($sessionUuid) {
$sth = $this->dbh->prepare("DELETE FROM phpSession WHERE 'sessionUuid' = :sessionUuid");
try {
$sth->execute(array('sessionUuid' => $sessionUuid));
} catch (PDOException $exception) {
// HANDLE EXCEPTION
}
return true;
}
function clean() {
$sth = $this->dbh->prepare("DELETE FROM phpSession WHERE 'expires' < UNIX_TIMESTAMP()");
try {
$sth->execute();
} catch (PDOException $exception) {
// HANDLE EXCEPTION
}
return true;
}
According to http://php.net/manual/en/function.session-write-close.php
Session data is usually stored after your script terminated without the need to call session_write_close(), but as session data is locked to prevent concurrent writes only one script may operate on a session at any time.
Locking is usually the reason for not having your session written. This might happen in framed pages (as the documentation states) or in lengthy ajax requests where more than one request happens simultaneously.
For PHP < 5.4 you need to register session_write_close() as a shutdown function, so that the database write happens before the script execution cycle ends.
register_shutdown_function('session_write_close');