본문 바로가기
APM

How to save PHP Sessions to a database

by 누피짱 2014. 8. 5.

Sessions are a critical component of creating dynamic websites or web applications. If you are building these types of website, you will most certainly be required to handle Sessions at some point.

In this post I will be looking at PHP Sessions, why you need them, how you should store them and I’ll give you a complete class for handling Sessions within a database.

What are Sessions?

Before we get into implementing a Sessions solution, it’s important to understand exactly what they are.

By default, the Internet is a “stateless” environment. This means that every request you make in a browser is anonymous in the sense that it is not tied to the previous request in any way. When you login to a website, the application must “remember” that you have logged in and you should be associated with an account. However, in order to maintain this “state”, we need to use Sessions.

A Session is a unique identifier that is recorded on the server and in your browser. By recording this information, the server is able to maintain the state across many page requests.

So essentially, the internet is unable to remember you every time you refresh the page. A Session is simply a small note that enables the server and your browser to maintain certain data.

How do Sessions work?

Sessions work by assigning each user a unique identifier. When you save data into the Session (such as being logged in), the Session will store that data into a file onto the server. The unique identifier is also stored as a cookie on the user’s computer.

When the user visits a new page, the session id from the cookie is used to find the session data from the file storage. If the data is found it will load it into the Session global variable ready for the application to use.

Are Sessions safe?

Like just about anything on the Internet, Sessions are safe up until a certain point. Whilst it is possible to “hijack” a Session, I wouldn’t worry too much about it unless you are storing really sensitive data on people.

If a hacker is able to hijack another user’s Session, they will be able to log into the user’s account as if they are that person.

There are many ways to make Sessions more secure. For example, you could store a token in the Session that is auto generated on each request. You could then compare these tokens to see if they matched. Another method could be to maintain a record of the user’s IP address and browser data. Both of these methods are not fool-proof as they are both still susceptible to an attack. Both methods will also add significant complexity to your application and will probably annoy the user more than it will keep them safe.

One of the problems with storing Sessions in a file system on the server is when you are using a shared hosting plan. If the server has not be configured correctly, it would be possible for someone to get access to your sessions.

One way to prevent this problem is by storing the Sessions in a database, rather than in file storage. Fortunately, storying Sessions in a database is really easy, and it will not negatively effect your user.

Storing Sessions in a database is also an advantage if you need to expand your application to multiple servers. By storing Session data in a central server that can be accessed by any of the other Servers, you negate the problem that would of arose.

Setting up storing Sessions in a database

I was first introduced to storing Sessions in a database by this great post by Chris Shiflett. Chris provides a perfect introduction to the concept and some sample code to get started. The code in this tutorial is an extension of Chris’ work, and so if you want to read his introduction, you can go do that first.

So in order to store our Session data in a database, we’re going to need a table.

Run the following SQL in your database to generate the table. I’m using MySQL, but hopefully the following should be enough to get you started.


