Marco Gasi
asked on
Can't save session data into the database
Hi everybody.
I'm trying to save PHP sessions to the database using mysqli but I'm struggking with an error I don't understand.
I use some classes autoloaded by Composer and everything worked fine until I have added the new classes Session and SessionModel. But first let me introduce the base class for DB connection, DbModel. Here the constructor:
All my ***Model classes extend this base class and they didn't give me any issues until now.
In Session class I have this:
And here the insterested code inSessionModel
The open function gives an error; Uncaught Error: Failed to open session: user
Forcing a true result of checkConnection method I get this
It looks like $this->db is not set but with the same code it is in every other ***Model class I'm using.
ANy idea.
Thank you for any help
I'm trying to save PHP sessions to the database using mysqli but I'm struggking with an error I don't understand.
I use some classes autoloaded by Composer and everything worked fine until I have added the new classes Session and SessionModel. But first let me introduce the base class for DB connection, DbModel. Here the constructor:
<?php
namespace Emgie\EmgieDB;
use Emgie\EmgieConfig\EmgieConfigurator;
require __DIR__ . '/../../vendor/autoload.php';
if (!class_exists('DB')) {
class DbModel
{
private $config;
protected static $conn = null;
protected $tableName;
public function __construct($tableName)
{
$this->tableName = $tableName;
$ec = new EmgieConfigurator();
$this->config = $ec->getConfig();
$this->connect();
}
private function connect()
{
if (!is_null(self::$conn)) {
// a connection has already been established
return;
}
self::$conn = new \mysqli($this->config->database->dbHost, $this->config->database->dbUserName, $this->config->database->dbPassword, $this->config->database->dbName);
if (self::$conn->connect_error) {
die("Connection failed: " . self::$conn->connect_errno . ' ' . self::$conn->connect_error);
}
self::$conn->set_charset('utf8');
}
}
}
All my ***Model classes extend this base class and they didn't give me any issues until now.
In Session class I have this:
<?php
namespace Emgie\EmgieUtils;
require __DIR__ . '/../../vendor/autoload.php';
/*code found at https://github.com/dominicklee/PHP-MySQL-Sessions*/
class Session
{
private $model;
public function __construct()
{
$this->model = new SessionModel();
// Set handler to overide SESSION
session_set_save_handler(
array($this, "_open"),
array($this, "_close"),
array($this, "_read"),
array($this, "_write"),
array($this, "_destroy"),
array($this, "_gc")
);
register_shutdown_function('session_write_close');
// Start the session
session_start();
}
public function _open()
{
return $this->model->checkConnection();
}
public function _read($id)
{
return $this->model->getSessionData($id);
}
}
And here the insterested code inSessionModel
<?php
namespace Emgie\EmgieUtils;
use Emgie\EmgieDB\DbModel;
require __DIR__ . '/../../vendor/autoload.php';
class SessionModel extends DbModel
{
protected $db;
public function __construct()
{
parent::__construct('sessions');
$this->db = parent::$conn;
}
public function checkConnection()
{
if (isset($this->db)) {
return true;
}
return false;
}
public function getSessionData($id)
{
$query = "SELECT data FROM sessions WHERE id = ?";
$stmt = $this->db->stmt_init(); //line 31
if ($stmt->prepare($query)) {
$stmt->bind_param("s", $id);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
$stmt->close();
return isset($row['data']) ? $row['data'] : '';
}
return '';
}
}
The open function gives an error; Uncaught Error: Failed to open session: user
Forcing a true result of checkConnection method I get this
Uncaught Error: Call to a member function stmt_init() on null in /***/admin/emgie/emgieUtils/SessionM odel.php:3 1
It looks like $this->db is not set but with the same code it is in every other ***Model class I'm using.
ANy idea.
Thank you for any help
ASKER
Hello David, thank you fro your help. Great suggestion! Now I know something more and I'm going to share it with you:
- the model in Session class is correctly initiated (not null)
- $this->db is an object in Model constructor...
- but $this->db is null in Model checkConnection()!
ASKER
I modified the Session class code in order to run the queries from within the class itself instead of use another class and this way above errors don't show up. But I get other errors!
First the new Session code:
Warning: session_start(): Session callback expects true/false return value in /var/www/vhosts/38999564.servicio-online.net/dev.bodegasferrera.es/admin/emgie/emgieUtils/Session.php on line 64
Warning: session_start(): Failed to read session data: user (path: /opt/alt/php72/var/lib/php/session) in /var/www/vhosts/38999564.servicio-online.net/dev.bodegasferrera.es/admin/emgie/emgieUtils/Session.php on line 64
line 64 is the one in constructor when session_start() is called.
Any idea?
First the new Session code:
<?php
namespace Emgie\EmgieUtils;
require __DIR__ . '/../../vendor/autoload.php';
require_once __DIR__ . '/../../ChromePhp.php';
use Emgie\EmgieDB\DbModel;
class Session
{
private $model;
private $db;
public function __construct()
{
$database = new DbModel('sessions');
/*I have added a getter to DbModel in order to access the static property $conn*/
$this->db = $database->getConnection();
// Set handler to overide SESSION
session_set_save_handler(
array($this, "_open"),
array($this, "_close"),
array($this, "_read"),
array($this, "_write"),
array($this, "_destroy"),
array($this, "_gc")
);
register_shutdown_function('session_write_close');
// Start the session
session_start();
}
public function _open()
{
if(is_null($this->db)){
\ChromePhp::log('db in _open is null');
return false;
}
\ChromePhp::log('db in _open is ok');
return true;
}
public function _close()
{
$this->db = null;
}
public function _read($id)
{
$query = "SELECT data FROM sessions WHERE id = ?";
$stmt = $this->db->stmt_init();
if ($stmt->prepare($query)) {
$stmt->bind_param("s", $id);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
$stmt->close();
\ChromePhp::log('$row[data]');
\ChromePhp::log($row['data']);
return $row['data'];
}
\ChromePhp::log('_read returns an empty string');
return '';
// return $this->model->getSessionData($id);
}
public function _write($id, $data)
{
$access = time();
$query = "REPLACE INTO sessions (id, access, data) VALUES (:id, :access, :data)";
if ($stmt = $this->db->prepare($query)) {
$stmt->bind_param("sis", $id, $access, $data);
return $stmt->execute();
}
return false;
}
public function _destroy($id)
{
$query = "DELETE FROM sessions WHERE id = :id";
$stmt = $this->db->prepare($query);
$stmt->bind_param("s", $id);
return $stmt->execute();
}
public function _gc($max)
{
$old = time() - $max;
$query = "DELETE FROM sessions WHERE access < :old";
$stmt = $this->db->prepare($query);
$stmt->bind_param("i", $old);
return $stmt->execute();
}
}
And now the warnings are:Warning: session_start(): Session callback expects true/false return value in /var/www/vhosts/38999564.servicio-online.net/dev.bodegasferrera.es/admin/emgie/emgieUtils/Session.php on line 64
Warning: session_start(): Failed to read session data: user (path: /opt/alt/php72/var/lib/php/session) in /var/www/vhosts/38999564.servicio-online.net/dev.bodegasferrera.es/admin/emgie/emgieUtils/Session.php on line 64
line 64 is the one in constructor when session_start() is called.
Any idea?
ASKER
Okay, here there is the new Session class. I have dropped the external SessionModel and merged the db logic.
<?php
namespace Emgie\EmgieUtils;
require __DIR__ . '/../../vendor/autoload.php';
require_once __DIR__ . '/../../ChromePhp.php';
class Session
{
private $db;
private $tableName;
public function __construct($tableName)
{
$database = new DbModel($tableName);
$this->tableName = $tableName;
$this->db = $database->getConnection();
session_set_save_handler(
array($this, "_open"),
array($this, "_close"),
array($this, "_read"),
array($this, "_write"),
array($this, "_destroy"),
array($this, "_gc")
);
register_shutdown_function('session_write_close');
session_start();
}
public function _open()
{
if (is_null($this->db)) {
\ChromePhp::log('db in _open is null');
return false;
}
\ChromePhp::log('db in _open is ok');
return true;
}
public function _close()
{
$this->db = null;
return true;
}
public function _read($id)
{
$query = "SELECT data FROM $this->tableName WHERE id = ?";
$stmt = $this->db->stmt_init();
if ($stmt->prepare($query)) {
$stmt->bind_param("s", $id);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
$stmt->close();
\ChromePhp::log('$row[data]');
\ChromePhp::log($row['data']);
if (is_null($row['data'])) {
return '';
}
return $row['data'];
}
\ChromePhp::log('_read returns an empty string');
return '';
}
public function _write($id, $data)
{
$access = time();
$query = "REPLACE INTO $this->tableName VALUES (?, ?, ?)";
$stmt = $this->db->stmt_init();
if ($stmt->prepare($query)) {
$stmt->bind_param("sis", $id, $access, $data);
return $stmt->execute();
}
return false;
}
public function _destroy($id)
{
$query = "DELETE FROM $this->tableName WHERE id = :id";
$stmt = $this->db->stmt_init();
if ($stmt->prepare($query)) {
$stmt->bind_param("s", $id);
return $stmt->execute();
}
}
public function _gc($max)
{
$old = time() - $max;
$query = "DELETE FROM $this->tableName WHERE access < :old";
$stmt = $this->db->stmt_init();
if ($stmt->prepare($query)) {
$stmt->bind_param("i", $old);
return $stmt->execute();
}
}
}
This time it looks to work but not really. when I call session_regenerate_id() I get this error:Uncaught Error: Failed to open session: userSo the question is now: how to use session_regenerate_id() function when using database to save sessions?
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
You're welcome!
Best to wrap your access of these objects + output an error about which one is NULL.
Based on which is NULL, you'll debug from there.