My favorites | English | Sign in

Google Base Data API (Labs)

Sample Applications: PHP 4

The PHP Recipe application is a small but complete example that demonstrates the five Google Base API services: queries, insertions, deletions, updates, and batch commands. It uses the standard libcurl library to perform efficient HTTP requests for authentication and interaction with Google Base API.

Contents

  1. Installing the Recipe application
  2. Stepping through the Recipe application code
    1. Setup and token exchange
      1. Building the introductory page
      2. Exchanging the single-session token
    2. Constructing XML requests and parsing XML responses
      1. Creating the item insertion request
      2. Creating the batch deletion request
      3. Parsing the user's items feed
    3. Managing Recipes
      1. Retrieving the user's entries
      2. Inserting new entries
      3. Updating existing entries
      4. Deleting existing entries
      5. Executing a batch command
  3. Troubleshooting

Installing the Recipe application

Download the full source code

You should use PHP 4.0.2 or later, since these versions ship with support for libcurl built-in. If you want to use an earlier version of PHP, you will need to install the libcurl library separately.

After installing the PHP sample application, replace line 4 with a valid API Key tied to your domain.

When you first use the Recipe application, you will have to log in to your Google Account and grant the script permission to access your Google Base entries.

Stepping through the Recipe application code

The Recipe application allows users to upload new recipes to Google Base, edit or delete recipes they have already added, or delete all their recipes with a batch request. It uses GET and POST parameters to maintain two key elements of program state: the user's AuthSub token and requested action (insert, update, delete, or batch).

Setup and token exchange

This section describes how the Recipe application initially interacts with the user and obtains a session token from the AuthSub server.

Building the introductory page: showIntroPage()

When a user first arrives at the Recipe application, no GET or POST parameters are provided to the form. This indicates that the user has not yet obtained a single-session token. The code below sets up a link to the AuthSubRequest page with these options:

  • session, indicating this token can be exchanged for a multi-use (session) token;
  • next, with a link back to the Recipe application;
  • scope, indicating that the Recipe application will only access the items feed.
function showIntroPage() {
  global $itemsFeedURL;

  $next_url  = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
  $redirect_url = 'https://www.google.com/accounts/AuthSubRequest?session=1';
  $redirect_url .= '&next=';
  $redirect_url .= urlencode($next_url);
  $redirect_url .= "&scope=";
  $redirect_url .= urlencode($itemsFeedURL);

  ...
}
      

The remainder of showIntroPage (omitted for brevity) prints the HTML tags to create a table containing this link.

Exchanging the single-session token: exchangeToken()

After the user authenticates to his Google Account using AuthSub, he is redirected back to the Recipe application page (as a result of the next parameter to AuthSubRequest). The redirection URL will contain one GET parameter: the single-use token granted by AuthSub.

The exchangeToken() function below is used to exchange the user's single-use token for a multi-use (session) token. The resulting session token is used for all interaction with the Google Base API: queries, insertions, deletions, updates, and batch commands. exchangeToken() uses the standard procedure for performing an HTTP request with libcurl, namely:

  1. Create a handle for the request with curl_init().
  2. Set the options for the request with curl_setopt(). In this case, we specify the AuthSubSessionToken URL (CURLOPT_URL) as well as the single-use AuthSub token (CURLOPT_HTTPHEADER). We also request that the HTTP response be returned rather than printed directly (CURLOPT_RETURNTRANSFER) and that the request should fail silently if an error occurs (CURLOPT_FAILONERROR).
  3. Execute the request with curl_exec() and catch the result.
  4. Close the handle with curl_close().

Finally, the HTTP response (Token=...) is parsed with the split() function and the result returned.

function exchangeToken($token) {
  $ch = curl_init();    /* Create a CURL handle. */

  curl_setopt($ch, CURLOPT_URL,
    "https://www.google.com/accounts/AuthSubSessionToken");
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_FAILONERROR, true);
  curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'Authorization: AuthSub token="' . $token . '"'
  ));

  $result = curl_exec($ch);  /* Execute the HTTP command. */
  curl_close($ch);

  $splitStr = split("=", $result);
  return trim($splitStr[1]);
}
      

Constructing XML requests and parsing XML responses

This section describes how the Recipe application builds XML requests to be sent to the Google Base feed and how it parses the XML responses.

Creating the item insertion request: buildInsertXML()

The buildInsertXML() function is used to construct the XML document that is sent when the user inserts a new recipe. It simply formats the POST fields submitted by the user (recipe_title, cuisine, and so forth) to conform to the Atom format for a single entry.