1
2
3
4
5
6
CREATE TABLE IF NOT EXISTS `sessions`(   
  `id`
varchar(32) NOT NULL,
  `access` int(10) unsigned DEFAULT NULL,
  `data` text,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

The Session Class

We are going to use a Class to handle our Sessions. This means we can simply instantiate a new Object of the class on each page request. It also keeps all of our Session methods all together in one place.

In order to store the Session data in a database, we are going to need to have a way of interacting with the database. For this tutorial I will be passing the Session class and instance of the Database class that I wrote about in Roll your own PDO PHP Class.

If you want to use your own Database abstraction layer, it should be pretty easy to switch it out. If you haven’t already got a database abstraction layer set up, take a look at that tutorial.

So the first thing we need to do is to create the Class.


1
2
3
class Session {
 
}

Next we declare a property for the database object we will be using.


1
2
3
4
5
6
7
8
9
10
11
/**
 * Session
 */
class Session {
 
  /**
   * Db Object
   */
  private $db;
 
}

The Constructor

When we instantiate the Session class, we need to set up a couple of things in order to make it work. The constructor method is automatically run when the class is instantiated, and so this is a good place to do just that.

First we instantiate a copy of the database class and store it in the db property.


1
2
3
4
public function _construct(){
  // Instantiate new Database object
  $this->db = new Database;
}

Next we need to override the Session handler to tell PHP we want to use our own methods for handling Sessions.


1
2
3
4
5
6
7
8
9
// Set handler to overide SESSION
session_set_save_handler(
  array($this, "_open"),
  array($this, "_close"),
  array($this, "_read"),
  array($this, "_write"),
  array($this, "_destroy"),
  array($this, "_gc")
);

This looks complicated, but all it is saying is we want to use our own methods for storing and retrieving data that is associated with the Session. Take a look at the PHP Manual to read more about this.

And finally we need to start the Session.


1
2
// Start the session
session_start();

So your constructor method should look like this:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public function _construct(){
  // Instantiate new Database object
  $this->db = new Database;
 
  // Set handler to overide SESSION
  session_set_save_handler(
    array($this, "_open"),
    array($this, "_close"),
    array($this, "_read"),
    array($this, "_write"),
    array($this, "_destroy"),
    array($this, "_gc")
  );
 
  // Start the session
  session_start();
}

Next we need to create each of the methods for handling our Session data. Each of these methods are really simple. If you are unfamiliar with the database abstractions from the PDO tutorial, have a read through that post for a more detailed explanation of the methods that will be interacting with the database.

Open

The first method we need to create simply checks to see if there is a database connection available to use.


1
2
3
4
5
6
7
8
9
10
11
12
/**
 * Open
 */
public function _open(){
  // If successful
  if($this->db){
    // Return True
    return true;
  }
  // Return False
  return false;
}

Here we are simply checking to see if there is a database connection. If there is one, we can return true, otherwise we return false.

Close

Very similar to the Open method, the Close method simply checks to see if the connection has been closed.


1
2
3
4
5
6
7
8
9
10
11
12
13
/**
 * Close
 */
public function _close(){
  // Close the database connection
  // If successful
  if($this->db->close()){
    // Return True
    return true;
  }
  // Return False
  return false;
}

Read

The Read method takes the Session Id and queries the database. This method is the first example of where we bind data to the query. By binding the id to the :id placeholder, and not using the variable directly, we use the PDO method for preventing SQL injection.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
 * Read
 */
public function _read($id){
  // Set query
  $this->db->query('SELECT data FROM sessions WHERE id = :id');
   
  // Bind the Id
  $this->db->bind(':id', $id);
 
  // Attempt execution
  // If successful
  if($this->db->execute()){
    // Save returned row
    $row = $this->db->single();
    // Return the data
    return $row['data'];
  }else{
    // Return an empty string
    return '';
  }
}

If the query returns data, we can return the data. If the query did not return any data, we simple return an empty string. The data from this method is passed to the Global Session array that can be accessed like this:

1
2
3
echo "<pre>";
print_r($_SESSION);
echo "</pre>";

Write

Whenever the Session is updated, it will require the Write method. The Write method takes the Session Id and the Session data from the Global Session array. The access token is the current time stamp.

Again, in order to prevent SQL injection, we bind the data to the query before it is executed. If the query is executed correctly, we return true, otherwise we return false.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
 * Write
 */
public function _write($id, $data){
  // Create time stamp
  $access = time();
     
  // Set query 
  $this->db->query('REPLACE INTO sessions VALUES (:id, :access, :data)');
     
  // Bind data
  $this->db->bind(':id', $id);
  $this->db->bind(':access', $access); 
  $this->db->bind(':data', $data);
 
  // Attempt Execution
  // If successful
  if($this->db->execute()){
    // Return True
    return true;
  }
   
  // Return False
  return false;
}

Destroy

The Destroy method simply deletes a Session based on it’s Id.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
 * Destroy
 */
public function _destroy($id){
  // Set query
  $this->db->query('DELETE FROM sessions WHERE id = :id');
     
  // Bind data
  $this->db->bind(':id', $id);
     
  // Attempt execution
  // If successful
  if($this->db->execute()){
    // Return True
    return true;
  }
 
  // Return False
  return false;
}

This method is called when you use the session destroy global function, like this:


1
2
// Destroy session
session_destroy();

Garbage Collection

And finally, we need a Garbage Collection function. The Garbage Collection function will be run by the server to clean up any expired Sessions that are lingering in the database. The Garbage Collection function is run depending on a couple of settings that you have on your server.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
 * Garbage Collection
 */
public function _gc($max){
  // Calculate what is to be deemed old
  $old = time() - $max;
 
  // Set query
  $this->db->query('DELETE * FROM sessions WHERE access < :old');
     
  // Bind data
  $this->db->bind(':old', $old);
     
  // Attempt execution
  if($this->db->execute()){
    // Return True
    return true;
  }
 
  // Return False
  return false;
}

The Garbage collection is run based upon the session.gc_probability and session.gc_divisor settings on your server. Say for example the probability is set to 1000 and the divisor is 1. This would mean that for every page request, there would be a 0.01% chance the Garbage collection method would be run.

The method is passed a max variable. This relates to the maximum number of seconds before PHP recognises a Session has expired. Again this is a setting on your server that is open for you to edit.

Both of these settings can be found in your php.ini file.

Conclusion

And there you have, a nice and simple way to get up and running with storing PHP Sessions in a database. Hopefully this was a good introduction to the concept and a practical example so you can see it in action.

댓글