Mobile Browser Detection in PHP

I've been meaning to post this for a few months now, I just for one reason or the other haven't gotten there - mostly because the actual running code is much more ugly and had to be cleaned up a bit before I posted it. Ever since I saw this post on the dev.mobi blog about detecting mobile browser in PHP, I wanted to post the way I do it, because it's faster, cleaner and more accurate.

The problem with the code they have is first, it uses regular expressions, which is slow as death to begin with, and secondly it's not as accurate as it's trying to match small-as-possible parts of the user-agent string, rather than a specific word. Honestly, I don't know if their list of 4 character codes is accurate or not, or if it'll produce false positives.

So instead of using regular expressions, I use the fastest method of matching strings in PHP, which is strpos() - it returns False if it doesn't find a match, or the number of the first character in that match. It's fast, and you'll see in the code below, chainable using the || (or) comparison operator. As soon as a match comes up, it'll short circuit the rest of the code, making common user-agents faster to detect with a simple re-ordering. Also, you can mix and match headers to look at including remote address, etc. For Mowser, I also like to make sure that bots always see the mobile version as well, so I've included detection code for those user-agents (and one common IP) at the bottom.

Here's the code (cleaned up a bit from what's in my actual running code):



$isMobile = false;
$isBot = false;

$op = strtolower($_SERVER['HTTP_X_OPERAMINI_PHONE']);
$ua = strtolower($_SERVER['HTTP_USER_AGENT']);
$ac = strtolower($_SERVER['HTTP_ACCEPT']);
$ip = $_SERVER['REMOTE_ADDR'];

$isMobile = strpos($ac, 'application/vnd.wap.xhtml+xml') !== false
        || $op != ''
        || strpos($ua, 'sony') !== false 
        || strpos($ua, 'symbian') !== false 
        || strpos($ua, 'nokia') !== false 
        || strpos($ua, 'samsung') !== false 
        || strpos($ua, 'mobile') !== false
        || strpos($ua, 'windows ce') !== false
        || strpos($ua, 'epoc') !== false
        || strpos($ua, 'opera mini') !== false
        || strpos($ua, 'nitro') !== false
        || strpos($ua, 'j2me') !== false
        || strpos($ua, 'midp-') !== false
        || strpos($ua, 'cldc-') !== false
        || strpos($ua, 'netfront') !== false
        || strpos($ua, 'mot') !== false
        || strpos($ua, 'up.browser') !== false
        || strpos($ua, 'up.link') !== false
        || strpos($ua, 'audiovox') !== false
        || strpos($ua, 'blackberry') !== false
        || strpos($ua, 'ericsson,') !== false
        || strpos($ua, 'panasonic') !== false
        || strpos($ua, 'philips') !== false
        || strpos($ua, 'sanyo') !== false
        || strpos($ua, 'sharp') !== false
        || strpos($ua, 'sie-') !== false
        || strpos($ua, 'portalmmm') !== false
        || strpos($ua, 'blazer') !== false
        || strpos($ua, 'avantgo') !== false
        || strpos($ua, 'danger') !== false
        || strpos($ua, 'palm') !== false
        || strpos($ua, 'series60') !== false
        || strpos($ua, 'palmsource') !== false
        || strpos($ua, 'pocketpc') !== false
        || strpos($ua, 'smartphone') !== false
        || strpos($ua, 'rover') !== false
        || strpos($ua, 'ipaq') !== false
        || strpos($ua, 'au-mic,') !== false
        || strpos($ua, 'alcatel') !== false
        || strpos($ua, 'ericy') !== false
        || strpos($ua, 'up.link') !== false
        || strpos($ua, 'vodafone/') !== false
        || strpos($ua, 'wap1.') !== false
        || strpos($ua, 'wap2.') !== false;

        $isBot =  $ip == '66.249.65.39' 
        || strpos($ua, 'googlebot') !== false 
        || strpos($ua, 'mediapartners') !== false 
        || strpos($ua, 'yahooysmcm') !== false 
        || strpos($ua, 'baiduspider') !== false
        || strpos($ua, 'msnbot') !== false
        || strpos($ua, 'slurp') !== false
        || strpos($ua, 'ask') !== false
        || strpos($ua, 'teoma') !== false
        || strpos($ua, 'spider') !== false 
        || strpos($ua, 'heritrix') !== false 
        || strpos($ua, 'attentio') !== false 
        || strpos($ua, 'twiceler') !== false 
        || strpos($ua, 'irlbot') !== false 
        || strpos($ua, 'fast crawler') !== false                        
        || strpos($ua, 'fastmobilecrawl') !== false 
        || strpos($ua, 'jumpbot') !== false
        || strpos($ua, 'googlebot-mobile') !== false
        || strpos($ua, 'yahooseeker') !== false
        || strpos($ua, 'motionbot') !== false
        || strpos($ua, 'mediobot') !== false
        || strpos($ua, 'chtml generic') !== false
        || strpos($ua, 'nokia6230i/. fast crawler') !== false;


Obviously, the most accurate way of detecting a mobile browser would be to use a WURFL-style database of mobile user agents, and then you'd be able to also get a very accurate list of capabilities as well, but in my code I've aimed for a medium capable device already, and then do minor adjustments in places I know where the markup will break on a lower-end phone. So to me, I just need to know if it's a phone or not, and then later I can do more specific queries if needed.

I did a quick loop comparing the above code to the code on dev.mobi and in the worst case (where the user agent matches nothing, or the last in the list) it's about twice as fast. In the best case, however, the code above can be 3 or 4 times as fast, since it shortcuts so quickly. If you're planning on delivering millions of mobile page views, these sorts of little numbers add up, and obviously, the more accurate user-agent detection is key as well.

I should mention, by the way, that I didn't come up with this way of doing mobile detection - I first found it in the Scuttle code I was using last year, and at first was wondering why the coder was being so obtuse with all the !== matches, etc. But it turns out that guy had a clue, and I'm lucky I ran into that code first.

I'm posting this as a sort of double-check, by the way. I'm pretty sure it's the best way to do this without explicitly matching each user-agent, but if I'm missing something big, by all means, please ping me.

-Russ

P.S. A really, really cool thing you can do with that code above? Put it at the top of your site in an if-statement like the one below, and forward your mobile users to a mobilized version powered by Mowser.

:-)


if($isMobile){
   header('Location: http://m.mowser.com/web/' . urlencode($_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']));
   exit();
}

< Previous         Next >