Session class ideas?

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Session class ideas?

Post by alex.barylski »

I'm in the process of designing a generic session class.

I'm stuck between using built in functionality or going homemade all the way...

Before I can ultimately decide which is the best solution, I need some advice...

1) $_SESSION super global - When I assign scalars, objects, etc to session variables, do they get persisted/restored as the builtin functions are called?

What I mean is...when you create a new session, store values in the $_SESSION global and persist those changes to a medium...and then have another script start up, restore the $_SESSION global, does the array restore exactly to how it was before?

Thats kind of cool if it does work that way...

Also...how do I control how the session magic cookie is stored?

What I mean by this is, sessions are linked with browser instances in 2 ways:
1) Client side cookie
2) SESSION ID persisted through site via $_GET

I like the idea of using $_GET as it's gaurnateed to work on most number of browsers, incase cookies are off...but that also makes the sessions truely temporary...whereas cookies can be set for given periods...which is nice also...

How does the PHP session management functions help you deal with this?

I suppose you could use one over the other based on a config setting???

I want to wrap this session management functionality into a class and use generic accessor/mutator for $_SESSION data...

Any suggestions on design? I have a idea, but i'm open to suggestions?

Arborint, seeing how your the resident design pattern geek...can you think of any which would help or benefit my design any?

I'm thinking singleton would be a good start as it doesn't make sense to have more than one instance? At least at first though anyways...

I'll be honest I haven't spent alot of time thinking about a solid OOP design, at most 15 minutes :oops: so if I sound lazy it's likely because I am :P

Ideas, suggestions, etc...???
User avatar
RobertGonzalez
Site Administrator
Posts: 14293
Joined: Tue Sep 09, 2003 6:04 pm
Location: Fremont, CA, USA

Post by RobertGonzalez »

Do want to the class to be database independent, meaning not needing a database to interact with?
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post by alex.barylski »

Everah wrote:Do want to the class to be database independent, meaning not needing a database to interact with?
I've included that as a requirement yes...

I accomplish this via datastore drivers like AdoDB, etc...

Why do you ask?

Edit: I am familiar with the session_set_save_handler incase thats where your going :P
User avatar
RobertGonzalez
Site Administrator
Posts: 14293
Joined: Tue Sep 09, 2003 6:04 pm
Location: Fremont, CA, USA

Post by RobertGonzalez »

I was asking because I tend to develop a small session handling class using database stored sessions. I was wondering if you were considering that as well, or if you were thinking of session data storage in flat files, etc.
User avatar
nielsene
DevNet Resident
Posts: 1834
Joined: Fri Aug 16, 2002 8:57 am
Location: Watertown, MA

Re: Session class ideas?

Post by nielsene »

Hockey wrote:I'm in the process of designing a generic session class.

I'm stuck between using built in functionality or going homemade all the way...

Before I can ultimately decide which is the best solution, I need some advice...

1) $_SESSION super global - When I assign scalars, objects, etc to session variables, do they get persisted/restored as the builtin functions are called?
Yes, with a caveat. You'll need to implement the __sleep and __wakeup methods for your classes for them to saved by the session. (If you're used to java, its basically making your classes serializable to a simple text format.)

Also...how do I control how the session magic cookie is stored?

What I mean by this is, sessions are linked with browser instances in 2 ways:
1) Client side cookie
2) SESSION ID persisted through site via $_GET

I like the idea of using $_GET as it's gaurnateed to work on most number of browsers, incase cookies are off...but that also makes the sessions truely temporary...whereas cookies can be set for given periods...which is nice also...

How does the PHP session management functions help you deal with this?

I suppose you could use one over the other based on a config setting???
Yes, this is all controllable via php.ini parameters. The defaults on most servers is to try to use cookies, and if cookies aren't accepted to fall back to GET(and URL rewriting). Typically on the first visit it uses both options as it can't know if the cookie will be accepted before rendering the page. However it possible to turn off one or the other. Its possible to require the cookie to be sent over SSL even, if that's the way your site is set-up.

The default php session handling is actually quote good, and if you're new to sessions I'd suggest sticking with it and building around it in small amounts as your application/designs demands it. There are a few places where I've been "annoyed" by not being able to "hijack" the default process early enough to add a few extra security measures to it, easily, but they start to get into the esoteric.
I want to wrap this session management functionality into a class and use generic accessor/mutator for $_SESSION data...

Any suggestions on design? I have a idea, but i'm open to suggestions?

Arborint, seeing how your the resident design pattern geek...can you think of any which would help or benefit my design any?

