Google Analytics and server-side PHP

How to call Google Analytics Measurement Protocol in PHP server code

Google published a handy API to implement hit tracking in your projects, the Measurement Protocol. This API is especially useful for APIs and other platforms that cannot use the pre-existing solutions to communicate usage and tracking stats with the Google Analytics servers.

The Protocol

The Analytics Mesurement Protocol API is actually pretty simple. You can access it either through a HTTP GET or HTTP POST request and send along a few variables. The host you send your data to is https://www.google-analytics.com/collect.

I would recommend using POST as you can run into unforseen caching issues either locally or in proxy servers when using GET requests.

The API offers a dizzing amount of parameters to choose from. The example in this article mentions only a handfull of the most basic tracking parameters that are relevant when tracking visits and exceptions that your users encounter.

The PHP Code

The PHP code is split into two classes. An abstract AbstractAnalytics class that serves as a common base for implementing analytics tracking. Then there is the concrete GoogleAnalytics class that implements the specifics of talking to the Google Analytics servers.

The Abstract Base Class

abstract class AbstractAnalytics {

    protected $debug = false;

    public function __construct($debug) {
          $this->debug = $debug;
    }

    /*
    Sends a normal page-view type tracking request to the analytics server
    */
    public function Track($title) {
        if (!method_exists($this, "GetHitRequest")) {
            throw new Exception( "Missing GetHitRequest function");
        }

        $response = $this->_URLPost(
                    $this->getHost(), 
                    $this->GetHitRequest($this->getUrlPath(), 
                                         $title));
        if( $this->debug )
            echo $response;
        return $response;
    }

    /*
    Sends a exception type tracking request to the analytics server
    */
    public function Error($title, $errorcode) {
        if (!method_exists($this, "GetErrorRequest")) {
            throw new Exception( "Missing GetErrorRequest function");
        }

        $response = $this->_URLPost(
                    $this->getHost(), 
                    $this->GetErrorRequest($this->getUrlPath(), 
                                           $title, 
                                           $errorcode));
        if( $this->debug )
            echo $response;
        return $response;
    }

    /*
    Gets the analytics host name (e.g. https://www.google-analytics.com)
    */
    abstract protected function getHost();

    /*
    Gets the full url to the requested resource
    */
    protected function getUrlPath() {
        return 'http' . (isset($_SERVER['HTTPS']) ? 's' : '') . 
           '://' . 
           "{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
    }

    /*
    Gets the user agent attached to the original request
    */
    protected function _getUserAgent() {
        return array_key_exists('HTTP_USER_AGENT', $_SERVER) 
            ? $_SERVER['HTTP_USER_AGENT'] : "";
    }

    /*
    Gets the http referer for the original request
    */
    protected function _getReferer() {
        return array_key_exists('HTTP_REFERER', $_SERVER) 
          ? $_SERVER['HTTP_REFERER'] : "";
    }

    /* 
    Gets the remote ip address for the original request
    */
    protected function _getRemoteIP() {
        return array_key_exists('REMOTE_ADDR', $_SERVER) 
          ? $_SERVER['REMOTE_ADDR'] : "";
    }

    /*
    Performs a POST request of the data in $data_array to the URL in $url
    */
    private function _URLPost($url, $data_array) { 
      // Need to encode spaces, otherwise services such
      // as Google will return 400 bad request!
      $url = str_replace(" ", "%20", $url);

      // Construct the contexts for the POST requests
      $opts = array(
        'https'=>array(
        'method'=>"POST",
        'header'=>
          "Accept: application/json, text/javascript, */*; q=0.01\r\n".
          "Content-type: application/x-www-form-urlencoded; charset=UTF-8\r\n".
          "User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.155 Safari/537.36\r\n".
          "Referer: https://api.example.com/\r\n",
        'content' => http_build_query($data_array)
        )
        ,
        'http'=>array(
        'method'=>"POST",
        'header'=>
          "Accept: application/json, text/javascript, */*; q=0.01\r\n".
          "Content-type: application/x-www-form-urlencoded; charset=UTF-8\r\n".
          "User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.155 Safari/537.36\r\n".
          "Referer: https://api.example.com/\r\n",
        'content' => http_build_query($data_array)
        )
      );

      $context = stream_context_create($opts);
      $result = null;
      $dh = fopen("$url",'rb', false, $context);
      if( !$dh )
        return null;

      if( $dh !== false )
        $result = stream_get_contents($dh);

      fclose($dh);

      return $result; 
  }
}

