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.
Related
OK, I'm fully expecting someone to come along and tell me I missed something obvious, but I've spent days trying to figure out what's wrong, and I at this point, if someone can point out a stupid mistake, I'll just be glad to solve the problem. I'm just starting to figure out PDO, so I'm guessing the problem is there, but I really don't know.
This is mostly working. It opens, closes, reads, and does garbage collection. It will write, but only the id, not the data. Destroy is not working either, which seems to be related. If I put echo into the functions, the variables are being passed. The queries work when I try them directly. What seems to be happening is that the binding isn't working. I've tried both bindParam and bindValue. I've tried so many other things I can't even list them all. So anyway, here's the class:
class Sessions {
private $id;
private $data;
protected $pdo;
private $maxTime;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
// Set handler to overide SESSION
#session_set_save_handler(
array($this, "openSession"),
array($this, "closeSession"),
array($this, "readSession"),
array($this, "writeSession"),
array($this, "destroySession"),
array($this, "gcSession")
);
session_start();
}
public function openSession()
{
// If successful
if ($this->pdo) {
return true;
} else {
return false;
}
}
public function closeSession() {
// Close the database connection
if ($pdo = NULL) {
#session_write_close();
return true;
} else {
return false;
}
}
public function readSession($id) {
if (isset($id)) {
// Set query
$q = 'SELECT data FROM sessions WHERE id = :id';
$stmt = $this->pdo -> prepare($q);
// Bind the Id
$stmt -> bindValue(':id', $id);
$stmt -> execute();
$r = $stmt -> fetch();
// Return the data
if (!empty($r['data'])) {
return $r['data'];
} else {
// Return an empty string
return '';
}
} else {
return '';
}
}
// when session started and updated
public function writeSession($id, $data) {
// Set query
$q = "INSERT INTO sessions
(id, `data`, startTime)
VALUES (:id, :data, NOW())
ON DUPLICATE KEY UPDATE
`data` = :data2,
startTime = NOW()";
$stmt = $this->pdo->prepare($q);
// Bind data
$stmt->bindParam(':id', $id);
$stmt->bindParam(':data', $data);
$stmt->bindParam(':data2', $data);
$stmt->execute();
if ($stmt->rowCount()) {
return TRUE;
} else {
return FALSE;
}
}// when session started and updated
public function writeSession($id, $data) {
// Set query
$q = "INSERT INTO sessions (id, `data`, startTime) VALUES (:id, :data, NOW()) ON DUPLICATE KEY UPDATE `data` = :data2, startTime = NOW()";
$stmt = $this->pdo -> prepare($q);
// Bind data
$stmt->bindParam(':id', $id);
$stmt->bindParam(':data', $data);
$stmt->bindParam(':data2', $data);
$stmt->execute();
if ($stmt->rowCount()) {
return TRUE;
} else {
return FALSE;
}
}
public function destroySession($id) {
echo $id;
$q = "DELETE FROM `sessions` WHERE `id`= :id";
$stmt = $this->pdo -> prepare($q);
$stmt->execute(array(':id' => $id));
// Attempt execution
if ($stmt === true) {
echo " worked ";
return TRUE;
} else {
echo " no ";
return FALSE;
}
}
public function gcSession($maxTime) {
$q="DELETE FROM sessions WHERE (NOW()-startTime > $maxTime)";
$stmt = $this->pdo -> prepare($q);
if ($stmt->execute()) {
return TRUE;
} else {
return FALSE;
}
}
}
The function is being called this way:
// Store the user in the session and redirect:
$startSessions = new Sessions($pdo);
$_SESSION['data'] = 'IDnotOK';
$startSessions -> writeSession(session_id(), 'IDallOK');
$startSessions -> gcSession(1800);
The 'IDnotOK' is being sent to the database (sort of - it's 'data|s:7:"IDnotOK";'), instead of 'IDallOK'. If the $_SESSION['data'] isn't there, nothing is sent at all. The id is passed just fine. The timestamp also updates just fine. When I call:
$startSessions -> destroySession(session_id());
it doesn't matter how I try to pass the id, it will echo from within the function, but won't do anything to the database entry. Garbage collections works (probably because it only relies on the time and doesn't care about the id).
I wish I could list everything I've tried, but I've lost track. If there are any other questions that will help track down the problem, please ask. Thank You!
Watching this online tutorial about MYSQL's session handler and got really confused about this part:
table_XXX == Table XXX;
col_XXX == Column XXX;
sid == Session id
Read method:
public function read($session_id)
{
$this->db->exec('SET TRANSACTION ISOLATION LEVEL READ COMMITTED');
$this->db->beginTransaction();
/**
* the data is selected and no other ppl can interfere
* the writing process until COMMIT is reached
*/
$sql = "SELECT $this->col_expiry, $this->col_data
FROM $this->table_sess
WHERE $this->col_sid = :sid FOR UPDATE";
$selectStmt = $this->db->prepare($sql);
$selectStmt->bindParam(':sid', $session_id);
$selectStmt->execute();
$results = $selectStmt->fetch(\PDO::FETCH_ASSOC);
if ($results) {
if ($results[$this->col_expiry] < time()) {
// return empty if data out of date
return '';
}
return $results[$this->col_data];
}
return $this->initializeRecord($selectStmt);
}
Protected method:
protected function initializeRecord(\PDOStatement $selectStmt)
{
try {
$sql = "INSERT INTO $this->table_sess
($this->col_sid, $this->col_expiry, $this->col_data)
VALUES (:sid, :expiry, :data)";
$insertStmt = $this->db->prepare($sql);
$insertStmt->bindParam(':sid', $session_id);
$insertStmt->bindParam(':expiry', $this->expiry); // expiry is defined
$insertStmt->bindValue(':data', '');
$insertStmt->execute();
return '';
} catch(\PDOException $e) {
$this->db->rollBack();
throw $e;
}
}
Write method:
public function write($session_id, $data)
{
try {
$sql = "INSERT INTO $this->table_sess ($this->col_sid,
$this->col_expiry, $this->col_data)
VALUES (:sid, :expiry, :data)
ON DUPLICATE KEY UPDATE
$this->col_expiry = :expiry,
$this->col_data = :data";
$stmt = $this->db->prepare($sql);
$stmt->bindParam(':expiry', $this->expiry, \PDO::PARAM_INT);
$stmt->bindParam(':data', $data);
$stmt->bindParam(':sid', $session_id);
$stmt->execute();
return true;
} catch (\PDOException $e) {
if ($this->db->inTransaction()) {
$this->db->rollback();
}
throw $e;
}
}
In 'Protected method', line 8, there is a $session_id, and clearly no $session_id is passed to the protected method, so bindParam() for that line simply binded nothing?
So initializeRecord() simply initiated a row that has expiry time but nothing else? And then the sid and data is inserted after write method is called?
This is doing a lot of string-construction trickery with WHERE $this->col_sid = :sid and so forth, as it creates SQL statements.
You might try echoing or dumping those SQL statements to see what they contain right before you run ->execute() on them. That will help you troubleshoot.
It's pretty clear your protected method is missing $session_id. Is it possible there's a value for $this->sid you could use there?
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'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.
I'm storing sessions in a database and I want to delete all session rows for users when they log out as a security precaution (in case they're logged in on multiple computers).
My sessions table looks like this:
id | access | data
mj6u4v5rs3hqbo18o5p3ip9h45 2014-08-14 02:47:02 user_id|i:1;fb_123412341234_user_id|s:11:"12341243";.....
If a user logs in on multiple computers, then there will be multiple rows in the database. The only way I can see to associate the rows with the user is the user_id variable in the data column.
My question is, how do I delete all rows associated with a particular user ID?
Here is the session class:
namespace app;
class Session {
/** #var \PDO */
private $db;
function __construct(\PDO $db) {
$this->db = $db;
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 test() {
return 'hey';
}
public function _open() {
return true;
}
public function _close() {
return true;
}
public function _read($id) {
$stmt = $this->db->prepare('SELECT data FROM sessions WHERE id = :id');
makeQuery($stmt, array(':id' => $id));
$sRow = $stmt->fetch(\PDO::FETCH_ASSOC);
if (!empty($sRow)) {
return $sRow['data'];
}
else {
return '';
}
}
public function _write($id, $data) {
$stmt = $this->db->prepare('REPLACE INTO sessions VALUES (:id, UTC_TIMESTAMP(), :data)');
makeQuery($stmt, array(':id' => $id, ':data' => $data));
return true;
}
public function _destroy($id) {
$stmt = $this->db->prepare('DELETE FROM sessions WHERE id = :id');
makeQuery($stmt, array(':id' => $id));
return true;
}
public function _gc($maxLifetimeTimestamp) {
$maxLifetime = date('Y-m-d H:i:s', $maxLifetimeTimestamp);
$stmt = $this->db->prepare('DELETE FROM sessions WHERE (UTC_TIMESTAMP() - access) > :maxLifetime'); // #todo test this to ensure math is correct
makeQuery($stmt, array(':maxLifetime' => $maxLifetime));
return true;
}
}
In short, with your current setup you cannot do what you want to accomplish.
All of your classes are setup to work off of the id in the session table, which correlates with a specific browser.
The only way I see you being able to do it is by creating a secondary script (either extend the class or create a completely different script) that will look through the session table, selecting every row and unserializing the data entity. However, I don't recommend this for a production, heavy volume site.
Can I ask why you need to delete all user sessions on logout?
If user_id is always the very first token in the data, then you can do something like this:
DELETE FROM sessions WHERE SUBSTRING(data, 11, LOCATE(';', data) - 11) = {$user_id}
Here 11 is the length of "user_id|i:", so substring will return you the substring between the end of "user_id|i:" and the very first occurrence of the semicolon, which, if user_id always goes first, will be the user id.
If user_id is not necessarily the first token, then the function will be slightly harder (especially because your other variable, the fb111_user_id, has user_id as a substring), but still doable.
I think I found my own solution. I modified my sessions table to add a user_id column, and then modified my Session class like this:
public function _write($id, $data) {
$stmt = $this->db->prepare('REPLACE INTO sessions VALUES (:id, UTC_TIMESTAMP(), :data, :userId)');
makeQuery($stmt, array(':id' => $id, ':data' => $data, ':userId' => isset($_SESSION['user_id'])?$_SESSION['user_id']:null));
return true;
}
Then, when logging the user out I use this code:
session_destroy();
unset($_SESSION);
setcookie('PHPSESSID', '', time() - 48*3600, '/', null, null, true);
EDIT: then execute a query to delete all session rows from the database where user_id equals the user ID.
I'm still testing, but so far this seems to work!