Using the Google Analytics API v3 with PHP: Fetching Data

Younes Rafie
Share

In the first part of our series, we introduced the Google Analytics API, including the basic usage. In this part, we will continue creating our demo and see how we can extend it with more functionality.

Google Analytics API

Management API

As we discussed in the first part, the Management API is responsible for getting user accounts, properties, views… for our first example we will retrieve the list of accounts available for our authenticated user.

// app/src/GA_Service.php
public function accounts(){
	if( !$this->isLoggedIn() ){
		//login
	}
	
	$service = new Google_AnalyticsService($this->client);
	$man_accounts = $service->management_accounts->listManagementAccounts();
	$accounts = [];

	foreach ($man_accounts['items'] as $account) {
		$accounts[] = [ 'id' => $account['id'], 'name' => $account['name'] ];
	}

	return $accounts;
}//accounts

// app/controllers/HomeController.php
public function accounts(){
	$accounts = $this->ga->accounts();

	return $accounts;
}//accounts

// app/routes.php
Route::get('/accounts', 'HomeController@accounts');

Inside GA_Service::accounts we create a new Google_AnalyticsService with our authorized client and then query the API for the list of accounts.

In this case the result is an array, but the API also makes use of objects, we just need to specify that inside our GA_Service::init function. In the following examples, I’m going to use array results.

$this->client->setUseObjects(true);

The listManagementAccounts function returns an array containing:

{
    kind: "analytics#accounts",
    username: "me@mail.com",
    totalResults: 3,
    startIndex: 1,
    itemsPerPage: 1000,
    items: [
        {
            id: "4449308",
            kind: "analytics#account",
            selfLink: "https://www.googleapis.com/analytics/v3/management/accounts/4449308",
            name: "me@mail.com",
            permissions: {
                effective: [
                    "COLLABORATE",
                    "EDIT",
                    "MANAGE_USERS",
                    "READ_AND_ANALYZE"
                ]
            },
            created: "2013-10-01T11:04:28.478Z",
            updated: "2013-10-01T11:04:28.478Z",
            childLink: {
                type: "analytics#webproperties",
                href: "https://www.googleapis.com/analytics/v3/management/accounts/4449308/webproperties"
            }
        }
    ]
}

Note that when you return an array as a response, Laravel automatically encodes the result as a JSON response and sends it to the browser.

The result contains information about the total results and some pagination info as well. The items column contains the list of accounts with their IDs, permissions, etc., but we looped through items to extract only the id and name from the accounts.

If you would like to have result pagination, you can always pass more options to listManagementAccount:

$service->management_accounts->listManagementAccounts( [ 'max-results' => $max_results, 'start-index' => $start_index ] );

Let’s assume that we’re going to show our users their list of accounts, and when they select one, we load the list of properties associated with it.

// app/src/GA_Service.php
public function properties( $account_id ){
	if( !$this->isLoggedIn() ){
		//login
	}

	try {
		$service = new Google_AnalyticsService($this->client);
		$man_properties = $service->management_webproperties->listManagementWebproperties($account_id);
		$properties = [];

		foreach ($man_properties['items'] as $property) {
			$properties[] = [ 'id' => $property['id'], 'name' => $property['name'] ];
		}//foreach

		return json_encode($properties);
	} catch (Google_ServiceException $e) {
		return Response::json([
			'status'	=> 0,
			'code'		=> 3,
			'message'	=> $e->getMessage()
		]);
	}//catch

}//properties

// app/controllers/HomeController.php
public function properties( $account_id ){
	$properties = $this->ga->properties( $account_id );

	return $properties;
}//properties
	
	
// app/routes.php
Route::get( '/properties/{account_id}', [ 'uses' => 'HomeController@properties' ] )->where('account_id', '\d+');

The GA_Service::properties accepts an account ID, and returns the list of properties for that account. We basically have the same process, like retrieving accounts.

[
    {
        id: "UA-52317977-1",
        name: "Prop1"
    },
    {
        id: "UA-52317977-2",
        name: "Prop1"
    }
]

Every property has a subset of views. By default, Google adds a view called All Web Site Data for every new property.

Using an ID from the list of properties and the account ID grabbed from the first part, we will query Google Analytics API for the list of available views for a given account property.

// app/src/GA_Service.php
public function views( $account_id, $property_id ){
	if( !$this->isLoggedIn() ){
			//login
	}
		
	try {
		$service = new Google_AnalyticsService($this->client);
		$man_views = $service->management_profiles->listManagementProfiles( $account_id, $property_id );
		$views = [];

		foreach ($man_views['items'] as $view) {
			$views[] = [ 'id' => $view['id'], 'name' => $view['name'] ];
		}//foreach

		return json_encode($views);
	} catch (Google_ServiceException $e) {
		return Response::json([
			'status'	=> 0,
			'code'		=> 3,
			'message'	=> $e->getMessage()
		]);
	}//catch
}//views

// app/controllers/HomeController.php
public function views( $account_id, $property_id ){
	$views = $this->ga->views( $account_id ,$property_id );

	return $views;
}//properties
	
