MongoDB, PHP

MongoDB PHP Session Handler

<?php

use MongoDB\Driver\Query;
use MongoDB\Driver\BulkWrite;
use MongoDB\BSON\UTCDateTime;

class Session
{
    /**
     * @var example: 'db.sessions'
     */
    public $collection = 'db.sessions';
    public $lifeTime;

    private $db;


    /**
     * This houses the actual save handler (session_set_save_handler) which then houses an array telling the sessions how to save (what methods to use)
     * @param $db The DB object you have from your application, in this case a MongoDB\Driver\Manager object
     */
    public function __construct($db)
    {
        // Read the maxlifetime setting from PHP
        $this->life_time = get_cfg_var("session.gc_maxlifetime");

        // Register this object as the session handler
        session_set_save_handler(
            [$this, "open"],
            [$this, "close"],
            [$this, "read"],
            [$this, "write"],
            [$this, "destroy"],
            [$this, "gc"]
        );

        //read in the db object my api uses
        $this->db = $db;
    }

    /**
     * This opens the session for reading or writing
     * @param $save_path
     * @param $session_name
     * @return bool
     */
    public function open($save_path, $session_name)
    {
        global $sess_save_path;
        $sess_save_path = $save_path;
        // Don't need to do anything. Just return TRUE.
        return true;

    }

    /**
     * @return bool
     */
    public function close()
    {
        return true;
    }

    /**
     * This reads all your session data from the table into $_SESSION whilst checking the session has not expires. Also makes a new session if needed.
     * @param $id
     * @return string
     */
    public function read($id)
    {
        // Set empty result
        $data = '';

        // Fetch session data from the selected database
        $time = time();

        $result = $this->db->executeQuery(
            $this->collection,
            new Query(
                [
                    'session_id' => $id,
                    'expires' => ['$gt' => $time]
                ],
                [
                    'limit' => 1
                ]
            )
        )->toArray();

        if ($result) {
            $data = $result[0]->session_data;
        }
        return $data;
    }

    /**
     * Writes all your $_SESSION data to database
     * @param $id
     * @param $data
     * @return integer
     */
    public function write($id, $data)
    {
        // Build query
        $time = time() + $this->life_time;

        $bulk = new BulkWrite();

        if (isset($_SESSION['uid'])) {
            $bulk->update(
                [
                    'session_id' => $id
                ],
                [
                    '$set' => [
                        'user_id' => $_SESSION['uid'],
                        'session_data' => $data,
                        'expires' => $time
                    ]
                ]
            );
        } else {
            $bulk->update(
                [
                    'session_id' => $id
                ],
                [
                    '$set' => [
                        'session_data' => $data,
                        'expires' => $time
                    ]
                ]
            );
        }

        return $this->db->executeBulkWrite($this->collection, $bulk)->getModifiedCount();
    }

    /**
     * This is complex because normally you can just use session_destroy or whatever but due to a bug in PHP which is meant to be fixed you had to logout the user manually. Best to look at my user class for help on this. This is most likely fixed but have never double checked.
     * @param $id
     * @return integer
     */
    public function destroy($id)
    {
        $bulk = new BulkWrite();
        $bulk->delete(['session_id' => $id]);
        return $this->db->executeBulkWrite($this->collection, $bulk)->getDeletedCount();
    }

    /**
     * This will delete any old sessions which should no longer exist. For this to work properly you must set carbage collection within your php.ini to 100% of the time. This is done within the session.gc_probability value in your php.ini, set it to 100 for 100%
     * @return integer
     */
    public function gc()
    {
        $bulk = new BulkWrite();
        $bulk->delete(['expires' => ['$lt' => new UTCDateTime(time() * 1000)]]);
        return $this->db->executeBulkWrite($this->collection, $bulk)->getDeletedCount();
    }
}
Advertisements

8 thoughts on “MongoDB PHP Session Handler

  1. Common man. PHP 6 is almost out but you’re still using old school constructors (use __constructor instead of function session).
    Also you don’t need an amp in this case:
    array( &$this, “open” )
    because all objects are passed by reference since PHP5 (I think).
    Also I’m not quite sure why you can’t just do this in gc:
    $this->mongo_db->remove(array(“expires” => array(‘$lt’ => $time));
    Why do you need this ‘active’ flag?
    And of course since mongo loves wasting space whenever it cans instead of session_id, session_data and expires I’d just use s,d and e. Will save you couple KBs 🙂

    1. You’ll be surprised how useful function constructors can be. Take a MVC framework. SAay you need a global function for a controller but as usual to override the main constructor would mean to do:

      function __constructor(){
      parent::__constructor();
      }

      This can sometimes change the default required behaviour of some functions within the base controller class and can create undesired effects. To overcome this you can use a function constructor like so:

      function session(){
      //no need to call or potientially change parent behaviour 🙂
      }

      But yea your right in that particular class it is best to use a __constructor method to assign default behaviour.

      1. Any descent MVC will have a preDispatch and postDispatch callbacks for that purpose. You should avoid overriding default constructor behavior unless you really have to. And sometimes they would not even let you do it by making the constructor final.

    2. I don’t delete due to how my sessions work. If I delete a session the only way to ressurect session data would be to store it on user machine in the form of cookies and just trust that their cookies are right. To allow for the most accurate form of judging whether a user is online atm or not I have made session lifetime about 2 mins. This, from many trail and error methods is the most reliable way in PHP to understand if a user has lef the site or not. Your right I could use a physical program like facebook does to understand online users etc, and I might actually but yea I just made this one to work only with PHP.

      I think the amp is what wordpress actually did to my code, it seems to change it once every so oftern, thanks for the heads up.

      I instead run a cronjob that does the job after say a day of inactivity.

    3. “Any descent MVC will have a preDispatch and postDispatch callbacks for that purpose. You should avoid overriding default constructor behavior unless you really have to. And sometimes they would not even let you do it by making the constructor final.”

      Noted :), I’ll update my own knowledge to reflect. I did actually think of doing that way using callbacks in my own mvc at first but I dunno why I just chose to use function constructors instead. Though it looked cleaner.

    4. I did find a slight problem with your constructor idea. I recoded this today and found out php physically does not support a __constructor within the session handler class. Had to use function constructor instead. Not so old school after all :P. Found theres a lot more php core override classes that cannot take __constructor functions either.

      I knew there was a reason why I didn’t use __constructor originally.

  2. Because it’s not constructor, it’s construct 🙂

    <?php

    class SessionHandler
    {
    public function __construct($argument)
    {
    echo "[Constructed: {$argument}]" . '’;

    session_set_save_handler(
    array($this, ‘open’),
    array($this, ‘close’),
    array($this, ‘read’),
    array($this, ‘write’),
    array($this, ‘destroy’),
    array($this, ‘gc’)
    );
    }

    public function __destruct()
    {
    echo ‘[Destructed]’ . ”;
    }

    public function open() { echo ‘open’ . ”; }
    public function close() { echo ‘close’ . ”; }
    public function read($sid) { echo “read {$sid}”. ”; }
    public function write($sid, $data) { echo “write [{$sid}: {$data}]” . ”; }
    public function destroy() { echo ‘destroy’ . ”; }
    public function gc() { echo ‘gc’ . ”; }
    }

    // setup
    $sh = new SessionHandler(‘arg_data’);
    session_start();

    1. Oh oops I must have been high or something lol. Yea retried it and it worked dunno why I wrote __constructor(). Thanks 🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s