function buildInsertXML() {
  $result = "<?xml version='1.0'?>" . "\n";
  $result .= "<entry xmlns='http://www.w3.org/2005/Atom'" .
    " xmlns:g='http://base.google.com/ns/1.0'>" . "\n";
  $result .= "<category scheme='http://base.google.com/categories/itemtypes'" .
    " term='Recipes'/>" . "\n";
  $result .= "<title type='text'>" . $_POST['recipe_title'] . "</title>" .
    "\n";
  $result .= "<g:cuisine>" . $_POST['cuisine'] . "</g:cuisine>" . "\n";
  $result .= "<g:item_type type='text'>Recipes</g:item_type>" . "\n";
  $result .= "<g:cooking_time type='intUnit'>" . $_POST['time_val'] .
             " " . $_POST['time_units'] . "</g:cooking_time>" . "\n";
  $result .= "<g:main_ingredient type='text'>" . $_POST['main_ingredient'] .
    "</g:main_ingredient>" . "\n";
  $result .= "<g:serving_count type='number'>" . $_POST['serves'] .
    "</g:serving_count>" . "\n";
  $result .= "<content>" . $_POST['recipe_text'] . "</content>" . "\n";
  $result .= "</entry>" . "\n";

  return $result;
}
      

Creating the batch deletion request: buildBatchXML()

The buildBatchXML() function creates the XML document used to perform a batch deletion request. It formats the links to each item's feed URI (submitted as POST fields when the user clicks the Delete All button) into the Atom format for batch processing.

function buildBatchXML() {
  $counter = 0;

  $result =  '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
  $result .= '<feed xmlns="http://www.w3.org/2005/Atom"' . "\n";
  $result .= ' xmlns:g="http://base.google.com/ns/1.0"' . "\n";
  $result .= ' xmlns:batch="http://schemas.google.com/gdata/batch">"' . "\n";
  foreach($_POST as $key => $value) {
    if(substr($key, 0, 5) == "link_") {
      $counter++;

      $result .= '<entry>' . "\n";
      $result .= '<id>' . $value . '</id>' . "\n";
      $result .= '<batch:operation type="delete"/>' . "\n";
      $result .= '<batch:id>' . $counter . '</batch:id>' . "\n";
      $result .= '</entry>' . "\n";
    }
  }
  $result .= '</feed>' . "\n";

  return $result;
}
      

Parsing the user's items feed

The Recipe application uses PHP's standard xml_parse function to parse the user's items feed into an internal array of entries. This function executes three provided handlers, or callback functions, as it parses an XML document: one when a start tag is read (startElement()), one when an end tag is read (endElement()) and one when character data is read (characterData()). These handlers coordinate using the following global variables:

  • $parsedEntries (array)
    contains the array of user-entered recipes. This variable is actually an array of arrays: each element of $parsedEntries is itself a hashtable of recipe attributes (keys) and their values.
  • $foundEntry (Boolean)
    specifies whether the application is currently processing an entry (true) or another XML tag (false). $foundEntry is set to true when startElement reads an opening ENTRY tag, and set to false when endElement reads a closing ENTRY tag.
  • $curElement (string):
    denotes the current tag being processed. $curElement is set by startElement(), then used by characterData() to update $parsedEntries.
/**
 * Callback function for XML start tags parsed by
 * xml_parse.
 */
function startElement($parser, $name, $attrs) {
  global $curElement, $foundEntry, $parsedEntries;

  $curElement = $name;
  if($curElement == "ENTRY") {
    $foundEntry = true;
    $parsedEntries[count($parsedEntries)] = array();
  } else if($foundEntry && $curElement == "LINK") {
    $parsedEntries[count($parsedEntries) - 1][$attrs["REL"]] = $attrs["HREF"];
  }
}

/**
 * Callback function for XML end tags parsed by
 * xml_parse.
 */
function endElement($parser, $name) {
  global $curElement, $foundEntry, $parsedEntries;
  if($name == "ENTRY") {
    $foundEntry = false;
  }
}

/**
 * Callback function for XML character data parsed by
 * xml_parse.
 */
function characterData($parser, $data) {
  global $curElement, $foundEntry, $parsedEntries;

  if($foundEntry) {
    $parsedEntries[count($parsedEntries) - 1][strtolower($curElement)] = $data;
  }
}
      

Managing Recipes

This section describes how the Recipe application interacts with the Google Base feed to retrieve, insert, update, and delete items.

Retrieving the user's entries: getItems()

The getItems() function, called to create the array of user-entered recipes, uses libcurl to perform an HTTP GET request on the user's items feed ($itemsFeedURL). It has the same basic structure as exchangeToken() above, with the exception of an added header (X-Google-Key) that specifies the application's developer key. After receiving the HTTP response, getItems() calls xml_parse to parse the Atom feed returned by the server.

function getItems($token) {
  $ch = curl_init();    /* Create a CURL handle. */
  global $developerKey, $itemsFeedURL;

  curl_setopt($ch, CURLOPT_URL, $itemsFeedURL . "?");
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'Content-Type: application/atom+xml',
    'Authorization: AuthSub token="' . trim($token) . '"',
    'X-Google-Key: key=' . $developerKey
  ));

  $result = curl_exec($ch);  /* Execute the HTTP command. */
  curl_close($ch);

  /* Parse the resulting XML. */
  $xml_parser = xml_parser_create();
  xml_set_element_handler($xml_parser, "startElement", "endElement");
  xml_set_character_data_handler($xml_parser, "characterData"); 
  xml_parse($xml_parser, $result);
  xml_parser_free($xml_parser);
}
      