// app/routes.php
Route::get( '/views/{account_id}/{property_id}', [ 'uses' => 'HomeController@views' ] )->where([ 'account_id', '\d+', 'property_id', '\d+' ]);

In the browser, when hitting /views/{account_id}/{property_id} route, we should get something similar to:

// http://localhost:8000/views/44493065/UA-44493083

[
 {
    id: "77278619",
    name: "All Web Site Data"
 }
]

Metadata API

To query some statistics from Google Analytics we need to provide a set of dimensions and metrics.

  • Metrics: metrics are the individual measurements of user activity on your property, such as sessions and pageviews..
  • Dimensions: dimensions break down metrics across some common criteria, such as country or browser.

To grab the list of available metadata you can simply use curl to query data from the following url https://www.googleapis.com/analytics/v3/metadata/ga/columns.

Google Analytics gives us an etag attribute that can be used for caching the response so that we don’t have to query the API on every request.

$gcurl = new Google_CurlIO;
$response = $gcurl->makeRequest( 
    			new Google_HttpRequest(
    			    "https://www.googleapis.com/analytics/v3/metadata/ga/columns" 
    		));
  • Google_CurlIO: a class wrapped with some curlutilities for dealing with caching, authentication, etc – by using this class we ensure the response is cached using the etagattribute.
  • Google_HttpRequest: is a class representing a single HTTP request.

The makeRequest method returns a Google_HttpRequest instance, and we can use the getResponseBody to get our metadata response.

// app/src/GA_Service.php

public function metadata(){
	$gcurl = new Google_CurlIO;
	$response = $gcurl->makeRequest( 
		new Google_HttpRequest( "https://www.googleapis.com/analytics/v3/metadata/ga/columns" ) 
	);

	//verify returned data
	$data = json_decode($response->getResponseBody());
	
	$items = $data->items;
	$data_items = [];
	$dimensions_data = [];
	$metrics_data = [];

	foreach( $items as $item ){
		if( $item->attributes->status == 'DEPRECATED' )
			continue;

		if( $item->attributes->type == 'DIMENSION' )
			$dimensions_data[ $item->attributes->group ][] = $item;

		if( $item->attributes->type == 'METRIC' )
			$metrics_data[ $item->attributes->group ][] = $item;
	}//foreach

	$data_items['dimensions'] = $dimensions_data;
	$data_items['metrics'] = $metrics_data;

	return $data_items;
}//metadata

// app/controllers/HomeController.php
public function metadata(){
	$metadata = $this->ga->metadata();

	return $metadata;
}//metadata
	
// app/routes.php
Route::get('/metadata', 'HomeController@metadata');

Now, when accessing the /metadata route in your browser you should get an array of dimensions and another one for metrics, and each one of them contains a list of grouped elements.

{
dimensions: {
    User: [
        {
            id: "ga:userType",
            kind: "analytics#column",
                attributes: {
                type: "DIMENSION",
                dataType: "STRING",
                group: "User",
                status: "PUBLIC",
                uiName: "User Type",
                description: "A boolean indicating if a user is new or returning. Possible values: New Visitor, Returning Visitor.",
                allowedInSegments: "true"
                }
        },
        ...
    ]
},
metrics: {
    ...
    }
}

To speed up the process we will use bootsnipp. If the user is logged in, we will show the home page.

Home Page

We need to update our HomeController@index to show the home page view.

// app/controllers/HomeController.php
public function index(){
    if( $this->ga->isLoggedIn() ){
    	$metadata = $this->metadata();
    	$dimensions = $metadata['dimensions'];
    	$metrics = $metadata['metrics'];
    	
    	return View::make('home', [ 
    	    'dimensions' => $dimensions, 
    	    'metrics' => $metrics 
        ]);
    }//if
    else{
    	$url = $this->ga->getLoginUrl();
    	return View::make('login', [ 'url' => $url ]);
    }
}//index

As you can see from the screenshot, when the user selects an account we asynchronously change the property and the view accordingly. To achieve that, I wrote some simple JS which you can check in the final repo.

Reporting API

By providing the selected view, metrics and dimensions we can get detailed statistics about users and interactions. The result after the user submission will be something similar to:

array(3) {
  ["items"]=>
  array(10) {
    [0]=>
    array(2) {
      [0]=>
      string(9) "Argentina"
      [1]=>
      string(1) "2"
    }
    [1]=>
    array(2) {
      [0]=>
      string(6) "Brazil"
      [1]=>
      string(2) "31"
    }
    [2]=>
    array(2) {
      [0]=>
      string(6) "France"
      [1]=>
      string(1) "1"
    }
    [3]=>
    array(2) {
      [0]=>
      string(7) "Germany"
      [1]=>
      string(1) "1"
    }
    [4]=>
    array(2) {
      [0]=>
      string(6) "Greece"
      [1]=>
      string(1) "1"
    }
    //...
  }
  ["columnHeaders"]=>
  array(2) {
    [0]=>
    array(3) {
      ["name"]=>
      string(10) "ga:country"
      ["columnType"]=>
      string(9) "DIMENSION"
      ["dataType"]=>
      string(6) "STRING"
    }
    [1]=>
    array(3) {
      ["name"]=>
      string(12) "ga:pageviews"
      ["columnType"]=>
      string(6) "METRIC"
      ["dataType"]=>
      string(7) "INTEGER"
    }
  }
  ["totalResults"]=>
  int(15)
}

Our GA_Service::report accepts four arguments: a view ID, a start and end date and an array of metrics.

Google can’t return all your legacy data – instead we provide a start and end date. In my example, I queried the last month’s results.

The third parameter is the list of metrics which we already have from the user selection.

The fourth optional parameter is an array of options.
max-results: the maximum number of results. (we used 10 to speed up the response).
dimensions: a comma separated list of values. (ga:country,ga:city)
filters: a comma separated list of rules to be appilied to the result .(ga:country!=usa,ga:pageviews>100)
In this example we excluded USA from the list of dimensions and only showed pageviews that are greater than 100.
segment: advanced segment ID to be applied to the data.
sort: order results by dimensions or metrics. Can combine multiple dimensions and metrics. (ga:country,-ga:pageviews = order by ga:country ascending, and by ga:pageviews descending.
start-index: can be used for pagination.

// app/src/GA_Service.php

public function report( $view, $dimensions, $metrics ){
	// to make the request quicker
	$max_results = 10;
	
	// query the last month analytics
	$now = new DateTime();
	$end_date = $now->format('Y-m-d');
	$start_date = $now->modify('-1 month')->format('Y-m-d');

	// if( !is_array( $dimensions ) )
	// 	$dimensions = array( $dimensions );

	$dimensions = implode( ",", $dimensions );
	$metrics = implode( ",", $metrics );

	try{
		$analytics = new Google_AnalyticsService($this->client);
		$options = [];

		$options['dimensions'] = $dimensions;
		$options['max-results'] = $max_results;

		$data = $analytics->data_ga->get( $view, $start_date, $end_date, $metrics,
			$options
		);

		$res = [
			'items' => isset($data['rows']) ? $data['rows'] : [],
			'columnHeaders'	=> $data['columnHeaders'],
			'totalResults'	=> $data['totalResults']
		];

	}catch( Google_ServiceException $ex ){
		return Response::json([
			'status'	=> 0,
			'code'		=> 2,
			'message'	=> 'Google analytics internal server error: (Technical details) ' . $ex->getErrors()[0]['message']
		]);
	}//catch

	return $res;
}//report

// app/controller/HomeController.php
public function report(){
	if( !$this->ga->isLoggedIn() )
		return Response::json([
			'status'	=> 0,
			'code'		=> 1,
			'message'	=> 'Login required'
		]);

	if( !Input::has('dimensions') || !Input::has('metrics') || !Input::has('view') )
		return Response::json([
			'status'	=> 0,
			'code'		=> 1,
			'message'	=> 'Invalid request parametter'
		]);

	$view = 'ga:' . Input::get('view');
	$dimensions = Input::get('dimensions');
	$metrics = Input::get('metrics');

	$report = $this->ga->report( $view, $dimensions, $metrics );

	return View::make('report', [ 'columns' => $report['columnHeaders'], 'items' => $report['items'], 'totalResults' => $report['totalResults' ] ]);
}//metadata

// app/routes.php
Route::post('/report', 'HomeController@report');

After calling the get Google_AnalyticsService::get method, we use the list of result items, column headers, and total results to output the result as a table.

Result Table

Extending The Demo

Now let’s see how we can extend our demo with filters, sorting and segments.

Filters

Filters are a way to exclude some data from the returned result. They take the following form:

ga:column operator value
  • ga:column: dimension or metric id (Ex: ga:country)
  • operator: the operator depends on the choice of metric or dimension column id, check the docs for the list of operators.
  • value: the value can be a number, a string, or a regex.

You can combine multiple filters: you can use comma (,) as an OR operator and a semi-colon (;) as an AND operator.

Segments

By default Google Analytics group all your data in one group called All Sessions. However, you can always choose from the built in segments or create a new one depending on your needs. You can group data by referral, device type, age, gender, etc.

You can extend the demo by adding a new select element with the available segment list, and pass the ID to the get method as discussed previously.

// app/src/GA_Service.php
public function segments(){
	if( !$this->isLoggedIn() ){
		//login
	}

	$service = new Google_AnalyticsService($this->client);
	$segments = $service->management_segments->listManagementSegments();

	return $segments;
}//segments

// app/controllers/HomeController.php

public function segments(){
	$segments = $this->ga->segments();

	return $segments;
}//segments

// app/routes.php

Route::get('/segments', 'HomeController@segments');

You can visit the /segments page to see the list of available segments with their IDs, and you can of course use this as an option as we saw earlier.

Wrapping up

The Google Analytics API is very flexible and provides a lot of features, but the documentation is not complete yet, and doesn’t provide good examples of use. You get a lot more by digging into the source code and testing possibilities and limits.

In this series, we focused on the basic usage of Google Analytics, but you can extend the demo with options from the Google Analytics dashboard.

You can check the final repo for the source code of this tutorial.

Questions? Comments? Let me know!