There are three functions of interest above.

The pure abstract function getHost which all subclasses must implement, this function returns the HTTP address for the Analytics tracking server.

Then it is the public Track function that is called to track a regular hit when a user visits one of your service endpoints. This function returns an array of keyed variable that will make up the content data in the POST.

Finally it is the public Error function that is called to track errors or exceptions that happen in your service endpoints and are presented to your users. Similarly to Track this function returns an array of POST data.

Concrete Google Analytics Class

require_once('AbstractAnalytics.php');

class GoogleAnalytics extends AbstractAnalytics {

    protected $google_host = 'https://www.google-analytics.com/collect';
    protected $google_debug_host = 'https://www.google-analytics.com/debug/collect';

    /* 
    The Google Analytics Tracking Id for this property (e.g. UA-XXXXXX-XX)
    */
    protected $trackingId = '';

    /*
    The name of the application, this is sent to the Google servers
    */
    protected $appName = '';

    public function __construct($TrackingID, $ApplicationName, $debug) {
        parent::__construct($debug);

        $this->trackingId = $TrackingID;
        $this->appName = $ApplicationName;
    }

    protected function getHost() {
      if( $this->debug )
          return $this->google_debug_host;
      return $this->google_host;
    }

    private function getCommonDataArray($url, $title){
        // Standard params
        $v   = 1;
        $cid = $this->_ParseOrCreateAnalyticsCookie();

        return array(
            'v'   => $v,
            'tid' => $this->trackingId,
            'cid' => $cid,
            'an'  => $this->appName,
            'dt'  => $title,
            'dl'  => $url, 
            'ua'  => $this->_getUserAgent(),
            'dr'  => $this->_getReferer(),
            'uip' => $this->_getRemoteIP(),
            'av'  => '1.0'
        );
    }

    protected function GetHitRequest($url, $title) {
        // Create the pageview data
        $data = $this->getCommonDataArray($url, $title);
        $data['t'] = 'pageview';

        // Send PageView hit as POST
        return $data;
    }

    protected function GetErrorRequest($url, $title, $errorcode){
        // Create the error data
        $data = $this->getCommonDataArray($url, $title);
        $data['t']   = 'exception';
        $data['exd'] = $errorcode;
        $data['exf'] = '1';

        return $data;
    }

  // Gets the current Analytics session identifier or 
  // creates a new one if it does not exist
    private function _ParseOrCreateAnalyticsCookie() {
      if (isset($_COOKIE['_ga'])) {
          // An analytics cookie is found
          list($version, $domainDepth, $cid1, $cid2) = preg_split('[\.]', $_COOKIE["_ga"], 4);
          $contents = array(
            'version' => $version,
            'domainDepth' => $domainDepth,
            'cid' => $cid1 . '.' . $cid2
          );
          $cid = $contents['cid'];
      } else {
          // no analytics cookie is found. Create a new one
          $cid1 = mt_rand(0, 2147483647);
          $cid2 = mt_rand(0, 2147483647);

          $cid = $cid1 . '.' . $cid2;
          setcookie('_ga', 'GA1.2.' . $cid, time() + 60 * 60 * 24 * 365 * 2, '/');
      }
      return $cid;
    }
}

This class collects and returns the Google Analytics specific variables for both the Track and Error scenarios. This class automatically switches between the Validation Server if the $debug parameter is set to true. This is very handy when testing your measurement configuration as the validation server will return you JSON data with information about your request. Normally the Analytics servers do not give you any response data back.

And that is it.

You can now instantiate your class and start tracking like so

$analytics = new GoogleAnalytics("UA-XXYYMM-X", "My API", true);
...
$analytics->Track("My Awesome API function");
...
$analytics->Error("NonExistingEndpoint", "404");

Hope you find this useful, please leave comments below if you have any questions or issues.



Software Developer
For hire


Developer & Programmer with +15 years professional experience building software.


Seeking WFH, remoting or freelance opportunities.