Inserting new entries: postItem()

The postItem() function inserts a new recipe by performing an HTTP POST on the user's items feed. The two curl options of note are:

  • CURLOPT_POST, which specifies that libcurl should execute a POST instead of a GET; and
  • CURLOPT_POSTFIELDS, which specifies the actual data to send (the item insertion request).
function postItem() {
  $ch = curl_init();    /* Create a CURL handle. */
  global $developerKey, $itemsFeedURL;

  /* Set cURL options. */
  curl_setopt($ch, CURLOPT_URL, $itemsFeedURL);
  curl_setopt($ch, CURLOPT_POST, true);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_FAILONERROR, true);
  curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'Authorization: AuthSub token="' . $_POST['token'] . '"',
    'X-Google-Key: key=' . $developerKey,
    'Content-Type: application/atom+xml'
  ));
  curl_setopt($ch, CURLOPT_POSTFIELDS, buildInsertXML());

  $result = curl_exec($ch);  /* Execute the HTTP request. */
  curl_close($ch);           /* Close the cURL handle. */

  return $result;
}
      

Updating existing entries: updateItem()

The updateItem function updates an existing recipe by performing an HTTP PUT on that recipe's feed URI. Since libcurl requires that HTTP PUT data be provided as a file resource, updateItem:

  1. creates a temporary file with the tmpfile function.
  2. writes the item insertion request to this file, then seeks back to the beginning.
  3. executes the curl request, using the options CURLOPT_PUT, CURLOPT_INFILE and CURLOPT_INFILESIZE to specify the PUT data and size.
  4. destroys the temporary file with fclose.
function updateItem() {
  $ch = curl_init();    /* Create a CURL handle. */
  global $developerKey;

  /* Prepare the data for HTTP PUT. */
  $putString = buildInsertXML();
  $putData = tmpfile();
  fwrite($putData, $putString);
  fseek($putData, 0);

  /* Set cURL options. */
  curl_setopt($ch, CURLOPT_URL, $_POST['link']);
  curl_setopt($ch, CURLOPT_PUT, true);
  curl_setopt($ch, CURLOPT_INFILE, $putData);
  curl_setopt($ch, CURLOPT_INFILESIZE, strlen($putString));
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_FAILONERROR, true);
  curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'Authorization: AuthSub token="' . $_POST['token'] . '"',
    'X-Google-Key: key=' . $developerKey,
    'Content-Type: application/atom+xml'
  ));

  $result = curl_exec($ch);  /* Execute the HTTP request. */
  fclose($putData);          /* Close and delete the temp file. */
  curl_close($ch);           /* Close the cURL handle. */

  return $result;
}
      

Deleting existing entries: deleteItem()

The deleteItem() function deletes a recipe the user has entered previously by performing an HTTP DELETE on its feed URI. The DELETE command is specified using the option CURLOPT_CUSTOMREQUEST below.

function deleteItem() {
  $ch = curl_init();
  global $developerKey;

    /* Set cURL options. */
  curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
  curl_setopt($ch, CURLOPT_URL, $_POST['link']);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_FAILONERROR, true);
  curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'Authorization: AuthSub token="' . $_POST['token'] . '"',
    'X-Google-Key: key=' . $developerKey
  ));

  $result = curl_exec($ch);  /* Execute the HTTP request. */
  curl_close($ch);           /* Close the cURL handle.    */

  return $result;
}
      

Executing a batch command: batchDelete()

The batchDelete() function deletes all of the user's recipes by issuing a batch request to the Google Base API. This is accomplished by performing an HTTP POST (specified by CURLOPT_POST) to the batch feed URL, specifying the batch XML document as POST data.

function batchDelete() {
  $ch = curl_init();    /* Create a CURL handle. */
  global $developerKey, $itemsFeedURL;

  /* Set cURL options. */
  curl_setopt($ch, CURLOPT_URL, $itemsFeedURL . "/batch");
  curl_setopt($ch, CURLOPT_POST, true);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_FAILONERROR, true);
  curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'Authorization: AuthSub token="' . $_POST['token'] . '"',
    'X-Google-Key: key=' . $developerKey,
    'Content-Type: application/atom+xml'
  ));
  curl_setopt($ch, CURLOPT_POSTFIELDS, buildBatchXML());

  $result = curl_exec($ch);  /* Execute the HTTP request. */
  curl_close($ch);           /* Close the cURL handle.    */

  return $result;
}
      

You can find more information on batch requests here.

Troubleshooting

Can't get the PHP sample code to work on your server? Here are a few suggestions: