Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; Chris@0: Chris@0: /** Chris@0: * MongoDB session handler. Chris@0: * Chris@0: * @author Markus Bachmann Chris@0: */ Chris@0: class MongoDbSessionHandler implements \SessionHandlerInterface Chris@0: { Chris@0: /** Chris@0: * @var \Mongo|\MongoClient|\MongoDB\Client Chris@0: */ Chris@0: private $mongo; Chris@0: Chris@0: /** Chris@0: * @var \MongoCollection Chris@0: */ Chris@0: private $collection; Chris@0: Chris@0: /** Chris@0: * @var array Chris@0: */ Chris@0: private $options; Chris@0: Chris@0: /** Chris@0: * Constructor. Chris@0: * Chris@0: * List of available options: Chris@0: * * database: The name of the database [required] Chris@0: * * collection: The name of the collection [required] Chris@0: * * id_field: The field name for storing the session id [default: _id] Chris@0: * * data_field: The field name for storing the session data [default: data] Chris@0: * * time_field: The field name for storing the timestamp [default: time] Chris@0: * * expiry_field: The field name for storing the expiry-timestamp [default: expires_at] Chris@0: * Chris@0: * It is strongly recommended to put an index on the `expiry_field` for Chris@0: * garbage-collection. Alternatively it's possible to automatically expire Chris@0: * the sessions in the database as described below: Chris@0: * Chris@0: * A TTL collections can be used on MongoDB 2.2+ to cleanup expired sessions Chris@0: * automatically. Such an index can for example look like this: Chris@0: * Chris@0: * db..ensureIndex( Chris@0: * { "": 1 }, Chris@0: * { "expireAfterSeconds": 0 } Chris@0: * ) Chris@0: * Chris@0: * More details on: http://docs.mongodb.org/manual/tutorial/expire-data/ Chris@0: * Chris@0: * If you use such an index, you can drop `gc_probability` to 0 since Chris@0: * no garbage-collection is required. Chris@0: * Chris@0: * @param \Mongo|\MongoClient|\MongoDB\Client $mongo A MongoDB\Client, MongoClient or Mongo instance Chris@0: * @param array $options An associative array of field options Chris@0: * Chris@0: * @throws \InvalidArgumentException When MongoClient or Mongo instance not provided Chris@0: * @throws \InvalidArgumentException When "database" or "collection" not provided Chris@0: */ Chris@0: public function __construct($mongo, array $options) Chris@0: { Chris@0: if (!($mongo instanceof \MongoDB\Client || $mongo instanceof \MongoClient || $mongo instanceof \Mongo)) { Chris@0: throw new \InvalidArgumentException('MongoClient or Mongo instance required'); Chris@0: } Chris@0: Chris@0: if (!isset($options['database']) || !isset($options['collection'])) { Chris@0: throw new \InvalidArgumentException('You must provide the "database" and "collection" option for MongoDBSessionHandler'); Chris@0: } Chris@0: Chris@0: $this->mongo = $mongo; Chris@0: Chris@0: $this->options = array_merge(array( Chris@0: 'id_field' => '_id', Chris@0: 'data_field' => 'data', Chris@0: 'time_field' => 'time', Chris@0: 'expiry_field' => 'expires_at', Chris@0: ), $options); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function open($savePath, $sessionName) Chris@0: { Chris@0: return true; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function close() Chris@0: { Chris@0: return true; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function destroy($sessionId) Chris@0: { Chris@0: $methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteOne' : 'remove'; Chris@0: Chris@0: $this->getCollection()->$methodName(array( Chris@0: $this->options['id_field'] => $sessionId, Chris@0: )); Chris@0: Chris@0: return true; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function gc($maxlifetime) Chris@0: { Chris@0: $methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteOne' : 'remove'; Chris@0: Chris@0: $this->getCollection()->$methodName(array( Chris@0: $this->options['expiry_field'] => array('$lt' => $this->createDateTime()), Chris@0: )); Chris@0: Chris@0: return true; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function write($sessionId, $data) Chris@0: { Chris@0: $expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime')); Chris@0: Chris@0: $fields = array( Chris@0: $this->options['time_field'] => $this->createDateTime(), Chris@0: $this->options['expiry_field'] => $expiry, Chris@0: ); Chris@0: Chris@0: $options = array('upsert' => true); Chris@0: Chris@0: if ($this->mongo instanceof \MongoDB\Client) { Chris@0: $fields[$this->options['data_field']] = new \MongoDB\BSON\Binary($data, \MongoDB\BSON\Binary::TYPE_OLD_BINARY); Chris@0: } else { Chris@0: $fields[$this->options['data_field']] = new \MongoBinData($data, \MongoBinData::BYTE_ARRAY); Chris@0: $options['multiple'] = false; Chris@0: } Chris@0: Chris@0: $methodName = $this->mongo instanceof \MongoDB\Client ? 'updateOne' : 'update'; Chris@0: Chris@0: $this->getCollection()->$methodName( Chris@0: array($this->options['id_field'] => $sessionId), Chris@0: array('$set' => $fields), Chris@0: $options Chris@0: ); Chris@0: Chris@0: return true; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function read($sessionId) Chris@0: { Chris@0: $dbData = $this->getCollection()->findOne(array( Chris@0: $this->options['id_field'] => $sessionId, Chris@0: $this->options['expiry_field'] => array('$gte' => $this->createDateTime()), Chris@0: )); Chris@0: Chris@0: if (null === $dbData) { Chris@0: return ''; Chris@0: } Chris@0: Chris@0: if ($dbData[$this->options['data_field']] instanceof \MongoDB\BSON\Binary) { Chris@0: return $dbData[$this->options['data_field']]->getData(); Chris@0: } Chris@0: Chris@0: return $dbData[$this->options['data_field']]->bin; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Return a "MongoCollection" instance. Chris@0: * Chris@0: * @return \MongoCollection Chris@0: */ Chris@0: private function getCollection() Chris@0: { Chris@0: if (null === $this->collection) { Chris@0: $this->collection = $this->mongo->selectCollection($this->options['database'], $this->options['collection']); Chris@0: } Chris@0: Chris@0: return $this->collection; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Return a Mongo instance. Chris@0: * Chris@0: * @return \Mongo|\MongoClient|\MongoDB\Client Chris@0: */ Chris@0: protected function getMongo() Chris@0: { Chris@0: return $this->mongo; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Create a date object using the class appropriate for the current mongo connection. Chris@0: * Chris@0: * Return an instance of a MongoDate or \MongoDB\BSON\UTCDateTime Chris@0: * Chris@0: * @param int $seconds An integer representing UTC seconds since Jan 1 1970. Defaults to now. Chris@0: * Chris@0: * @return \MongoDate|\MongoDB\BSON\UTCDateTime Chris@0: */ Chris@0: private function createDateTime($seconds = null) Chris@0: { Chris@0: if (null === $seconds) { Chris@0: $seconds = time(); Chris@0: } Chris@0: Chris@0: if ($this->mongo instanceof \MongoDB\Client) { Chris@0: return new \MongoDB\BSON\UTCDateTime($seconds * 1000); Chris@0: } Chris@0: Chris@0: return new \MongoDate($seconds); Chris@0: } Chris@0: }