2015-08-18 00:00:26 +00:00
< ? php
/*
* This file is part of the Symfony package .
*
* ( c ) Fabien Potencier < fabien @ symfony . com >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler ;
/**
2018-11-23 12:29:20 +00:00
* Session handler using the mongodb / mongodb package and MongoDB driver extension .
2015-08-18 00:00:26 +00:00
*
* @ author Markus Bachmann < markus . bachmann @ bachi . biz >
2018-11-23 12:29:20 +00:00
*
* @ see https :// packagist . org / packages / mongodb / mongodb
* @ see http :// php . net / manual / en / set . mongodb . php
2015-08-18 00:00:26 +00:00
*/
2018-11-23 12:29:20 +00:00
class MongoDbSessionHandler extends AbstractSessionHandler
2015-08-18 00:00:26 +00:00
{
private $mongo ;
/**
* @ var \MongoCollection
*/
private $collection ;
/**
* @ var array
*/
private $options ;
/**
* Constructor .
*
* List of available options :
* * database : The name of the database [ required ]
* * collection : The name of the collection [ required ]
* * id_field : The field name for storing the session id [ default : _id ]
* * data_field : The field name for storing the session data [ default : data ]
* * time_field : The field name for storing the timestamp [ default : time ]
2018-11-23 12:29:20 +00:00
* * expiry_field : The field name for storing the expiry - timestamp [ default : expires_at ] .
2015-08-18 00:00:26 +00:00
*
* It is strongly recommended to put an index on the `expiry_field` for
* garbage - collection . Alternatively it ' s possible to automatically expire
* the sessions in the database as described below :
*
* A TTL collections can be used on MongoDB 2.2 + to cleanup expired sessions
* automatically . Such an index can for example look like this :
*
* db .< session - collection >. ensureIndex (
* { " <expiry-field> " : 1 },
* { " expireAfterSeconds " : 0 }
* )
*
* More details on : http :// docs . mongodb . org / manual / tutorial / expire - data /
*
* If you use such an index , you can drop `gc_probability` to 0 since
* no garbage - collection is required .
*
2018-11-23 12:29:20 +00:00
* @ param \MongoDB\Client $mongo A MongoDB\Client instance
* @ param array $options An associative array of field options
2015-08-18 00:00:26 +00:00
*
* @ throws \InvalidArgumentException When MongoClient or Mongo instance not provided
* @ throws \InvalidArgumentException When " database " or " collection " not provided
*/
public function __construct ( $mongo , array $options )
{
2018-11-23 12:29:20 +00:00
if ( $mongo instanceof \MongoClient || $mongo instanceof \Mongo ) {
@ trigger_error ( sprintf ( 'Using %s with the legacy mongo extension is deprecated as of 3.4 and will be removed in 4.0. Use it with the mongodb/mongodb package and ext-mongodb instead.' , __CLASS__ ), E_USER_DEPRECATED );
}
2017-02-03 00:28:38 +00:00
if ( ! ( $mongo instanceof \MongoDB\Client || $mongo instanceof \MongoClient || $mongo instanceof \Mongo )) {
2015-08-18 00:00:26 +00:00
throw new \InvalidArgumentException ( 'MongoClient or Mongo instance required' );
}
if ( ! isset ( $options [ 'database' ]) || ! isset ( $options [ 'collection' ])) {
throw new \InvalidArgumentException ( 'You must provide the "database" and "collection" option for MongoDBSessionHandler' );
}
$this -> mongo = $mongo ;
$this -> options = array_merge ( array (
'id_field' => '_id' ,
'data_field' => 'data' ,
'time_field' => 'time' ,
'expiry_field' => 'expires_at' ,
), $options );
}
/**
* { @ inheritdoc }
*/
public function close ()
{
return true ;
}
/**
* { @ inheritdoc }
*/
2018-11-23 12:29:20 +00:00
protected function doDestroy ( $sessionId )
2015-08-18 00:00:26 +00:00
{
2017-02-03 00:28:38 +00:00
$methodName = $this -> mongo instanceof \MongoDB\Client ? 'deleteOne' : 'remove' ;
$this -> getCollection () -> $methodName ( array (
2015-08-18 00:00:26 +00:00
$this -> options [ 'id_field' ] => $sessionId ,
));
return true ;
}
/**
* { @ inheritdoc }
*/
public function gc ( $maxlifetime )
{
2018-11-23 12:29:20 +00:00
$methodName = $this -> mongo instanceof \MongoDB\Client ? 'deleteMany' : 'remove' ;
2017-02-03 00:28:38 +00:00
$this -> getCollection () -> $methodName ( array (
$this -> options [ 'expiry_field' ] => array ( '$lt' => $this -> createDateTime ()),
2015-08-18 00:00:26 +00:00
));
return true ;
}
/**
* { @ inheritdoc }
*/
2018-11-23 12:29:20 +00:00
protected function doWrite ( $sessionId , $data )
2015-08-18 00:00:26 +00:00
{
2017-02-03 00:28:38 +00:00
$expiry = $this -> createDateTime ( time () + ( int ) ini_get ( 'session.gc_maxlifetime' ));
2015-08-18 00:00:26 +00:00
$fields = array (
2017-02-03 00:28:38 +00:00
$this -> options [ 'time_field' ] => $this -> createDateTime (),
2015-08-18 00:00:26 +00:00
$this -> options [ 'expiry_field' ] => $expiry ,
);
2017-02-03 00:28:38 +00:00
$options = array ( 'upsert' => true );
if ( $this -> mongo instanceof \MongoDB\Client ) {
$fields [ $this -> options [ 'data_field' ]] = new \MongoDB\BSON\Binary ( $data , \MongoDB\BSON\Binary :: TYPE_OLD_BINARY );
} else {
$fields [ $this -> options [ 'data_field' ]] = new \MongoBinData ( $data , \MongoBinData :: BYTE_ARRAY );
$options [ 'multiple' ] = false ;
}
$methodName = $this -> mongo instanceof \MongoDB\Client ? 'updateOne' : 'update' ;
$this -> getCollection () -> $methodName (
2015-08-18 00:00:26 +00:00
array ( $this -> options [ 'id_field' ] => $sessionId ),
array ( '$set' => $fields ),
2017-02-03 00:28:38 +00:00
$options
2015-08-18 00:00:26 +00:00
);
return true ;
}
/**
* { @ inheritdoc }
*/
2018-11-23 12:29:20 +00:00
public function updateTimestamp ( $sessionId , $data )
{
$expiry = $this -> createDateTime ( time () + ( int ) ini_get ( 'session.gc_maxlifetime' ));
if ( $this -> mongo instanceof \MongoDB\Client ) {
$methodName = 'updateOne' ;
$options = array ();
} else {
$methodName = 'update' ;
$options = array ( 'multiple' => false );
}
$this -> getCollection () -> $methodName (
array ( $this -> options [ 'id_field' ] => $sessionId ),
array ( '$set' => array (
$this -> options [ 'time_field' ] => $this -> createDateTime (),
$this -> options [ 'expiry_field' ] => $expiry ,
)),
$options
);
return true ;
}
/**
* { @ inheritdoc }
*/
protected function doRead ( $sessionId )
2015-08-18 00:00:26 +00:00
{
$dbData = $this -> getCollection () -> findOne ( array (
$this -> options [ 'id_field' ] => $sessionId ,
2017-02-03 00:28:38 +00:00
$this -> options [ 'expiry_field' ] => array ( '$gte' => $this -> createDateTime ()),
2015-08-18 00:00:26 +00:00
));
2017-02-03 00:28:38 +00:00
if ( null === $dbData ) {
return '' ;
}
if ( $dbData [ $this -> options [ 'data_field' ]] instanceof \MongoDB\BSON\Binary ) {
return $dbData [ $this -> options [ 'data_field' ]] -> getData ();
}
return $dbData [ $this -> options [ 'data_field' ]] -> bin ;
2015-08-18 00:00:26 +00:00
}
/**
* Return a " MongoCollection " instance .
*
* @ return \MongoCollection
*/
private function getCollection ()
{
if ( null === $this -> collection ) {
$this -> collection = $this -> mongo -> selectCollection ( $this -> options [ 'database' ], $this -> options [ 'collection' ]);
}
return $this -> collection ;
}
/**
2015-10-08 18:40:12 +00:00
* Return a Mongo instance .
2015-08-18 00:00:26 +00:00
*
2017-02-03 00:28:38 +00:00
* @ return \Mongo | \MongoClient | \MongoDB\Client
2015-08-18 00:00:26 +00:00
*/
protected function getMongo ()
{
return $this -> mongo ;
}
2017-02-03 00:28:38 +00:00
/**
* Create a date object using the class appropriate for the current mongo connection .
*
* Return an instance of a MongoDate or \MongoDB\BSON\UTCDateTime
*
* @ param int $seconds An integer representing UTC seconds since Jan 1 1970. Defaults to now .
*
* @ return \MongoDate | \MongoDB\BSON\UTCDateTime
*/
private function createDateTime ( $seconds = null )
{
if ( null === $seconds ) {
$seconds = time ();
}
if ( $this -> mongo instanceof \MongoDB\Client ) {
return new \MongoDB\BSON\UTCDateTime ( $seconds * 1000 );
}
return new \MongoDate ( $seconds );
}
2015-08-18 00:00:26 +00:00
}