Server side browser features with phpcaniuse

The Browser Capabilities Project is an effort to determine browser capabilities based on the User-Agent string. It’s a great project which has kept up to date with UA stings. The data is used to power the PHP function get_browser(), and is used by the phpbrowscap standalone project. The challenge with the project is that their features haven’t kept up with the rapid development of web technology.

The CanIUse.com project catalogs granular data about the capabilities of web browsers. They’ve done an excellent job of tracking emerging features like HTML5 canvas and web audio. They also make all of the data available as a json file on github. Thanks!

So what do you do when you have two different projects, with different goals butĀ  some overlap? You create a mashup! Enter PHPCanIuse, which will let you check browser capabilities from the caniuse.com project, based on the decoded UA string from the browscap project.

JQuery and other javascript frameworks have client side feature detection, but there are a few scenarios where you might want to do it server side:

  • Embed a Flash video player or an HTML 5 video
  • Use native websockets or flash based (or, ew, timer based)
  • Send SVG as native markup, or pre-render it as a PNG

DISCLAIMER: Users can theoretically change their UA string, so it can’t be trusted 100%. It’s an advanced feature, so anyone who does, deserves the broken web experience they get.

Using phpcaniuse

phpcaniuse is a composer project. You’ll need the following require:

"robertlabrie/phpcaniuse": "dev-master"

Then as usual just composer install. The class is a single file, so if you hate composer, you canĀ  just include CanIUse.php. Then just instantiate the object

$can = new phpcaniuse\CanIUse($browser,$data);

The $browser object can come from either get_browser() or phpbrowscap. CanIUse expects the object, not the associative array. $data is a string containing the contents of data.json from the caniuse.com project. Pass it in as a string, do not decode it.

The methods are pretty straight forward:

Function Description
check(list) Checks to see if the browser supports the specified list of features. Takes a string of a single feature, or an array of features. Returns the lowest level of support for the list.
featureList() Returns an associative array of all the features tracked by caniuse. The key is the key used to check a feature, and value is a friendly description.
featureGet(feature) Retuns the set of JSON data for the specified feature.
browserCan() Returns an associative array of all features and the status within the browser. Key is the feature, value is the status.
agentMapAdd(browscapName,caniuseName) Adds a mapping between a browscap name and a caniuse name.
browserSet(browser) Sets the browser object. Must come from get_browser() or phpcaniuse.
dataSet(data) Sets the json data from caniuse. Must be a string, not a json object or array.
dataGet() Returns the caniuse json data as an array.

phpcaniuse maps the browser name returned by browscap with the one used by caniuse. It’s currently done for Firefox, Internet Explorer, Opera, Safari and Chrome. Mobile browsers are not done, but if you can identify a mapping, please tell me.

The package includes a demo.php file which will tell you all about your browser.

Understanding the CanIUse.com data.json

The caniuse.com data.json file is broken up into several sections. I’ve laid it out in a table with my working notes, but the best way to understand it is print_r().

agents ie array of browsers – major ones listed here, many others supported
firefox
browser Firefox Seems to match the ->Browser property from browscap
versions …,”24″,”25″ array of browser versions where the index is used by caniuse
chrome
safari
opera
statuses status codes for features
rec Recommendation
pr Proposed Recommendation
cr Candidate Recommendation
wd Working Draft
other Other
unoff Unofficial / Note
cats categories for capabilities
updated date() the date the data was last updated
data
title A friendly title
description A good description
spec URL to specification
status Relates to the status codes above
stats Associative array of browsers, version feature
firefox Or any agent name
23 Vsersion 23
x Supported or not

The actual status codes are outlined below. They’re based on reverse engineering and might not be 100% accurate:

Stat Description
n Not supported
a Partially Supported
a x Partially Supported with prefix
p Polyfill required
y x Supported with prefix
y Supported
u Unknown
Advertisements

PHP detect browser name and version with phpbrowscap

This is an old problem: how to programatically detect the visitors browser and version. There are some very good reasons for wanting to do this:

  • Known compatibility issues
  • Customizing download or support links
  • Leveraging advanced features in newer browsers

Googling around for this, you always find the objection “someone can use a fake User-Agent string, so don’t bother”. That’s an obnoxious and unhelpful remark which should be deleted from forums. The vast majority of users do not adulterate their UA string, and those who do have to accept the broken experience that comes with it.

PHP has a native function called get_browser() which does the job. It relies on the php_browscap.ini file downloaded from the Browser Capabilities Project. To enable it you set a directive in the php.ini file and you’re off to the races. There is one catch: it can only be configured system wide. So what happens if you’re on shared hosting? Or if you want to keep your own version in git and not use the system version? The solution is phpbrowscap.

Before I get into browscap, there is one other thing tip I want to cover. What if you want this information client side? The Navigator.appName property appears promising, except that Mozilla, Chrome and Safari all return the exceptionally useless “Netscape” value. Only MSIE actually tells you who it is. You can use a server side snippet like this to expose the data client side:

echo "<script type=text/javascript>var Browser=" . json_encode($browser) . ";</script>\n";

This will work for you whether you got $browser from get_browser() or from phpbrowscap.

Phpbrowscap is fully compatible with get_browser() and is usually faster. It processes the browscap.ini file and writes it out to native PHP code so that subsequent loads don’t have to parse the ini. The simplest possible usage is:

require 'Browscap.php';
use phpbrowscap\Browscap;
$bc = new Browscap('/tmp');
$browser = $bc->getBrowser();

The default behaviour is to automatically download the latest version of the browscap.ini file and store it in the cache directory. If you have no access to write anywhere, and do not want auto-update, you need to do something like this:

$browscap = new phpbrowscap\Browscap(__DIR__);
$browscap->doAutoUpdate=false;
$browscap->updateMethod=false;

For this to work, you must have a copy of browscap.ini and cache.php as generated by phpbrowscap. What I did was run the above version on some development box and copy the two files from /tmp to my source directory, then added them to my git.

The documentation isn’t great, so I’ll try to help out a little:

Directive Properties
doAutoUpdate true – default, also false
localFile Path to local copy of browscap. Used for offline updating.
updateMethod Browscap::UPDATE_LOCAL – use a local file specified in localFileBrowscap::UPDATE_FOPEN – Uses the fopen url wrapper (use file_get_contents)Browscap::UPDATE_FSOCKOPEN – Uses the socket functions (fsockopen)

Browscap::UPDATE_CURL – Uses the cURL extension

null or false – do not attempt update

cacheFilename Override the default cache file name of cache.php. Useful if auto-update is off.
iniFilename Override the default ini file name of browscap.ini. Useful if auto-update is off.

There are also some useful helper functions:

  • autodetectProxySettings() – gets settings from the *_proxy environment variables
  • addProxySettings($server, $port = 3128, $wrapper = ‘http’, $username = null, $password = null) – pretty obvious. If you’re wondering, 3128 is used by cntlm.
  • clearProxySettings() – I guess you might want this
  • updateCache() – Rebuild the cache file from ini