About the Author

Chris Shiflett

Hi, I’m Chris: web craftsman, community leader, husband, father, and partner at Fictive Kin.


All posts for Mar 2011

PHP Session Debugging

For many PHP developers, calling session_start() and using $_SESSION for stuff you want to persist from page to page is all there is to know about sessions. This is understandable, because PHP's native session support is so simple and reliable. But, what if something goes wrong?

Understanding how sessions work is your best tool when it's time to debug a problem. There's really no substitute. I wrote The Truth about Sessions way back in 2003, and it focuses on an outdated technique, but I think it's still worth reading to learn a bit more about how sessions work. (Just read the first few sections.)

Nothing beats getting your hands dirty, and session_set_save_handler() can help. If you're not already using this function, the manual has an example that mimics the native behavior. Storing Sessions in a Database, another old article, shows you how to store sessions in MySQL. For now, it's just important that you're using a custom session handler, because this gives you more insight into what's really going on.

If you're having trouble getting session_set_save_handler() working, don't worry. Figuring out what's wrong is a valuable learning exercise. If you're using the example from the manual, note that it depends upon configuration directives like session.save_path being set correctly.

When something goes wrong, it's almost impossible to determine the exact cause of the problem without some record of what happened. Was the session identifier sent in the request? Was it the same one that was sent in the previous request? Was the session data saved properly? I could come up with dozens of questions like these, because there are a lot of things that can fail, but the point is that we need answers.

Let's look at the read() function from the example in the manual:

<?php
 
function read($id)
{
    global $sess_save_path;
 
    $sess_file = "$sess_save_path/sess_$id";
    return (string) @file_get_contents($sess_file);
}
 
?>

There are a few questions that can be asked:

  • What is the value of $id?
  • Are we able to read from $sess_file?
  • What data is returned?

With a few modifications, you can answer these questions:

<?php
 
function read($id)
{
    global $sess_save_path;
 
    echo "<p>Session identifier is: {$id}</p>";
 
    $sess_file = "{$sess_save_path}/sess_{$id}";
 
    if (is_readable($sess_file)) {
        $data = (string) file_get_contents($sess_file);
        echo "<p>Can read from file: {$sess_file}</p>";
        echo "<p>Data is: {$data}</p>";
    } else {
        echo "<p>Cannot read from file: {$sess_file}</p>";
    }
 
    return $data;
}
 
?>

There are a few problems with this approach:

  • Although you may not mind, depending upon where you're debugging and who has access, each echo is vulnerable to XSS. (In older versions of PHP, $id was not restricted to any particular format. This is no longer the case.) If you do mind, use htmlentities() or htmlspecialchars().
  • Using echo means you can't debug Ajax requests, API requests, etc.
  • Although open(), read(), and gc() are executed (in this order) before the output stream is closed, this is not true of write() and close().

The manual includes the following note:

The write handler is not executed until after the output stream is closed. Thus, output from debugging statements in the write handler will never be seen in the browser. If debugging output is necessary, it is suggested that the debug output be written to a file instead.

This is good advice, but if you really want to use echo, you can force the session to finish its business by using session_write_close(). Call this from __destruct() if you're using a class, or use register_shutdown_function(). I frequently use session_write_close() for this reason, because I like to have debugging output on every page. It's super convenient.

As convenient as it is, I also log, for a couple of reasons:

  • Logs have a recorded history. The current page can't always tell the whole story of what went wrong.
  • Logs capture all requests, not just the one for the current page. This is especially important for Ajax and APIs.

For logging, I use error_log():

<?php
 
error_log($message, 3, '/tmp/session.log');
 
?>

As a starting point, here's an example of what I typically log:

[25 Mar 2011 12:34:56][shiflett.org] [/]
 
Session::start()
    $_COOKIE['PHPSESSID'] [412e11d5317627e48a4b0615c84b9a8f]
Session::open()
Session::read()
    $id [412e11d5317627e48a4b0615c84b9a8f]
    $data [count|i:1;]
Session::write()
    $id [412e11d5317627e48a4b0615c84b9a8f]
    $data [count|i:2;]
Session::close()

If you want to make $data a little easier to read, you have to use session_decode(). This is inconvenient for a couple of reasons:

  • Unlike unserialize() (which won't work, because the format is slightly different), session_decode() assigns the result to $_SESSION. You'll have to preserve $_SESSION yourself if you want to use this for debugging. I wish it had the same optional argument that print_r() has, so that we could opt to have it return the result instead of assign it. Anyone want to help us out? :-)
  • Because session_decode() assigns the result to $_SESSION, it won't work in read().

You can write your own session_decode() in PHP (see the user contributed notes for some examples) or use WDDX:

<?php
 
ini_set('session.serialize_handler', 'wddx');
 
?>

If you use WDDX, you can use wddx_unserialize to unserialize the session data.

Please keep in mind that debugging in production requires extra consideration that I've not covered. If possible, do your session debugging elsewhere.

I'd love to go into more depth, but in the spirit of Ideas of March, I'm posting this short introduction, and hopefully it's helpful. If you have any questions or tips of your own, please leave a comment.

Ideas of March

We need a blog revival.

For the past few years, I've been a pretty active Twitter user. As a result, I feel very tuned into what's happening in the web design and development community. It's pretty cool. Recent events such as Twitter taking the @girlgeeks account and restricting how developers are allowed to use their API dampen the positive vibes, but that's not why I'm blogging today.

