I am using a handler for php session where you save the session data in MongoDB. However, reloading the page in short intervals throws an error: "Failed to write session data (user)" and recommends checking the configuration of "session.save_path". This error also occurs after using "header ('Location: pagina-any.php')". Although this error does not harm the current data in the global "$ _SESSION" prevent new data from being inserted into the database (or updated). As I use sessions to save tokens used in forms validation and some requests this hinders ... I looked for references to this error, unsuccessful since the php manual only refers to this error in handling session logging to disk on the server. which is not my case because I'm saving for MongoDB.
In "php.ini" "session.save_habdler" and "session.save_path" omitted (commented).
MongoDB Version: 3.0.12
PHP Version: 7.0.8
Version of the mongodb extension: 1.1.8
I am already grateful for references, content for reading or clarification.
MongoCRUD.php
// define default content and charset (text/html and UTF-8)
header("Content-type: text/html; charset=UTF-8");
/**
* More info in:
* http://docs.php.net/set.mongodb
* http://mongodb.github.io/mongo-php-driver
*/
class MongoCRUD
{
// defaults
private $url = '127.0.0.1';
private $port = '27017';
private $base = 'local';
private $errors;
/**
* Private function for display errors (matchs in tray|catch blocks)
*
* Private variable "errors" define if this function retur errors (defaut - development)
*/
private function __ERRORSTRYCATCH__($e)
{
// check status code
if($this->errors == true){
$filename = basename(__FILE__);
echo "<br><br>Exception: " . $e->getMessage() . "<br>";
echo "In File: " . $e->getFile() . "<br>";
echo "In Line: " . $e->getLine() . "<br>";
}
}
private function wc()
{
// Construct a write concern
return new MongoDB\Driver\WriteConcern(MongoDB\Driver\WriteConcern::MAJORITY,100);
}
private function rp()
{
// Construct a read preference
return new MongoDB\Driver\ReadPreference(MongoDB\Driver\ReadPreference::RP_SECONDARY_PREFERRED,[]);
}
private function open()
{
return new MongoDB\Driver\Manager("mongodb://" . $this->url . ":" . $this->port . "/" . $this->base);
}
private function bulk()
{
return new MongoDB\Driver\BulkWrite;
}
private function query($filter = false)
{
if($filter){
return new MongoDB\Driver\Query($filter);
}else{
return new MongoDB\Driver\Query([]);
}
}
/**
* $database {string} set database for operation
* $options {array} set options.
*/
function __construct($database = false, $options = false)
{
// set default variables
$this->errors = $options['err'] ?? true;
// check if have database
if(!$database){
return false;
}else{
// define data base
$this->base = $database;
}
}
public function create($collection, $document)
{
// open manager
$manager = self::open();
// open bulk
$bulk = self::bulk(['ordered' => true]);
// bulk insert
$bulk->insert($document);
//
try{
// grab result
$result = $manager->executeBulkWrite($this->base . '.' . $collection, $bulk, self::wc());
// check
if($result->getInsertedCount() === 1){
return true;
}else{
return false;
}
}catch(MongoDB\Driver\Exception\Exception $e){
// shutdown error
self::__ERRORSTRYCATCH__($e);
return false;
}
}
public function reader($collection, $document = false, $filter = false)
{
// define document
$document = $document ? $document : [];
// define query
$query = self::query($filter) ?? self::query();
// open manager
$manager = self::open();
//
try{
// cursor
$cursor = $manager->executeQuery($this->base . '.' . $collection, $query, self::rp());
// store
$multiples = [];
// loop
foreach($cursor as $document){
array_push($multiples, $document);
}
// check if have result
if(count($multiples) == 0){
return false;
}else{
return $multiples;
}
}catch(MongoDB\Driver\Exception\Exception $e){
// shutdown error
self::__ERRORSTRYCATCH__($e);
return false;
}
}
public function update($collection, $filter, $newObj, $options = false)
{
// define options
if($options != false && is_array($options)){
$options = array_merge(["multi" => false, "upsert" => false], $options);
}else{
$options = ["multi" => false, "upsert" => false];
}
// open manager
$manager = self::open();
// open bulk
$bulk = self::bulk();
// bulk update
$bulk->update($filter, $newObj, $options);
//
try{
// grab result
$result = $manager->executeBulkWrite($this->base . '.' . $collection, $bulk, self::wc());
// check
if($result->getModifiedCount() >= 1){
return true;
}else{
return false;
}
}catch(Exception $e){
// shutdown error
self::__ERRORSTRYCATCH__($e);
return false;
}
}
public function delete($collection, $filter, $limit = false)
{
/**
* WARNING! $limit zero "0" delete all matches in collection! RISK ATENTION HERE!
* Default param is 0...
*/
$limit = (is_bool($limit) && $limit == true) ? ["limit" => 1] : ["limit" => 0];
// open manager
$manager = self::open();
// open bulk
$bulk = self::bulk();
// bulk delete
$bulk->delete($filter, $limit);
//
try{
// grab result
$result = $manager->executeBulkWrite($this->base . '.' . $collection, $bulk, self::wc());
// check
if($result->getDeletedCount() >= 1){
return true;
}else{
return false;
}
}catch(MongoDB\Driver\Exception\Exception $e){
// shutdown error
self::__ERRORSTRYCATCH__($e);
return false;
}
}
}
MongoHandlerSession.php
class HandlerSessionManager
{
private $dbCollection;
private $dbSession;
private $expire;
private $token;
private $clean;
public function __construct($options = false) {
// define name of colection (set session_name() or default session_name())
$this->dbCollection = $options['collection'] ?? session_name();
// define database for connection in MongoDB (if no have.. create.)
$this->dbSession = new MongoCRUD( ( $options[ 'databasename' ] ?? 'SessionManager' ) );
// define expiration time session (get default or choose (options or default 1 hour))
$this->expire = get_cfg_var( "session.gc_maxlifetime" ) ?? ( $options['expire'] ?? 3600 );
// define token (for request session values in another fonts e.g: in nodejs)
$this->token = $options[ 'token' ] ?? false;
// define if use auto_garbage function for clean old sessions (default false)
$this->clean = $options[ 'force_garbage' ] ? true : false;
// define name of session
session_name($this->dbCollection);
// set save handler
session_set_save_handler(
[ $this, 'open' ],
[ $this, 'close' ],
[ $this, 'read' ],
[ $this, 'write' ],
[ $this, 'destroy' ],
[ $this, 'gc' ]
);
// check if session already started
if(!isset($_SESSION)){
session_start();
}
// shutdown
@register_shutdown_function('session_write_close');
// auto call
self::auto_garbage();
}
public function open()
{
if($this->dbSession){
return true;
}else{
return false;
}
}
public function close()
{
$this->dbSession = null;
return true;
}
public function read($id)
{
$doc = $this->dbSession->reader( $this->dbCollection, ["_id" => $id] );
if(is_array($doc)){
return $doc[0]->sessionData;
}else{
return "";
}
}
public function write($id, $data)
{
$create = $this->dbSession->update( $this->dbCollection, ["_id" => $id], ['$set' => ["_id" => $id, "token" => $this->token, "sessionData" => $data, "expire" => ( time() + $this->expire )] ], ["upsert" => true] );
return $create;
}
public function destroy($id)
{
$result = $this->dbSession->delete( $this->dbCollection, ["_id" => $id] );
return $result;
}
public function gc()
{
$result = $this->dbSession->delete( $this->dbCollection, false, ["expire" => ['$lt' => time()]] );
return $result;
}
public function auto_garbage()
{
if($this->clean){
$this->dbSession->delete( $this->dbCollection, ["expire" => ['$lt' => time()]] );
}
}
public function __destruct()
{
@session_write_close();
}
}
index.php
require 'mongodb_/MongoCRUD.php';
require 'mongodb_/MongoHandlerSession.php';
$options = [
'databasename' => 'SessionManager',
'collection' => 'PHPSESSID',
'expire' => 1440, // it's don't is majority set. It is optional case php.ini no have value set
'token' => false, // 'YOUR_SUPER_ENCRYPTED_TOKEN'
'force_garbage' => true
];
// init class
new HandlerSessionManager($options);
// Usage
$_SESSION['foo'] = 'bar';