I'm thinking singleton would be a good start as it doesn't make sense to have more than one instance? At least at first though anyways...

I'll be honest I haven't spent alot of time thinking about a solid OOP design, at most 15 minutes :oops: so if I sound lazy it's likely because I am :P

Ideas, suggestions, etc...???
I'd agree that it probably wants to be a singleton. I'm always torn on the need for a Session class, however; Its almost too trivial. I think a lot of people fold it into a more generic Request class that also handles the access to GET/POST/COOKIE as need by your application. Of course SESSION is the only one that needs write access of those (and that's why I tend to put it somewhere else, but I tend to be different.....)

I do use a Session class when I tack on my extra security stuff as it needs to wrap the built-in functions. I don't tend to put the functions for custom session handlers (DB) into the Session class, but would rather pass in a "handler" object to the session constructor if I want to reaplce the built-in file based system.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

Here is a very simple PHP4 session class that does some basic things. It doesn't start the session until it is actually accessed, so no need to deal with headers sent for every code path possible for a request, has support for close before redirects, namespaces session to hopefully avoid clashes with other code, etc. It does not allow clean access to hierarchical vars which a PHP5 version might do. It is sort of a singleton in that it accesses the superglobal. It does not deal with objects, but probably some combo of __autoload() and having object serialize certain data is best for that. This class is far from full featured, but maybe a starting point for discussion session classes.

Code: Select all

class Session {
    var $namespace;
    
    function Session($namespace='Session') {
        $this->namespace = $namespace;
    }

    function start() {
        if (session_id() == '') {
            if (strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE')) {
                session_cache_limiter('must-revalidate');
            }
            session_start();
        }
    }

    function get($name) {
        $this->start();
    	return (isset($_SESSION[$this->namespace][$name]) ? $_SESSION[$this->namespace][$name] : null);
    }

    function set($name, $value) {
        $this->start();
    	$_SESSION[$this->namespace][$name] = $value;
    }

    function has($name) {
        $this->start();
    	return isset($_SESSION[$this->namespace][$name]);
    }

    function close() {
        session_write_close();
    }

}
(#10850)
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post by alex.barylski »

Everah wrote:I was asking because I tend to develop a small session handling class using database stored sessions. I was wondering if you were considering that as well, or if you were thinking of session data storage in flat files, etc.
Hmmm...I see...so you were interested in swapping notes?

I don't have much right now as I've spent only minutes (at time of writting) thinking about design...I figured I'd get a community opinion before formalizing some design...

But I'm will to see what you have as an API, etc if you care to post it???

Cheers :)
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Re: Session class ideas?

Post by alex.barylski »

Yes, with a caveat. You'll need to implement the __sleep and __wakeup methods for your classes for them to saved by the session.
I was under the impression that was only required if you needed to perform some last minute cleanup/init code on the data members before serialization??? :?
User avatar
nielsene
DevNet Resident
Posts: 1834
Joined: Fri Aug 16, 2002 8:57 am
Location: Watertown, MA

Re: Session class ideas?

Post by nielsene »

Hockey wrote:
Yes, with a caveat. You'll need to implement the __sleep and __wakeup methods for your classes for them to saved by the session.
I was under the impression that was only required if you needed to perform some last minute cleanup/init code on the data members before serialization??? :?
I beleive that's correct; last time I was playing with storing objects into the session, they always had references to DB or file handles, so I had extra issues to worry about.
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post by alex.barylski »

Care to share listen to me i'm a poet and I didn't even know it your session class publicly? :P

I hate sifting through phpclasses code... :?
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post by alex.barylski »

arborint wrote:Here is a very simple PHP4 session class that does some basic things. It doesn't start the session until it is actually accessed, so no need to deal with headers sent for every code path possible for a request, has support for close before redirects, namespaces session to hopefully avoid clashes with other code, etc. It does not allow clean access to hierarchical vars which a PHP5 version might do. It is sort of a singleton in that it accesses the superglobal. It does not deal with objects, but probably some combo of __autoload() and having object serialize certain data is best for that. This class is far from full featured, but maybe a starting point for discussion session classes.

Code: Select all

class Session {
    var $namespace;
    
    function Session($namespace='Session') {
        $this->namespace = $namespace;
    }

    function start() {
        if (session_id() == '') {
            if (strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE')) {
                session_cache_limiter('must-revalidate');
            }
            session_start();
        }
    }

    function get($name) {
        $this->start();
    	return (isset($_SESSION[$this->namespace][$name]) ? $_SESSION[$this->namespace][$name] : null);
    }

    function set($name, $value) {
        $this->start();
    	$_SESSION[$this->namespace][$name] = $value;
    }

    function has($name) {
        $this->start();
    	return isset($_SESSION[$this->namespace][$name]);
    }

    function close() {
        session_write_close();
    }

}
Just curious, but what does:

Code: Select all

session_cache_limiter('must-revalidate');
do?
Gambler
Forum Contributor
Posts: 246
Joined: Thu Dec 08, 2005 7:10 pm

Post by Gambler »

My advice: do not write session wrapper, write session handler from scratch. Will save you a lot of headache.

Untested class from my framework (gee, I need to upload this stuff to my website):

Code: Select all

<?php
class ASession{
    const COOKIE_NAME= 'SID';
    
    private static $_id = '';
    private static $_data = array();
    private static $_create = FALSE;
    private static $_update = FALSE;
    
    static function begin(){
        $sessionId = sha1(uniqid(rand(), TRUE));
        $expireDate = NULL;
        $config = Config::get('Session');
        if ($config['sessionsLife'] != NULL) {
            $expireDate = time() + $config['sessionsLife'];
        }
        setcookie(self::COOKIE_NAME, $sessionId, $expireDate);
        self::$_id = $sessionId;
        self::$_create = TRUE;
    }
    
    static function end(){
        if (empty(self::$_id)) {
            return FALSE;
        }
        
        $id = mysql_real_escape_string(self:$_id);
        $query = "DELETE FROM sessions
            WHERE id = '$id'";
        $result = mysql_query($query);
        if (!$result) {
            throw new Exception("Invalid query: [<i>$query</i>] ".mysql_error());
        }
        
        setcookie(self::ID, FALSE);
    }
    
    static function load(){
        if (empty($_COOKIE[self::COOKIE_NAME])) {
            return;
        } else {
            self::$_id = $_COOKIE[self::COOKIE_NAME];
        }
        
        $id = mysql_real_escape_string(self::$_id);
        $query = "SELECT s.id AS id, 
            s.data AS data, 
            s.userId AS userId,
            s.ip AS ip
            FROM sessions AS s
            WHERE s.id = '$id'";
        $result = mysql_query($query);
        if (!$result) {
            throw new Exception("Invalid query: [<i>$query</i>] ".mysql_error());
        }
        $row = mysql_fetch_assoc($result);
        if ($row != NULL) {
            self::$_data = unserialize($row['data']);
        }
    }
    
    static function store(){
        if (empty(self::$_id)) {
            return FALSE;
        }
        
        if (self::$_create) {
            $id = mysql_escape_string(self::$_id);
            $data = mysql_escape_string(serialize(self::$_data));
            $query = "INSERT INTO sessions
                (id, usedFirst, usedLast, data)
                VALUES ('$sessionId', NOW(), NOW(), '$data')";
            $result = mysql_query($query);
            if (!$result) {
                throw new Exception("Invalid query: [<i>$query</i>] ".mysql_error());
            }
            if (mysql_affected_rows() > 0) {
                return TRUE;
            }// else {
                return FALSE;
            //}
        } elseif (self::$_update) {
            $id = mysql_escape_string(self::$_id);
            $data = mysql_escape_string(serialize(self::$_data));
            $query = "UPDATE sessions
                SET data = '$data',
                usedLast = NOW()
                WHERE id = '$id'";
            $result = mysql_query($query);
            if (!$result) {
                throw new Exception("Invalid query: [<i>$query</i>] ".mysql_error());
            }
            if (mysql_affected_rows() > 0) {
                return TRUE;
            }// else {
                return FALSE;
            //}
        }
    }
    
    static function prune(){
        $config = Config::get('Session');
        $chance = $config['pruneSessionsChance'];
        if ($chance < 0) { return; } //zero chance disables prunning
        $dice = $config['pruneSessionsDice'];
        if ($dice >= $chance) { //dice smaller or equal to chance makes prunning to occur every time 
            if ($chance < rand(1, $dice)) { //determines when pruning does NOT occure
                return;
            }
        }
        $query = "DELETE FROM sessions
            WHERE usedFirst < NOW() - $config[sessionsLife]
            OR usedLast < NOW() - $config[sessionsThreshold']";
        $result = mysql_query($query);
        if (!$result) {
            throw new Exception("Invalid query: [<i>$query</i>] ".mysql_error());
        }
        return mysql_affected_rows();
    }
    
    private $_name;
    
    function __construct($name){
        $this->_name = $name;
    }
    
    function __get($attr){
        return self::$_data[$this->_name][$attr];
    }
    
    function __set($attr, $value){
        self::$_data[$this->_name][$attr] = $value;
        self::$_updated = TRUE;
    }
}
?>
Example:

Run1{
ASession::load();
//session is NOT loaded, because there is no cookie
ASession::store();
}

Run2{
ASession::load();
ASession::begin(); //sends cookie header. record will be created during store() call
ASession::store();
}

Run3{
ASession::load();
//session is loaded
$session = new ASession(__CLASS__); //to avoid name collisions
bla($session->variable);
$session->variable = 'bla'; //now record will be updated during store() call
ASession::store();
}

Run4{
ASession::load();
ASession::end(); //deletes everything
ASession::store();
}
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post by alex.barylski »

My advice: do not write session wrapper, write session handler from scratch. Will save you a lot of headache.
Well...what headaches are you refering too? :?

I've encountered a few, but wouldn't mind hearing someone else's stories...
Gambler
Forum Contributor
Posts: 246
Joined: Thu Dec 08, 2005 7:10 pm

Post by Gambler »

For example, if you use custom session-handling functions, the write function is called during script termination, so you can't debug it using echo.

PHP uses GET variable to store SID, which is sometimes undesirable. (Prone to session hijacking.) Disabling it is quite tricky, since GET is used only if cookies are not available, and there are countless ini options.

PHP tends to send session cookies to all users after you call session_start(). Personally, I like when user gets cookie only when there is real need for it. That's why my class has separate begin() and load() methods.

It's difficult to fine-tune session GC with built-in sessions, since they deal only with lifetime. You might want to delete sessions that weren't used for a certain time. For example, you might have session lifetime of one month, but sessions could be also GCed if user doesn't show up for a week.

Older versions of PHP do not have session_regenerate().

Built-in session handler uses serialize() and unserialize() intenally. If you want to use something else in you override methods, you have to work around that.

Etcetra.
alex.barylski
DevNet Evangelist
Posts: 6267
Joined: Tue Dec 21, 2004 5:00 pm
Location: Winnipeg

Post by alex.barylski »

Pretty much the same problems I've been experiencing, but we deal with them differently..
For example, if you use custom session-handling functions, the write function is called during script termination, so you can't debug it using echo.
You can expedite the write process by calling session_write_close() which I've just had to use in order for my remove() to work properly :)
PHP uses GET variable to store SID, which is sometimes undesirable. (Prone to session hijacking.) Disabling it is quite tricky, since GET is used only if cookies are not available, and there are countless ini options.
Thats the one thing that influenced me to use the built in functionality over a homemade version...session propagation is alot of work...well not really...but more than I was willing to invest unless I had too :)

Curious though, if you don't have cookies...and you are afraid of using get, how in the world to you plan on persisting session state?

You can't rely on IP addresses...so if not cookies or persistance through a GET/POST request, then how?

I do believe there is a setting in the INI file which makes GET more secure by not appending any SID on to external URL's which is where the problem might come in...if you clicked on a external URL and passed along your current SID...otherwise I don't see the danger in it at all :?
PHP tends to send session cookies to all users after you call session_start(). Personally, I like when user gets cookie only when there is real need for it. That's why my class has separate begin() and load() methods
Not sure I understand you fully...but if I do...you could just test if a given user needs a session and if they do session_start() otherwise continue as normal...

Personally I don't see assigning every a session being that big a deal, but that is likely a personal prefereance thing, which I can understand your point, if you cannot do what I have suggested...
Older versions of PHP do not have session_regenerate().
Now, if I understand correctly...that function is used to keep your SID dynamic so theft is not possible, as you can use this function to make the SID unique on every visit therefore having a single use and terminate policy in effect. If someone steals a SID they can only use it if they make the request first...in which case the original user would be bumped or logged out, etc...

So, IMHO that is a crappy security measure...and completely pointless...if someone has the ability to retreive you SID period...your in trouble as they could easily capture the SID before you do and us it...

In that way, the problem is more than just PHP session security...
It's difficult to fine-tune session GC with built-in sessions, since they deal only with lifetime. You might want to delete sessions that weren't used for a certain time. For example, you might have session lifetime of one month, but sessions could be also GCed if user doesn't show up for a week.
I did have problems here...but circumvented the issues by just passing phantom handlers and using the object to perform cleanup, etc...

Anyways, appreciate the reply but again :)

Cheers :)
Post Reply