Community Pick: Many members of our community have endorsed this article.
Editor's Choice: This article has been selected by our editors as an exceptional contribution.

PHP Sessions: Simpler Than You May Think!

Published:
Updated:
A Change in PHP Behavior with Session Write Short Circuit (Winter 2014)**
With the release of PHP 5.6 the session handler changed in a way that many think should be considered a bug.  See the note at the end of this article.

A Foreword: PHP session_unregister() (Fall 2014)
Some obsolete code sets contain "Logout" examples that use the session_unregister() function.  PHP deprecated this function more than 5 years ago and removed it recently.  If you have a script that uses session_unregister(), you should replace the function name with unset(), using the same function call arguments.  Going forward, do not use session_unregister(), session_register(), or session_is_registered().  Instead check the PHP man pages for these functions.  There are solutions and explanations of the alternatives in the user-contributed notes.

And now, on to our Article: PHP Sessions -- Simpler Than You Think
This EE question ("I am looking for a solution for logout after inactivity.") got me thinking...
http://www.experts-exchange.com/Web_Development/Web_Languages-Standards/PHP/Q_28169149.html#a39280833

Developers who are new to PHP session handling sometimes over-think the role and behavior of PHP sessions.  This article takes the process apart and reconstructs it to show how easy it can be to use the PHP session to your (and your clients') advantage.

Use the Built-In Features Whenever Possible
PHP already has a built-in solution for logout after inactivity.  All you need to do is embrace it!  Do not write separate programming to try to cause this to happen -- all that will happen is that you will get confused by the overlapping layers of your programming and the standard, expected behaviors of the PHP session handler.

To Use the Built-In Features, You Need to Know the Built-In Features
You may know a good bit about PHP or you may want to get a foundation in how PHP works.  This requires some study and familiarity with the PHP language and syntax.  There will be minor differences from release to release, and differences in MySQL, MySQLi, PostGreSQL and SQL Server, but the differences will be mostly a matter of configuration settings and query syntax, and not a matter of design principle.  This article contains some of the concepts you need to get started with PHP programming and contains links to good-quality, vetted learning resources.
http://www.experts-exchange.com/Web_Development/Web_Languages-Standards/PHP/A_11769-And-by-the-way-I-am-new-to-PHP.html

To Make the Best Use, You Need to Understand HTTP Client/Server Protocols
You will also want to understand why there is no such thing as a "logged in" user, and you'll need some background information about the HTTP cookie.  The phenomena you will see in PHP session handling will be due to the stateless nature of the HTTP client-server protocol.  This article explains it.
http://www.experts-exchange.com/Web_Development/Web_Languages-Standards/A_11271-Understanding-Client-Server-Protocols-and-Web-Applications.html

Not Everything is Transparent
Despite the function name, PHP sessions are not necessarily eliminated by session_destroy().  If you read the Login-Logout article, you will find a workable way to force a "logout" condition.  Here is a bit more about how the session handler works.  You must read and understand these two pages to work with PHP sessions.
http://php.net/manual/en/book.session.php
http://php.net/manual/en/session.examples.basic.php

How the PHP Session Handler Works
When the client requests a page that contains the session_start() statement, the following things take place.  This will be true almost 100% of the time.  There are edge cases, such as clients who have turned off cookies and PHP configurations that have odd settings, but for almost everyone, things will work as described here.

1. PHP initiates the session garbage collector ("GC").  The GC looks at all sessions on the server to see if there has been recent access to the session data.  If any session data anywhere has not been accessed in the last 24 minutes, PHP deletes that session data.  This has the effect of causing any client with 24 minutes of inactivity to be logged out.  Inactivity for this purpose is defined as the absence of any HTTP request to a page that used session_start().**

2. PHP looks to see if a session cookie is associated with the request. If a cookie is found, PHP attempts to load the preexisting session data into the $_SESSION array.  The data may be present or it may have been deleted by the GC.  To the extent that the session data is found, it is loaded into the superglobal $_SESSION array.  If no cookie is found, obviously no session data can be found, and no action will occur at this point.

3. PHP issues a setcookie() command with a cookie value that is a pointer to a session storage area and an expiration time of zero seconds.  This has the effect of causing the PHP session cookie to expire when the browser is closed.*  The cookie name will be "PHPSESSID" and the cookie value will be either the new pointer value if the session is being started for the first time, or the value discovered in step 2.  The cookie value will be used on subsequent requests to locate the session data.

4. When the script terminates, PHP writes the contents of $_SESSION to the session storage area that was designated by the session cookie.  You can do this yourself, with session_write_close(), and in practice, since PHP 5.6 changes, and if the session is shared with other iFrames or among AJAX requests session_write_close() may be needed.

That's it.  You can complicate things or mess this process up with various configuration settings, but if you don't actively do something to change the settings or process, you can depend on the sequence of events to make your scripts work correctly.

How to Write Your Code
Now that you know what the PHP session handler will do, how should you write your PHP code to make best use of the standard process?

1. Put session_start() at the logical top of your script.  Do this unconditionally on every page without exception, no excuses.  You must issue this command before any browser output whatsoever, even including invisible whitespace.  This is because the PHP session handler will try to set a cookie.  Cookies are part of the HTTP headers, and it is a law of HTTP that all headers must come first and be complete before any browser output.  If you do not follow this "session-first" principle, you'll see a failure with a message something like "cannot send session cache limiter - headers already sent."

2. Add and remove data from the $_SESSION array.  Expect it to be present and persistent from request to request.  Almost any kind of data can be put into $_SESSION.  There are a few exceptions (references, resources) but scalar data and arrays work beautifully.  Beware of Register Globals, an artifact PHP feature which should be gone by the time you read this.  However... the process for storing objects in $_SESSION has a "gotcha."  PHP will attempt to serialize() the object before storing the session data.  A practical limitation of this "gotcha" is that the built-in PHP objects, such as the SimpleXML objects, cannot be stored in the session in the form of objects.  A workable solution might be to use json_encode() and store the JSON string in the session.  Please see this PHP note for an explanation.
http://php.net/manual/en/function.unserialize.php#112823

3. Expect the session data to be eliminated when the client closes the browser.*

4. Expect the session data to be eliminated when the client is inactive for 24 minutes.**

It's just that simple, but it's important to understand the principles if you're using the session to authenticate a client for separate privileges, such as a "login" condition.  So please take the time to read the articles and links, and feel free to experiment with your own SSCCE for session handling.

Cross-SubDomain Sessions
By default, the PHP session handler will set separate cookies for separate subdomains:

http://www.domain.com
http://test.domain.com
http://domain.com  

This is a useful feature, since you might have different versions of your web site online at the same time, perhaps using the test subdomain for your own testing, while using the www subdomain for the deployed web application.  But what if you wanted to share cookies across the subdomains?  This script shows how that can be accomplished.  Note that it will share cookies across all subdomains.  You can install this script on your server and run it.  As you click the links you will see the URL change, and the value of the session counter change, but the PHPSESSID cookie will remain intact.  This implies that a user who is "logged in" to the www subdomain is also logged in to any and all of the other subdomains.

Note to readers who are using domains like england.co.uk, you will need to modify this code a little bit to work correctly with a TLD like .co.uk in place of more traditional .com or .org.
 
<?php // RAY_session_cookie_domain.php
                      /**
                       * QUESTION: WHEN CLIENTS VISIT MY SITE SOMETIMES THEY USE www.mysite.org
                       * BUT SOMETIMES THEY USE mysite.org WITHOUT THE WWW.  HOW CAN I HANDLE
                       * THE SESSION ISSUES THAT ARISE FROM THIS?
                       *
                       * ANSWER: ONE WAY IS TO REWRITE THE URL TO REMOVE THE SUBDOMAIN IF IT
                       * IS WWW.  FOR EXAMPLE:
                       *
                       *     Options +FollowSymlinks
                       *     RewriteEngine on
                       *     RewriteCond %{http_host} ^www\.example\.org [NC]
                       *     RewriteRule ^(.*)$ http://example.org/$1 [R=301,NC]
                       *
                       * ANOTHER WAY IS TO MODIFY THE SESSION COOKIE SO IT WORKS ACROSS ALL OF
                       * YOUR SUBDOMAINS.  YOUR CHOICE WILL LARGELY DEPEND ON THE WAY YOU WANT
                       * TO HANDLE OTHER SUBDOMAINS (OTHER THAN WWW).
                       */
                      
                      // DEMONSTRATE HOW TO START SESSIONS THAT WORK IN DIFFERENT SUBDOMAINS PHP 5.2+
                      error_reporting(E_ALL);
                      
                      
                      // MAKE THE SESSION COOKIE AVAILABLE TO ALL SUBDOMAINS
                      // MAKE A DOMAIN NAME THAT OMITS WWW OR OTHER SUBDOMAINS
                      // BREAK THE HOST NAME APART AT THE DOTS
                      $x = explode('.', strtolower($_SERVER["HTTP_HOST"]));
                      $y = count($x);
                      // POSSIBLY 'localhost'
                      if ($y == 1)
                      {
                          $host = $x[0];
                      }
                      // MAYBE SOMETHING LIKE 'www2.atf70.whitehouse.gov'
                      else
                      {
                          // USE A DOT PLUS THE LAST TWO POSITIONS TO MAKE THE HOST DOMAIN NAME
                          $host
                          = '.'
                          . $x[$y-2]
                          . '.'
                          . $x[$y-1]
                          ;
                      }
                      
                      // START THE SESSION AND SET THE COOKIE FOR ALL SUBDOMAINS
                      $sess_name = session_name();
                      if (session_start())
                      {
                          // MAN PAGE http://php.net/manual/en/function.setcookie.php
                          setcookie
                           ( $sess_name
                           , session_id()
                           , NULL                // THIS IS WHERE YOU CAN SET THE TIME
                           , DIRECTORY_SEPARATOR
                           , $host
                           , FALSE
                           , TRUE
                           )
                           ;
                      }
                      
                      
                      // PROVE THAT THE COOKIE WORKS IN MULTIPLE DOMAINS
                      // LOAD UP SOME INFORMATION TO SHOW SESSION CONTENTS
                      if (!isset($_SESSION["count"])) $_SESSION["count"] = 0;
                      $_SESSION["count"] ++;
                      
                      
                      // STRIP OFF THE DOT THAT WAS NEEDED FOR SETCOOKIE
                      $gost = ltrim($host,'.');
                      $dmn_link = 'http://'    . $gost . $_SERVER['REQUEST_URI'];
                      $www_link = 'http://www' . $host . $_SERVER['REQUEST_URI'];
                      
                      // PUT UP TWO LINKS WITH DIFFERENT SUBDOMAINS
                      echo "<br/>Click these links to the _SESSION and _COOKIE data" . PHP_EOL;
                      echo "<br/><a href=\"$www_link\">$www_link</a>" . PHP_EOL;
                      echo "<br/><a href=\"$dmn_link\">$dmn_link</a>" . PHP_EOL;
                      
                      
                      // SHOW WHAT IS IN COOKIE AND IN $_SESSION
                      echo "<pre>";
                      echo "PHPSESSID: ";
                      var_dump($_COOKIE['PHPSESSID']);
                      echo PHP_EOL . PHP_EOL;
                      echo "SESSION: ";
                      var_dump($_SESSION);
                      echo "</pre>";
                      
                      // CREATE A FORM TO UPDATE THE COOKIE
                      $form = <<<ENDFORM
                      <form method="post">
                      <input type="submit" value="CLICK ME" />
                      </form>
                      ENDFORM;
                      echo $form;

Open in new window


PHP Objects in the PHP Session
In theory, the $_SESSION array can contain any kind of PHP variable: Boolean, String, Integer, Array, Object, etc.  In practice it's probably wise to leave Object off of this list.  There is a note on the PHP unserialize() man page that identifies a "gotcha" in PHP 5.3+ regarding the storing of objects in $_SESSION.  In a nutshell, it's a bad idea for security and other reasons.  In order to be able to unserialize() an object, the class of that object needs to be defined.  What this means in practice is that you may lose all of your session data (not just the object) if serialization fails.  And serialization will fail for the PHP predefined class objects like those built from StdClass and SimpleXML.  An appropriate workaround for this issue would be to convert the object to a JSON string with JSON_Encode() and store the JSON string in $_SESSION.  When the session is resumed on the next HTTP request, the object's properties can be recovered with JSON_Decode().  It's also worth noting that PHP serialize() may not handle non-public object properties in a user-friendly manner.

Sessions to Share Data Among IFrames and AJAX Requests
PHP sessions work perfectly when client browser HTTP requests load unitary web pages sequentially, but what about pages that are made up from a collection of iframes, or are created dynamically with multiple AJAX requests?  In these cases there is a risk of race conditions.  Session data is locked to prevent concurrent writes, therefore only one script may operate on the same session at any time.  PHP session_write_close() can be called to store the session data, unlock the session, and make the session available to other scripts.  There may be some interaction with output buffering, according to this note.  It appears that PHP7 has output buffering turned on by default, and that would be a change from pre-PHP7 settings.  I have not been able to verify the interaction of output buffering and session_write_close() independently.

Sessions and Header("Location: ... ")
One of the common patterns in PHP "login" scripts includes updating something in the session and then using header() to redirect the client browser to a new page.  This can induce a race condition in busy servers.  If the browser gets the redirection notice and makes a new request to the server, that request can potentially be started before the first script has finished the normal script termination process.  In this case, the symptom would be intermittent losses of session data.  PHP session_write_close() can prevent the data from being lost.  Call session_write_close() before the header() function to be safe.

A Script to Test Your Session Handler
I've never encountered a correctly configured PHP installation that did not run this script correctly.  If you run it and find that it does not behave as expected, contact your server administrator!  You can use this as a starting point to develop your own SSCCE for experimenting with the PHP session handler.
 
<?php // RAY_session_test.php
                      error_reporting(E_ALL);
                      
                      
                      // DEMONSTRATE HOW PHP SESSIONS WORK
                      // MAN PAGE HERE: http://php.net/manual/en/function.session-start.php
                      
                      
                      // START THE SESSION (DO THIS FIRST, UNCONDITIONALLY, IN EVERY PHP SCRIPT ON EVERY PAGE)
                      session_start();
                      
                      // INITIALIZE THE SESSION ARRAY TO SET A DEFAULT VALUE
                      if (empty($_SESSION["cheese"])) $_SESSION["cheese"] = 1;
                      
                      // SEE IF THE CORRECT SUBMIT BUTTON WAS CLICKED
                      if (isset($_POST['fred']))
                      {
                          // ADD ONE TO THE CHEESE
                          $_SESSION['cheese']++;
                      }
                      
                      // RECOVER THE CURRENT VALUE FROM THE SESSION ARRAY
                      $cheese = $_SESSION['cheese'];
                      
                      
                      // END OF PROCESSING SCRIPT - CREATE THE FORM USING HEREDOC NOTATION
                      $form = <<<ENDFORM
                      <html>
                      <head>
                      <title>Session Test</title>
                      </head>
                      <body>
                      Currently, SESSION["cheese"] contains: $cheese<br/>
                      <form method="post">
                      <input type="submit" value="increment this cheese" name="fred"  />
                      <input type="submit" value="leave my cheese alone" name="john" />
                      </form>
                      </body>
                      </html>
                      ENDFORM;
                      
                      echo $form;

Open in new window


* The Fine Print
The "developer-only" gotcha associated with closing the browser goes something like this.  You open a browser window and login as User-1.  Then you open another browser window and login as User-2.  Then you close the second window, and navigate back to the original window expecting to find User-1.  But, gadzooks, you find User-2!  Is something terribly wrong?  Actually, no, this is to be expected.  

This almost never bothers "normal" users, because they sign into the site using their own credentials and rarely try to be "two users at once."  But it drives developers nuts because many developers do not understand that all instances of the same browser share the same "cookie jar" file.  This is true whether the browsers are tabs in the same window or separate windows.  FF does not share with IE or Chrome, etc, but all instances of the same browsers share a common cookie jar.

There can be only one cookie in the cookie jar with the name PHPSESSID, since all cookie names must be unique (the names are array indices).  When the browser received the setcookie() command for User-2, it stored the new value in the cookie jar.  And the browser will always return the newest data for every cookie.

Although not directly related to the PHP session, this dual-conflicted-identity issue has risen to the level of a W3 API recommendation for persistent data storage in web clients.  See the W3 Web Storage recommendation for details.

** More Fine Print: An Unpleasant Surprise at PHP 5.6+
With the release of PHP 5.6 the session handler changed in a way that many think should be considered a design flaw or an introduced bug.  The change is this: unless the data in the PHP session actually changed, PHP does not rewrite the session.  This change to PHP introduces a risk that session timestamps can go out of date, even while a client is visiting your site, going from page to page.  Consider a client who could visit your web site and traverse many pages without changing the contents of the $_SESSION array.  The login process would set data in the session, but if the client was simply "window shopping," even the logged-in session could expire in the middle of an otherwise active visit.  Without taking an action that changed the session (such as adding a product to a shopping cart) the client could suddenly be "forgotten" in the middle of a web visit, and that's not exactly acceptable UX.    If you're implementing PHP's SessionHandlerInterface, see this PHP User-Contributed Note.  If you're using procedural PHP consider adding session_write_close() logic to your scripts, or otherwise changing a data element in the session with every session_start() command.  Though untested, this code should restore the expected behavior.
 
<?php
                      session_start();
                      $_SESSION['when'] = time();

Open in new window


Summary
PHP sessions are one of the wonderfully useful tools for web site development since they allow your scripts to "remember" many things about the client from page to page.  Used correctly, they provide an elegantly simple solution to the question of short-term "stateful" data that must persist between requests.  The PHP session allow you to pass data back and forth in iframes and AJAX requests, as well as between conventional page loads.

Please give us your feedback!
If you found this article helpful, please click the "thumb's up" button below. Doing so lets the E-E community know what is valuable for E-E members and helps provide direction for future articles.  If you have questions or comments, please add them.  Thanks!
 
10
13,748 Views

Comments (5)

tigermattStaff Platform Engineer
CERTIFIED EXPERT
Most Valuable Expert 2011

Commented:
Ray,

I spotted the notification in my Inbox that a new PHP article had been written. From the title, I had an inkling that it was probably one of yours before I even clicked the link.

Once again, my thanks for your continued contribution of this valuable and well-presented material to the Experts Exchange community. I enjoy reading each and every one of your articles and your answers to questions in the Q&A part of the site.

Best,

Matt / tigermatt
I also want to thank Ray for answering a lot of my questions, teaching me the correct way to do things, and these articles.

Ray, more articles, please :)
Most Valuable Expert 2011
Author of the Year 2014

Author

Commented:
Gladly, Keith :-)  What topics would you like to cover?
Jim RiddlesPrepress/OMS Specialist
CERTIFIED EXPERT

Commented:
The same goes for me.  I have learned so much about the proper way to approach PHP programming.  I still have so much more to learn, but with Ray's articles, I know that I will get there.  Thanks so much, Ray for all of your time and efforts!  They are much appreciated.
Nice write up on PHP sessions. Have you used Redis server for php session management? I am using it on my server. Redis is efficient in handling sessions in PHP. If you use memcached as well, you will see a significant increase in performance of database.

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.