Most conversation has moved from blogs to Twitter, and although Twitter is more active than blogs ever were, there are fewer quality conversations and debates taking place as a result of this transition. I'm hoping you'll join me in a blog revival. It's pretty simple:

  • Write a post called Ideas of March.
  • List some of the reasons you like blogs.
  • Pledge to blog more the rest of the month.
  • Share your thoughts on Twitter with the #ideasofmarch hashtag.

If we all blog a little more than we normally would this month, maybe we can be reminded of all of the reasons blogs are great. Here are a few reasons why I like blogs:

  • Posts can be as short or are long as you want.
  • You don't have to use broken language to fit a complete thought.
  • Posts aren't immediately lost in a sea of updates.
  • Posts can be easily found later.
  • You don't have to know what's trending among the riff-raff of the Internet.
  • Posts tend to be more meaningful.
  • All conversation related to a post is easy to find.

There are some benefits to Twitter as well, but I don't think more blogging is going to hurt Twitter in any way. In fact, I think Twitter could benefit from more quality content being generated elsewhere, since it's a natural way to share that content.

I still actively curate a list of blogs I find interesting, and I enjoy reading it daily. I would love to read more posts from the blogs I follow and add more blogs to my reading list.

I pledge to blog more in 2011 than I did in 2010.

Our First PHPCon

This April, the PHP community is going to descend upon Nashville, Tennessee for its inaugural PHPCon. Judging by the speakers and attendees, it's going to be a conference to remember and one that will be talked about for some time. It's being organized primarily by Ben Ramsey, Lisa Denlinger, and Nick Sloan, with plenty of help and support from the rest of the PHP community.

This is a conference I've been hoping would emerge for years. Conferences organized for profit have their place, but the heart and soul of the PHP community, and the open source community in general, is not profit-driven. From PHPCon's about page:

We believe the PHP community is just about the friendliest group around, and we're going to be sharing stories of successes, failures, and lessons learned along the way. Because this is our first conference, we're hoping you'll join us, roll up your sleeves, and help make this something we can all be proud of.

This is your chance to support a good thing, and you get so much in return. Those who register will be supporting the conference financially as well as supporting the people who make it happen. Thanks to some generous sponsors, the all-inclusive pass to PHPCon is only $300, and it includes:

  • Both tutorials on Thursday
  • An entire day of talks on Friday
  • Free food, snacks, and conversation
  • The chance to meet the people who make PHP

This is probably the best lineup of PHP speakers I've ever seen, and I've been to a lot of PHP conferences. Have you ever been to a conference with Rasmus Lerdorf, Andrei Zmievski, Sean Coates, Paul Reinheimer, and Terry Chay? I haven't. Have you ever met the people behind PHPDeveloper.org, Phorum, 24 Ways, Smarty, Spaz, CakePHP, Lithium, or Zend Framework? They're all going to be there, along with people from Flickr, Etsy, Automattic, and SourceForge.

The tutorials are free for everyone who is registered, and you get to choose from Lorna Mitchell talking about web services, Helgi Þormar Þorbjörnsson talking about caching, Joël Perras talking about Lithium, and Matthew Weier O'Phinney talking about Zend Framework.

All of this is happening in a really cool space with a rather dumb name (aVenue). :-)

If I sound a little bit jealous, it's because I am. I'm not going to be able to make it this year, but I genuinely hope it's a huge success, and I can't wait to hear all about it. If you're lucky enough to be there, be sure to get the Pot Pourri at the Spaghetti Factory and the Kansas City Strip and Demos'. That'll make me extra jealous.

Have fun, learn lots, and make something great.

Using Twitter for Comments

I just remade this site using Lithium (something I'll blog about later), and I wanted to note a change that relates to Drew McClellan's post on OpenID.

I'm no longer supporting OpenID.

I still really like OpenID, but I don't think the lack of adoption can be blamed on misuse or misunderstanding. I think the failure, as Drew describes it, is that it's too complicated for users and too troublesome for providers.

The idea is great. I really like using shiflett.org as my identity. (I just went through the exact same experience Drew describes when 37signals dropped support for OpenID, because shiflett was not available.) As a user, however, I've noticed that I much prefer sites where I have an account, because I can log in directly with 1Password. It's just easier.

OpenID is also difficult to support. A big part of the problem is how delegation works. Far too many people have sites that don't validate, and because OpenID libraries parse HTML to grab the delegation information, the failure rate is incredibly high. I get about as many complaints about OpenID failures as I get comments.

Even if your site validates right now, it's very easy to forget to check after you change something. In fact, I just made this very mistake and now have a duplicate id on some of my pages. Oops! I'll always do my best to fix mistakes as I make them, but it happens.

There are other factors, but I don't want to dwell on why I'm no longer supporting OpenID. Unless you're searching for the best way to identify people commenting on a blog, your needs are likely different than mine.

After seeing how easy it was to log in on Lanyrd, I decided Twitter might be the best solution. After all, I don't want people to have to create an account just to comment on my blog. That's ridiculous, plus it sounds like work, and I always try to avoid that.

Because I had already written about Twitter OAuth, implementing it was straightforward. I just had to move some code around and change a callback. I also changed my application settings to only ask for read access. After all, I just want you to identify yourself. What you say on Twitter is your business. :-)

The last change I made is just a change in policy, but it's a big one. I no longer allow anonymous comments. I think having to own what you say will generally be a good thing. I hope you agree.