4
\$\begingroup\$
<?php
/**
*session.php
*danutz0501
*@copyright 2013
**/
//defined('BASE_PATH')||(header("HTTP/1.1 403 Forbidden")&die('403 - Acces direct interzis.'));
class session{

    private $db, $table_name = 'session_data', $session_name = 'PDOSESSID', $session_litetime = 1440;
    private static $instance = null;


    public static function init(PDO $pdo, $table_name = null, $session_name = null, $session_lifetime = null){
        if(self::$instance == null)
            self::$instance = new self($pdo, $table_name, $session_name, $session_lifetime);
        return self::$instance;
    }

    private function __construct($pdo, $table_name, $session_name, $session_lifetime){
        $this->db               = $pdo;
        $this->table_name       = (is_null($table_name))       ? $this->table_name       : $table_name;
        $this->session_name     = (is_null($session_name))     ? $this->session_name     : $session_name;
        $this->session_litetime = (is_null($session_lifetime)) ? $this->session_litetime : $session_lifetime;
        session_set_save_handler(
            [$this, 'open'], [$this, 'close'], [$this, 'read'], [$this, 'write'], [$this, 'destroy'], [$this, 'gc']
        );
        register_shutdown_function('session_write_close');
        ini_set('session_auto_start', 0);
        ini_set('session.cookie_httponly',1);
        ini_set('session.use_only_cookies',1);
        ini_set('session.gc_probability', 25);
        ini_set('session.gc_divisor', 100);
        ini_set('session.gc_maxlifetime', $this->session_litetime);
        session_name($this->session_name);
        session_start();
        $this->regenerate_id();
    }

    private function __clone(){}

    public function open($save_path, $session_name){
        return true;
    }

    public function close(){
        $this->gc($this->session_litetime);
        return true;
    }

    public function read($session_id){
       $session = $this->fetch_session($session_id);
        return ($session === false) ? false : $session['data'];
    }

    public function write($session_id, $session_data){
        $session = $this->fetch_session($session_id);
        if($session === false)
            $stmp = $this->db->prepare('INSERT INTO '.$this->get_table().' (id, data, unixtime) VALUES (:id, :data, :time)');
        else
            $stmp = $this->db->prepare('UPDATE '.$this->get_table().' SET data = :data, unixtime = :time WHERE id = :id');
        $stmp->execute([':id' => $session_id, ':data' => $session_data, ':time' => time()]);
    }

    public function destroy($session_id){
        $stmp = $this->db->prepare('DELETE FROM '.$this->get_table().' WHERE id = :id');
        $stmp->execute([':id' => $session_id]);
    }

    public function gc($session_lifetime){
        $stmp = $this->db->prepare('DELETE FROM '.$this->get_table().' WHERE unixtime < :time');
        $stmp->execute([':time' => (time() - (int) $session_lifetime)]);
    }

    private function get_table(){
        $table = $this->db->quote($this->table_name);
        return ($table) ? $table : $this->table_name;
    }

    private function regenerate_id(){
        $old_session = session_id();
        session_regenerate_id();
        $new_session = session_id();
        $stmp = $this->db->prepare('UPDATE '.$this->get_table().' SET id = :new_id WHERE id = :old_id');
        $stmp->execute([':new_id' => $new_session, ':old_id' => $old_session]);
    }

    private function fetch_session($session_id){
        $smtp = $this->db->prepare("SELECT id, data FROM ".$this->get_table()." WHERE id = :id AND unixtime > :unixtime");
        $smtp->execute(['id' => $session_id, 'unixtime' => (time() - (int) $this->session_litetime)]);
        $session = $smtp->fetchAll();
        return empty($session) ? false : $session[0];
    }

    public static function set($key, $value){
        if(!self::check_dot($key))
            $_SESSION[$key] = $value;
        else
            self::set_dot($_SESSION, $key, $value);
    }

    public static function get($key){
        if(!self::check_dot($key))
            return (isset($_SESSION[$key])) ? $_SESSION[$key] : false;
        else
            return self::get_dot($_SESSION, $key);
    }

    private static function set_dot(&$root, $key, $value){
        $keys = explode('.', $key);
        while(count($keys) > 1) {
            $key1 = array_shift($keys);
            if(!isset($root[$key1])) {
                $root[$key1] = [];
            }
            $root = &$root[$key1];
        }
        $key1 = reset($keys);
        $root[$key1] = $value;
    }

    private static function get_dot(&$root, $key){
        foreach(explode('.', $key) as $key1){
            if(!is_array($root) || !array_key_exists($key1, $root))
                return false;
            $root = &$root[$key1];
        }
        return $root;
    }

    public static function delete(){
        $_SESSION = [];
        if(isset($_COOKIE[session_name()])){
            $params = session_get_cookie_params();
            setcookie(session_name(), '', 1, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
        }
        session_destroy();
    }

    private static function check_dot($val){
        return (bool) (strpos($val, '.')) ? true : false;
    }

    public static function check_data($key, $val){
        foreach($key as $k){
            if(!array_key_exists($k, $_SESSION) || $_SESSION[$k] != array_shift($val))
                return false;
        }
        return true;
    }
}

I just want some opinions about my session class. How bad is it? What can I improve? What's wrong about my class? I am trying to use a dot syntax for writing multi-dimensional arrays. EDIT: including some calling code, really simple

$pdo = new PDO('sqlite:data.db');
$pdo->exec('CREATE TABLE IF NOT EXISTS session_data(id INT, data TEXT, unixtime INT)');
include 'session.php';
session::init($pdo);
session::set('abc1.abc2.abc3.abc4.abc5','test');
echo session::get('abc1.abc2.abc3.abc4.abc5');
session::set('abc1.abc2.abc31', 45);
echo '<br />'.session::get('abc1.abc2.abc31').'<pre>';
print_r($_SESSION);
\$\endgroup\$
1
  • \$\begingroup\$ How about including some calling code? \$\endgroup\$ Commented Jan 13, 2014 at 0:10

2 Answers 2

3
\$\begingroup\$

For one thing, to increase readability and cleanliness, try organizing your code a little better. You can read more on that here.

In init(), you can use if(is_null(self::$instance)) instead of if(self::$instance == null).

In __construct(), I don't believe your ternaries need parentheses around the is_null functions.

Hopefully you'll be adding to open() because right now it has no purpose. And then in your setter and getter, I suppose you could switch the if statement around so instead of if (!self::check_dot($key)) you'll have if (self::check_dot($key)).

Other than though, your connection looks good and you seem to be handling everything okay!

\$\endgroup\$
1
  • 2
    \$\begingroup\$ You've recently spiked in activity on this site! You should come join us in the chat room sometime! :) \$\endgroup\$
    – syb0rg
    Commented Feb 19, 2014 at 3:32
2
\$\begingroup\$

I was under the impression that the native session functions in the session handling class (open, close, read, write, destroy, gc) should all return values (read--string, the others bool). For instance, your read method returns inconsistent types (string or false), and write/destroy/gc methods return nothing.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ That is a nice observation. How about recommending a consistent behaviour, or showing the benefits and drawbacks of the respective approaches? That would make an even better answer ;) \$\endgroup\$
    – Vogel612
    Commented May 18, 2014 at 18:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.