Integrate Google Book Search into a PHP application

Use PHP to process and integrate data from Google Book Search with a custom Web application

Frequently used acronyms API: Application programming interface

DOM: Document Object Model

HTTP: Hypertext Transfer Protocol

HTML: Hypertext Markup Language

ISBN: International Standard Book Number

REST: Representational state transfer

URL: Uniform Resource Locator

XML: Extensible Markup Language

If you are connected in any way to the publishing industry, or even if you are just an avid reader, chances are that you'll have heard about Google Books. It's Google's attempt to create the world's largest searchable digital library, by scanning millions of books and making them available for preview or purchase online. It's also one of Google's most controversial projects, the focus of a class action lawsuit that was only recently settled in November 2009.

In addition to making for interesting water-cooler conversation, Google Books is also interesting from a developer perspective on account of its Data API. This API allows developers to read and search the Google Books database for books matching user-specified criteria, and use the results of these searches in other Web applications. You can access this API, which follows the REST model, through any XML-capable development toolkit. The API already has client libraries for PHP, Java™, and other common programming languages.

This article will introduce you to the Google Book Search Data API, showing you how to integrate and use book search results with a custom PHP application. It includes examples of searching for books by keyword, by language or by author; retrieving book data (including ISBN numbers and thumbnail images); and adding reviews and ratings to books that are already in the database. Come on in, and get started!

Understanding Book Search feeds

Before you start the PHP code, a few words about the Google Book Search Data API are in order. As with all REST-based services, the API accepts HTTP requests that contain one or more XML-encoded input arguments and returns XML-encoded responses that can be parsed in any XML-aware client. With the Google Book Search Data API, the response always consists of an Atom feed containing the requested information.

A typical Book Search feed includes more than enough information to build a useful and relevant application. To see this for yourself, try to access the URL http://books.google.com/books/feeds/volumes?q=php in your favourite Web browser. This REST method returns a list of books matching the keyword php . The raw XML response to this method (which you can view in the source code of the resulting page) contains detailed information on these books, and might look something like Listing 1:

Listing 1: An example Google Book Search feed

<?xml version='1.0' encoding='UTF-8'?> <feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:gbs='http://schemas.google.com/books/2008' xmlns:dc='http://purl.org/dc/terms' xmlns:gd='http://schemas.google.com/g/2005'> <id>http://www.google.com/books/feeds/volumes</id> <updated>2009-12-28T06:14:28.000Z</updated> <category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/books/2008#volume'/> <title type='text'>Search results for php</title> <link rel='alternate' type='text/html' href='http://www.google.com'/> <link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://www.google.com/books/feeds/volumes'/> <link rel='self' type='application/atom+xml' href='http://www.google.com/books/feeds/volumes?q=php'/> <link rel='next' type='application/atom+xml' href='http://www.google.com/books/feeds/volumes?q=php &start-index=11&max-results=10'/> <author> <name>Google Books Search</name> <uri>http://www.google.com</uri> </author> <generator version='beta'>Google Book Search data API</generator> <openSearch:totalResults>277</openSearch:totalResults> <openSearch:startIndex>1</openSearch:startIndex> <openSearch:itemsPerPage>10</openSearch:itemsPerPage> <entry> <id>http://www.google.com/books/feeds/volumes/tywvv3ULal0C</id> <updated>2009-12-28T06:14:28.000Z</updated> <category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/books/2008#volume'/> <title type='text'>Programming PHP</title> <link rel='http://schemas.google.com/books/2008/thumbnail' type='image/x-unknown' href='http://bks8.books.google.com/books? id=tywvv3ULal0C&printsec=frontcover&img=1&zoom=5 &edge=curl&sig=ACfU3U0WFIZOyvLPjIv7jqlX4XZ7GI4TAg &source=gbs_gdata'/> <link rel='http://schemas.google.com/books/2008/info' type='text/html' href='http://books.google.com/books?id=tywvv3ULal0C&dq=php &ie=ISO-8859-1&source=gbs_gdata'/> <link rel='http://schemas.google.com/books/2008/preview' type='text/html' href='http://books.google.com/books?id=tywvv3ULal0C &printsec=frontcover&dq=php&ie=ISO-8859-1&cd=1 &source=gbs_gdata'/> <link rel='http://schemas.google.com/books/2008/annotation' type='application/atom+xml' href='http://www.google.com/books/feeds/users/me/volumes'/> <link rel='alternate' type='text/html' href='http://books.google.com/books? id=tywvv3ULal0C&dq=php&ie=ISO-8859-1'/> <link rel='self' type='application/atom+xml' href='http://www.google.com/books/feeds/volumes/tywvv3ULal0C'/> <gbs:embeddability value='http://schemas.google.com/books/2008#embeddable'/> <gbs:openAccess value='http://schemas.google.com/books/2008#disabled'/> <gbs:viewability value='http://schemas.google.com/books/2008#view_partial'/> <dc:creator>Rasmus Lerdorf</dc:creator> <dc:creator>Kevin Tatroe</dc:creator> <dc:creator>Peter MacIntyre</dc:creator> <dc:date>2006</dc:date> <dc:description>With style tips and practical programming advice, this book will help you become not just a PHP programmer, but a "good" PHP programmer.</dc:description> <dc:format>521 pages</dc:format> <dc:format>book</dc:format> <dc:identifier>tywvv3ULal0C</dc:identifier> <dc:identifier>ISBN:0596006810</dc:identifier> <dc:identifier>ISBN:9780596006815</dc:identifier> <dc:publisher>O'Reilly Media, Inc.</dc:publisher> <dc:subject>Computers</dc:subject> <dc:title>Programming PHP</dc:title> </entry> <entry> ... </entry> </feed>

Take a quick glance through this output to familiarize yourself with its main elements:

The Google Book Search Data API responds to a REST request with an Atom feed containing the requested data. In most cases, the outermost <feed> element contains <link> elements, which contain URLs for the current, next and previous pages of the result set, and <openSearch:> elements, which contain summary statistics for the search.

element contains elements, which contain URLs for the current, next and previous pages of the result set, and elements, which contain summary statistics for the search. The outermost <feed> element encloses one or more <entry> elements, each representing a book or "volume" matching the search query. Each <entry> contains further information on the book it represents, including the title, description, publication date, author, and publisher. Each <entry> also contains <link> elements, which provide URL links to detailed book information, a thumbnail image, a preview (if available), and annotations such as reviews and ratings.

element encloses one or more elements, each representing a book or "volume" matching the search query. Each contains further information on the book it represents, including the title, description, publication date, author, and publisher. Each also contains elements, which provide URL links to detailed book information, a thumbnail image, a preview (if available), and annotations such as reviews and ratings. The <dc:> namespaced elements within each entry bear special mention. These elements correspond to elements from the Dublin Core Metadata Initiative (DCMI), which provides a set of standard, reusable definitions for simple information markup and access. As Listing 1 illustrates, these elements enclose information on the title, authors, publisher), format, subject, and ISBN identifiers for each book.

Not all Google Book Search Data API functions are publicly accessible in this manner. While you can access search functions without authentication, you only access other functions that modify data (including functions to add reviews and labels, or add books to a user's library), if you are an authenticated user who can provide a valid Google Accounts user name and password. You'll see examples of both types of functions in this article.

Executing Book Search queries

Now that you know how to access Google Book Search results through the public REST API, look at how to do the same thing from within a PHP application. One way is, of course, to use PHP's built-in XML processing extensions (SimpleXML, DOM, or XMLReader) to parse the XML feed returned by Google Book Search and extract the relevant fragments of information from it. However, this isn't very convenient, especially when you deal with large feeds or large volumes of namespaced information. And so, this article will use a different approach: the Zend Framework's Zend_Gdata client library, which is designed specifically for developers trying to integrate PHP applications with Google Data APIs.

The Zend_Gdata library can be downloaded either as part of the Zend Framework or as a stand-alone package (see Related topics for a link). It includes a module specifically for working with the Google Book Search Data API, providing pre-defined classes and methods to simplify data access and authentication. Not only does this library provide a solid, community-tested codebase for your application, but when you use it, you can focus on core application functions, rather than on the nitty-gritties of navigating XML trees or handling custom namespaces.

Listing 2 illustrates how to use the Zend_Gdata client library to retrieve and parse a feed of book search results using the Google Book Search Data API:

Listing 2: Retrieving search results using the Zend_Gdata library

<?php // load Zend Gdata libraries require_once 'Zend/Loader.php'; Zend_Loader::loadClass('Zend_Gdata_Books'); Zend_Loader::loadClass('Zend_Gdata_Books_VolumeQuery'); Zend_Loader::loadClass('Zend_Gdata_ClientLogin'); // set credentials for ClientLogin authentication $user = "xxx@gmail.com"; $pass = "secret"; try { // perform login $client = Zend_Gdata_ClientLogin::getHttpClient( $user, $pass, 'print'); $client->setHeaders('X-Forwarded-For', $_SERVER['REMOTE_ADDR']); $books = new Zend_Gdata_Books($client); // prepare and execute search query $query = new Zend_Gdata_Books_VolumeQuery; $query->setQuery(urlencode('robert crais')); $feed = $books->getVolumeFeed($query); } catch (Exception $e) { die('ERROR:' . $e->getMessage()); } ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>Searching for book titles</title> </head> <body> <h2><?php echo $feed->title; ?></h2> <div> <?php echo $feed->totalResults; ?> result(s) found. </div> <div id="results") <ol> <?php foreach ($feed as $entry): ?> <li> <?php echo $entry->getTitle(); ?> </li> <?php endforeach; ?> </ol> </div> </body> </html>

Listing 2 first loads the Zend class libraries, and then initializes an instance of the Zend_Http_Client class. This client is provided with the user credentials needed to create an authenticated connection to the Google Book Search service. Once an authenticated connection is created, a new instance of the Zend_Gdata_Books class is initialized; this class serves as the control point for all subsequent interaction with the Google Book Search Data API.

The Zend_Gdata_Books method that you're most likely to use is the getVolumeFeed() method, which returns a feed of book titles matching a search query. This method is passed an instance of a configured Zend_Gdata_Books_VolumeQuery class, with the query string set through the setQuery() class method. The response to the getVolumeFeed() method is an Atom feed similar to the one displayed in Listing 1; this feed is automatically parsed and converted into an array of Zend_Gdata_Books_VolumeEntry objects, each representing one <entry> in the feed. It's now a simple matter to iterate over this array, retrieve the details of each entry using object properties, and turn it into an HTML page.

Figure 1 demonstrates the output you might see—a list of all books matching the keyword 'robert crais':

Figure 1. The result of a primitive Google Books API search

With this basic understanding in place, it's quite easy to modify Listing 2 to make it more interactive. Listing 3 demonstrates, adding a search form that can be used to perform a user-supplied book search query:

Listing 3: Retrieving search results matching user-supplied keywords

<?php if (isset($_POST['submit'])) { // load Zend Gdata libraries require_once 'Zend/Loader.php'; Zend_Loader::loadClass('Zend_Gdata_Books'); Zend_Loader::loadClass('Zend_Gdata_Books_VolumeQuery'); Zend_Loader::loadClass('Zend_Gdata_ClientLogin'); // set credentials for ClientLogin authentication $user = "xxx@gmail.com"; $pass = "secret"; try { // perform login $client = Zend_Gdata_ClientLogin::getHttpClient( $user, $pass, 'print'); $client->setHeaders('X-Forwarded-For', $_SERVER['REMOTE_ADDR']); $books = new Zend_Gdata_Books($client); // prepare and execute search query $query = new Zend_Gdata_Books_VolumeQuery; $query->setQuery(urlencode($_POST['q'])); $feed = $books->getVolumeFeed($query); } catch (Exception $e) { die('ERROR:' . $e->getMessage()); } } ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>Searching for book titles</title> </head> <body> <h2>Search</h2> <form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>"> Search for: <input type="text" name="q" value="<?php echo isset($_POST['q']) ? $_POST['q'] : ''; ?>" /> <input type="submit" name="submit" value="Go" /> </form> <?php if (isset($feed)): ?> <h2>Search results for '<?php echo $_POST['q']; ?>'</h2> <div> <?php echo $feed->totalResults; ?> result(s) found. </div> <div id="results") <ol> <?php foreach ($feed as $entry): ?> <li> <a href="<?php echo $entry->getInfoLink()->getHref(); ?>"> <?php echo $entry->getTitle(); ?></a> </li> <?php endforeach; ?> </ol> </div> <?php endif; ?> </body> </html>

Figure 2 demonstrates an example of the output generated by a search for 'indian cooking':

Figure 2. The result of a primitive Google Books API search

You'll notice one addition in Listing 3: the use of the Zend_Gdata_Books_VolumeEntry::getInfoLink() method, which returns a link to the book information page on the Google Book Search Web site. Clicking this link will redirect the user to a page that contains, among other things, a detailed book description and user reviews. Figure 3 has an example of what one such page looks like:

Figure 3. A book detail page

It's also worth pointing out that the query string passed to the Google Book Search Data API must be a URL-encoded string. You'll see that this is done in both the previous listings, with PHP's urlencode() method.

Retrieving detailed book information

As discussed in Listing 1, the volume feed returned by the Google Book Search Data API contains a fair amount of information, apart from the book title: the author and publisher names, the ISBN number, the number of pages, the subject, and so on. With Zend_Gdata_Books, all of this information is represented as a set of objects, which you can access and manipulated within the scope of a PHP script to create a more informative search results page.

To illustrate, consider Listing 4:

Listing 4: Retrieving book details

<?php if (isset($_POST['submit'])) { // load Zend Gdata libraries require_once 'Zend/Loader.php'; Zend_Loader::loadClass('Zend_Gdata_Books'); Zend_Loader::loadClass('Zend_Gdata_Books_VolumeQuery'); Zend_Loader::loadClass('Zend_Gdata_ClientLogin'); // set credentials for ClientLogin authentication $user = "xxx@gmail.com"; $pass = "secret"; try { // perform login $client = Zend_Gdata_ClientLogin::getHttpClient( $user, $pass, 'print'); $client->setHeaders('X-Forwarded-For', $_SERVER['REMOTE_ADDR']); $books = new Zend_Gdata_Books($client); // prepare and execute search query $query = new Zend_Gdata_Books_VolumeQuery; $query->setQuery(urlencode($_POST['q'])); $feed = $books->getVolumeFeed($query); } catch (Exception $e) { die('ERROR:' . $e->getMessage()); } } ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>Searching for book titles</title> <style> .entry { height: 120px; border-bottom: dashed silver 2px; padding-top: 10px; } .thumbnail { float: left; border: solid black 2px; padding: 2px; margin-right: 10px; } .desc { font-style: italic; } .small { font-size: smaller; } </style> </head> <body> <h2>Search</h2> <form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>"> Search for: <input type="text" name="q" value="<?php echo isset($_POST['q']) ? $_POST['q'] : ''; ?>" /> <input type="submit" name="submit" value="Go" /> </form> <?php if (isset($feed)): ?> <h2>Search results for '<?php echo $_POST['q']; ?>'</h2> <div> <?php echo $feed->totalResults; ?> result(s) found. </div> <div id="results"> <?php $x = 1; ?> <?php foreach ($feed as $entry): ?> <?php //print_r($entry); $book = new stdClass; // get title if (is_array($entry->getTitles())) { foreach ($entry->getTitles() as $title) { $book->titles[] = $title->getText(); } } // get authors if (is_array($entry->getCreators())) { foreach ($entry->getCreators() as $creator) { $book->authors[] = $creator->getText(); } } // get publishers if (is_array($entry->getPublishers())) { foreach ($entry->getPublishers() as $publisher) { $book->publishers[] = $publisher->getText(); } } // get publication date if (is_array($entry->getDates())) { $arr = $entry->getDates(); $book->pubdate = (is_object($arr[0])) ? $arr[0]->getText() : 'Unspecified'; } // get ISBN numbers if (is_array($entry->getIdentifiers())) { foreach ($entry->getIdentifiers() as $id) { if (preg_match('/ISBN/', $id->getText())) { $book->isbn[] = $id->getText(); } } } // get first subject if (is_array($entry->getSubjects())) { $arr = $entry->getSubjects(); $book->subject = is_object($arr[0]) ? $arr[0]->getText() : 'Unspecified'; } // get first description if (is_array($entry->getDescriptions())) { $arr = $entry->getDescriptions(); $book->desc = is_object($arr[0]) ? $arr[0]->getText() : 'No description available'; } ?> <div class="entry"> <div class="thumbnail"> <img src="<?php echo ($entry->getThumbnailLink()) ? $entry->getThumbnailLink()->getHref() : ''; ?>" /> </div> <div class="data"> <?php echo $x; ?>. <?php echo ucwords(@implode(': ', $book->titles)); ?><br/> <?php echo @implode(', ', $book->authors); ?> | <?php echo @implode(', ', $book->publishers); ?> | <?php echo $book->subject; ?> | <?php echo date('d M Y', strtotime($book->pubdate)); ?> <br/> <span class="desc"><?php echo $book->desc; ?></span> <br/> <span class="small"><?php echo @implode(', ', $book->isbn); ?> | <a href="<?php echo $entry->getInfoLink()->getHref(); ?>"> More information</a> </span> </div> </div> <?php $x++; ?> <?php endforeach; ?> <?php endif; ?> </body> </html>

Listing 4 introduces a number of new methods of the Zend_Gdata_Books_VolumeEntry class. Here's a quick list of the important ones:

The getDates() method returns the publication date of the book

method returns the publication date of the book The getCreators() method returns the names of the authors

method returns the names of the authors The getPublishers() method returns the names of the publishers

method returns the names of the publishers The getSubjects() and getDescriptions() methods return the book subjects and description

and methods return the book subjects and description The getTitles() method returns the book title (and sub-title, if present)

method returns the book title (and sub-title, if present) The getIdentifiers() method returns an array of the ISBN-10 and ISBN-13 numbers for the book, as well as its Google Book Search record ID

method returns an array of the ISBN-10 and ISBN-13 numbers for the book, as well as its Google Book Search record ID The getThumbnailLink() method returns the URL to a thumbnail image of the book cover

method returns the URL to a thumbnail image of the book cover The getVolumeId() method returns the unique volume identifier of the book within the Google Book Search service

All of these methods return an array of objects, corresponding to the various Dublin Core metadata elements; it's then up to the developer to access the elements of each array and use the information within it to reconstitute the search result. Figure 4 illustrates an example of the result.

Figure 4. The result of a Google Books API search, with search results enhanced to include a cover image and additional book data

Using query filters

You might have noticed that, in all the previous examples, the search results feed only contains 10 entries, even though the <openSearch:> elements actually indicate a much higher number of matches. This is because, by default, Google Book Search feeds are limited to 10 matches per feed. This is by no means set in stone; you can easily customize the API output by adding some of the following parameters to your REST query:

The start-index parameter, which specifies the start offset for the entries in a feed

parameter, which specifies the start offset for the entries in a feed The max-results parameter, which specifies the number of entries in a feed

parameter, which specifies the number of entries in a feed The min-viewability parameter, which specifies whether feed entries only include those books for which a partial or complete preview exists

In addition to these parameters, you can also add additional filters to a search query to search for books by title, author, publisher, language, subject, description, or ISBN number. Listing 5 illustrates how this may be done:

Listing 5: Filtering search results by author, title and viewability

<?php if (isset($_POST['submit'])) { // load Zend Gdata libraries require_once 'Zend/Loader.php'; Zend_Loader::loadClass('Zend_Gdata_Books'); Zend_Loader::loadClass('Zend_Gdata_Books_VolumeQuery'); Zend_Loader::loadClass('Zend_Gdata_ClientLogin'); // set credentials for ClientLogin authentication $user = "xxx@gmail.com"; $pass = "secret"; try { // perform login $client = Zend_Gdata_ClientLogin::getHttpClient( $user, $pass, 'print'); $client->setHeaders('X-Forwarded-For', $_SERVER['REMOTE_ADDR']); $books = new Zend_Gdata_Books($client); // prepare and execute search query $query = new Zend_Gdata_Books_VolumeQuery; $queryStr = ''; if (!empty($_POST['title'])) { $queryStr .= '+intitle:'.urlencode($_POST['title']); } if (!empty($_POST['author'])) { $queryStr .= '+inauthor:'.urlencode($_POST['author']); } $query->setQuery($queryStr); $query->setMinViewability($_POST['v']); $query->setMaxResults(20); $feed = $books->getVolumeFeed($query); } catch (Exception $e) { die('ERROR:' . $e->getMessage()); } } ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>Searching for book titles</title> <style> .entry { height: 120px; border-bottom: dashed silver 2px; padding-top: 10px; } .thumbnail { float: left; border: solid black 2px; padding: 2px; margin-right: 10px; } .desc { font-style: italic; } .small { font-size: smaller; } </style> </head> <body> <h2>Search</h2> <form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>"> Title: <input type="text" name="title" value="<?php echo isset($_POST['title']) ? $_POST['title'] : ''; ?>" /> Author: <input type="text" name="author" value="<?php echo isset($_POST['author']) ? $_POST['author'] : ''; ?>" /> Viewability: <select name="v"> <option value="none">Any</option> <option value="partial_view">Partial</option> <option value="full_view">Full</option> </select> <input type="submit" name="submit" value="Go" /> </form> <?php if (isset($feed)): ?> <h2>Search results for '<?php echo $query->getQuery(); ?>' </h2> <div> <?php echo $feed->totalResults; ?> result(s) found. </div> <div id="results"> <?php $x = 1; ?> <?php foreach ($feed as $entry): ?> <?php //print_r($entry); $book = new stdClass; // get title if (is_array($entry->getTitles())) { foreach ($entry->getTitles() as $title) { $book->titles[] = $title->getText(); } } // get authors if (is_array($entry->getCreators())) { foreach ($entry->getCreators() as $creator) { $book->authors[] = $creator->getText(); } } // get publishers if (is_array($entry->getPublishers())) { foreach ($entry->getPublishers() as $publisher) { $book->publishers[] = $publisher->getText(); } } // get publication date if (is_array($entry->getDates())) { $arr = $entry->getDates(); $book->pubdate = (is_object($arr[0])) ? $arr[0]->getText() : 'Unspecified'; } // get ISBN numbers if (is_array($entry->getIdentifiers())) { foreach ($entry->getIdentifiers() as $id) { if (preg_match('/ISBN/', $id->getText())) { $book->isbn[] = $id->getText(); } } } // get first subject if (is_array($entry->getSubjects())) { $arr = $entry->getSubjects(); $book->subject = is_object($arr[0]) ? $arr[0]->getText() : 'Unspecified'; } // get first description if (is_array($entry->getDescriptions())) { $arr = $entry->getDescriptions(); $book->desc = is_object($arr[0]) ? $arr[0]->getText() : 'No description available'; } ?> <div class="entry"> <div class="thumbnail"> <img src="<?php echo ($entry->getThumbnailLink()) ? $entry->getThumbnailLink()->getHref() : ''; ?>" /> </div> <div class="data"> <?php echo $x; ?>. <?php echo ucwords(@implode(': ', $book->titles)); ?><br/> <?php echo @implode(', ', $book->authors); ?> | <?php echo @implode(', ', $book->publishers); ?> | <?php echo $book->subject; ?> | <?php echo date('d M Y', strtotime($book->pubdate)); ?> <br/> <span class="desc"><?php echo $book->desc; ?></span> <br/> <span class="small"><?php echo @implode(', ', $book->isbn); ?> | <a href="<?php echo $entry->getInfoLink()->getHref(); ?>"> More information</a> </span> </div> </div> <?php $x++; ?> <?php endforeach; ?> <?php endif; ?> </body> </html>

In this revision of Listing 4, the user is presented with separate search fields for title, author, and visibility. Depending on the input received, the search query is constructed with appropriate modifiers and transmitted to the Google Book Search API. Notice the setMinViewability() and setMaxResults() method, which provide an object-oriented interface to the min-viewability and max-results input parameters.

Figure 5 and Figure 6 illustrate the output of Listing 5, the former displaying the result of a title search and the latter displaying the result of a combined author/title search.

Figure 5. The result of a Google Books search, filtered by title

Figure 6. The result of a Google Books API search, filtered by author and title name

Accessing user libraries

In addition to allowing public searches, Google Book Search also allows authenticated users to create their own virtual library of books. This "My Library" feature allows users to review and label books, and create and share book collections with other users. Like other Google Book Search features, it is available through the Google Book Search Data API.

To see how the "My Library" feature works, log in to Google Book Search using your Google Account, enter a search term, and use the "Add to my library" link that appears next to each search result to begin creating a virtual library. You can access this library at any time using the "My Library" link that appears in the top right corner of each page. Figure 7 illustrates an example of one such user library:

Figure 7. A user library on the Google Books Web site

The contents of the authenticated user's library can also be obtained as an Atom feed, suitable for parsing in a PHP application. Like the volume feed described in Listing 1, this feed contains a set of <entry> elements, each representing a book in the user's library. This feed can be parsed into a set of PHP objects through the Zend_Gdata_Books object's getUserLibraryFeed() method, and can then be used to generate an HTML view of the library. Listing 6 illustrates:

Listing 6: Retrieving user library contents

<?php // load Zend Gdata libraries require_once 'Zend/Loader.php'; Zend_Loader::loadClass('Zend_Gdata_Books'); Zend_Loader::loadClass('Zend_Gdata_ClientLogin'); // set credentials for ClientLogin authentication $user = "xxx@gmail.com"; $pass = "secret"; try { // perform login $client = Zend_Gdata_ClientLogin::getHttpClient( $user, $pass, 'print'); $client->setHeaders('X-Forwarded-For', $_SERVER['REMOTE_ADDR']); $books = new Zend_Gdata_Books($client); // get authenticated user's library feed $feed = $books->getUserLibraryFeed(); } catch (Exception $e) { die('ERROR:' . $e->getMessage()); } ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>Displaying a user's library</title> <style> .entry { height: 120px; border-bottom: dashed silver 2px; padding-top: 10px; } .thumbnail { float: left; border: solid black 2px; padding: 2px; margin-right: 10px; } .desc { font-style: italic; } .small { font-size: smaller; } </style> </head> <body> <?php if (isset($feed)): ?> <h2>My Library</h2> <div> <?php echo $feed->totalResults; ?> result(s) found. </div> <div id="results"> <?php $x = 1; ?> <?php foreach ($feed as $entry): ?> <?php $book = new stdClass; // get title if (is_array($entry->getTitles())) { foreach ($entry->getTitles() as $title) { $book->titles[] = $title->getText(); } } // get authors if (is_array($entry->getCreators())) { foreach ($entry->getCreators() as $creator) { $book->authors[] = $creator->getText(); } } // get publishers if (is_array($entry->getPublishers())) { foreach ($entry->getPublishers() as $publisher) { $book->publishers[] = $publisher->getText(); } } // get publication date if (is_array($entry->getDates())) { $arr = $entry->getDates(); $book->pubdate = (is_object($arr[0])) ? $arr[0]->getText() : 'Unspecified'; } // get ISBN numbers if (is_array($entry->getIdentifiers())) { foreach ($entry->getIdentifiers() as $id) { if (preg_match('/ISBN/', $id->getText())) { $book->isbn[] = $id->getText(); } } } // get first subject if (is_array($entry->getSubjects())) { $arr = $entry->getSubjects(); $book->subject = is_object($arr[0]) ? $arr[0]->getText() : 'Unspecified'; } // get first description if (is_array($entry->getDescriptions())) { $arr = $entry->getDescriptions(); $book->desc = is_object($arr[0]) ? $arr[0]->getText() : 'No description available'; } ?> <div class="entry"> <div class="thumbnail"> <img src="<?php echo ($entry->getThumbnailLink()) ? $entry->getThumbnailLink()->getHref() : ''; ?>" /> </div> <div class="data"> <?php echo $x; ?>. <?php echo ucwords(@implode(': ', $book->titles)); ?><br/> <?php echo @implode(', ', $book->authors); ?> | <?php echo @implode(', ', $book->publishers); ?> | <?php echo $book->subject; ?> | <?php echo date('d M Y', strtotime($book->pubdate)); ?> <br/> <span class="desc"><?php echo $book->desc; ?></span> <br/> <span class="small"><?php echo @implode(', ', $book->isbn); ?> | Added to library on: <?php echo ($entry->getPublished()) ? date('d M Y', strtotime($entry->getPublished()->getText())) : ''; ?> | <a href="<?php echo $entry->getInfoLink()->getHref(); ?>" >More information</a> </span> </div> </div> <?php $x++; ?> <?php endforeach; ?> <?php endif; ?> </body> </html>

Most of this should be familiar to you from the previous sections of this article. The script opens an authenticated connection to the Google Book Search service, initializes a Zend_Gdata_Books object, and uses the getUserLibraryFeed() method to retrieve a feed containing a list of books in the user's library. This feed is represented as an array of PHP objects, and it is now a simple matter to iterate over the array, process each entry and display the results with appropriate HTML markup.

Figure 8 illustrates an example of the result:

Figure 8. The result of a Google Books API request for a user's library feed

Adding books to user libraries

That takes care of retrieving the contents of a user's library...but what about adding new titles to it? With the Google Book Search Data API, you simply post an XML-encoded <entry> containing the book's unique volume identifier to the user's library feed. Listing 7 has an example of what such a POST request might look like:

Listing 7: An example POST request for adding a book to a user's library

POST /books/feeds/users/me/collections/library/volumes HTTP/1.1 Host: books.google.com Connection: close User-Agent: MyCompany-MyApp-1.0 Zend_Framework_Gdata/1.9.0 authorization: GoogleLogin Content-Type: application/atom+xml Accept-encoding: identity Content-Length: 97 <atom:entry xmlns:atom="http://www.w3.org/2005/Atom"> <atom:id>BOOK_VOLUME_ID_HERE</atom:id> </atom:entry>

To accomplish the same task with Zend_Gdata_Books, initialize a new Zend_Gdata_Books_VolumeEntry object, assign it the volume identifier, and attach it to the user's library feed with the insertVolume() method. Listing 8 illustrates the code:

Listing 8: Adding books to a user's library

<?php // load Zend Gdata libraries require_once 'Zend/Loader.php'; Zend_Loader::loadClass('Zend_Gdata_Books'); Zend_Loader::loadClass('Zend_Gdata_ClientLogin'); // set credentials for ClientLogin authentication $user = "xxx@gmail.com"; $pass = "secret"; try { // perform login $client = Zend_Gdata_ClientLogin::getHttpClient( $user, $pass, 'print'); $books = new Zend_Gdata_Books($client); // add book to user's library using volume ID $id = 'BOOK_VOLUME_ID_HERE'; $entry = new Zend_Gdata_Books_VolumeEntry(); $entry->setId(new Zend_Gdata_App_Extension_Id($id)); $books->insertVolume( $entry, Zend_Gdata_Books::MY_LIBRARY_FEED_URI ); // display success message echo "Volume added successfully with ID: $id"; } catch (Exception $e) { die('ERROR:' . $e->getMessage()); } ?>

Adding book reviews and labels

In addition to allowing users to add books to their library, the Google Book Search API also allows them to add reviews and labels to books. Reviews and labels are stored as what Google calls "annotations" and, if you look back at Listing 1, you'll see that every entry includes an annotation URL. To add a review or a label to a book, therefore, you simply need to create an XML-encoded <entry> for the book, attach the review and/or labels to this entry, and POST the entire thing to the book's annotation URL.

Listing 9 has an example of what such a POST request might look like:

Listing 9: An example POST request for adding a book review

POST /books/feeds/users/me/volumes HTTP/1.1 Host: www.google.com Connection: close User-Agent: MyCompany-MyApp-1.0 Zend_Framework_Gdata/1.9.0 authorization: GoogleLogin Accept-encoding: identity Content-Type: application/atom+xml Content-Length: 3291 <atom:entry xmlns:atom="http://www.w3.org/2005/Atom"> <openAccess xmlns="http://schemas.google.com/books/2008" value="http://schemas.google.com/books/2008#disabled"/> <atom:category term="http://schemas.google.com/books/2008#volume" scheme="http://schemas.google.com/g/2005#kind"/> <atom:id>http://www.google.com/books/feeds/volumes/BOOK_VOLUME_ID_HERE </atom:id> <atom:link href="http://bks7.books.google.com/books?id=BOOK_VOLUME_ID_HERE &printsec=frontcover&img=1&zoom=5&sig=ACfU3U0ayCK47roiq 7r_hf_Iy-tQ&source=gbs_gdata" rel="http://schemas.google.com/books/2008/thumbnail" type="image/x-unknown"/> <atom:link href="http://books.google.com/books?id=BOOK_VOLUME_ID_HERE &ie=ISO-8859-1&source=gbs_gdata" rel="http://schemas.google.com/books/2008/info" type="text/html"/> <atom:link href="http://www.google.com/books/feeds/users/me/volumes" rel="http://schemas.google.com/books/2008/annotation" type="application/atom+xml"/> <atom:link href="http://books.google.com/books?id=BOOK_VOLUME_ID_HERE &ie=ISO-8859-1" rel="alternate" type="text/html"/> <atom:link href="http://www.google.com/books/feeds/volumes/BOOK_VOLUME_ID_HERE" rel="self" type="application/atom+xml"/> <atom:title type="text">The Two Minute Rule</atom:title> <atom:updated>2009-12-28T10:15:44.000Z</atom:updated> <dc:creator xmlns:dc="http://purl.org/dc/terms">Robert Crais</dc:creator> <dc:date xmlns:dc="http://purl.org/dc/terms">2006-01-01</dc:date> <dc:format xmlns:dc="http://purl.org/dc/terms">Dimensions 10.8x17.2x3.0 cm </dc:format> <dc:format xmlns:dc="http://purl.org/dc/terms">465 pages</dc:format> <dc:format xmlns:dc="http://purl.org/dc/terms">book</dc:format> <dc:identifier xmlns:dc="http://purl.org/dc/terms">BOOK_VOLUME_ID_HERE </dc:identifier> <dc:identifier xmlns:dc="http://purl.org/dc/terms">ISBN:1111111111 </dc:identifier> <dc:identifier xmlns:dc="http://purl.org/dc/terms">ISBN:1111111111111 </dc:identifier> <dc:language xmlns:dc="http://purl.org/dc/terms">en</dc:language> <dc:publisher xmlns:dc="http://purl.org/dc/terms">Pocket Books </dc:publisher> <dc:subject xmlns:dc="http://purl.org/dc/terms">Fiction / Action & Adventure </dc:subject> <dc:subject xmlns:dc="http://purl.org/dc/terms">Fiction / Suspense </dc:subject> <dc:subject xmlns:dc="http://purl.org/dc/terms">Fiction / Action & Adventure </dc:subject> <dc:title xmlns:dc="http://purl.org/dc/terms">The Two Minute Rule </dc:title> <gbs:embeddability xmlns:gbs="http://schemas.google.com/books/2008" value="http://schemas.google.com/books/2008#not_embeddable"/> <gd:rating xmlns:gd="http://schemas.google.com/g/2005" min="1" max="5" average="4.20"/> <gbs:review xmlns:gbs="http://schemas.google.com/books/2008"> This book is amazing - v!</gbs:review> <gbs:viewability xmlns:gbs="http://schemas.google.com/books/2008" value="http://schemas.google.com/books/2008#view_no_pages"/> </atom:entry>

Listing 10 has an example of posting a review using the volume identifier of the target book and the Zend_Gdata library:

Listing 10: Adding a book review

<?php // load Zend Gdata libraries require_once 'Zend/Loader.php'; Zend_Loader::loadClass('Zend_Gdata_Books'); Zend_Loader::loadClass('Zend_Gdata_ClientLogin'); // set credentials for ClientLogin authentication $user = "xxx@gmail.com"; $pass = "secret"; try { // perform login $client = Zend_Gdata_ClientLogin::getHttpClient( $user, $pass, 'print'); $books = new Zend_Gdata_Books($client); // add review to book $id = 'BOOK_VOLUME_ID_HERE'; $entry = $books->getVolumeEntry($id); $review = new Zend_Gdata_Books_Extension_Review(); $review->setText("This book is amazing - v!"); $entry->setReview($review); $books->insertVolume( $entry, $entry->getAnnotationLink()->getHref() ); // display success message echo "Review successfully added with ID: $id"; } catch (Exception $e) { die('ERROR:' . $e->getMessage()); } ?>

In Listing 10, the getVolumeEntry() method is used to retrieve the current entry for a book using its volume identifier, and a Zend_Gdata_Books_Extension_Review object containing the review text is then attached to the entry through the setReview() method. The insertVolume() method then takes care of creating the POST request and updating the entry on the Google servers.

In a similar vein, Google Book Search also allows users to tag, or "label", each book with descriptive keywords. This is a useful community feature that can produce more efficient and relevant search results. Listing 11 has an example of how to programmatically attach these labels to a book entry:

Listing 11: Adding book labels

<?php // load Zend Gdata libraries require_once 'Zend/Loader.php'; Zend_Loader::loadClass('Zend_Gdata_Books'); Zend_Loader::loadClass('Zend_Gdata_ClientLogin'); // set credentials for ClientLogin authentication $user = "xxx@gmail.com"; $pass = "secret"; try { // perform login $client = Zend_Gdata_ClientLogin::getHttpClient( $user, $pass, 'print'); $books = new Zend_Gdata_Books($client); // add labels to book $id = 'BOOK_VOLUME_ID_HERE'; $entry = $books->getVolumeEntry($id); $entry->setCategory(array( new Zend_Gdata_App_Extension_Category('crime', 'http://schemas.google.com/books/2008/labels'), new Zend_Gdata_App_Extension_Category('suspense', 'http://schemas.google.com/books/2008/labels'), new Zend_Gdata_App_Extension_Category('elvis cole', 'http://schemas.google.com/books/2008/labels') )); $books->insertVolume( $entry, $entry->getAnnotationLink()->getHref() ); // display success message echo "Labels successfully added for ID: $id"; } catch (Exception $e) { die('ERROR:' . $e->getMessage()); } ?>

In Listing 11, each label is represented as a Zend_Gdata_App_Extension_Category instance, and these instances are then attached to the book entry with the setCategory() method. The insertVolume() method is then invoked to add the labels to the book entry in the Google Book Search database.

Reviews and labels added to Google Book Search are not private, and are visible to all Internet users, either through the Google Books Web site or through a public feed URL. To illustrate, consider that you can view all the reviews and labels created by a particular user, simply by accessing the user's annotation feed at the URL http://books.google.com/books/feeds/users/USER_ID/volumes. Note that the USER_ID in this URL must be the user's unique identification number on Google Book Search and not his or her Google Accounts user name. You'll find the Google Book Search identification number of the user in his or her "My Library" URL link.

Conclusion

The Google Book Search Data API, although still under development, offers tremendous potential in allowing developers to integrate book search results into a Web application. The examples in this article introduced you to Google Book Search volume feeds; showed you how to search for videos by keyword, author, and title; and illustrated how to extract book meta-data, including publisher and author information, thumbnail images and ISBN numbers, from search result feeds. It also gave you a crash course in the community features of Google Book Search, illustrating how to programmatically add reviews and labels to book entries.

As these examples prove, the Google Book Search REST API offers developers a great deal of flexibility and freedom when it comes to creating new Web applications. It's very useful if you're trying to mash up book search data with data from other Web services, or simply building a customized search interface for authors, publishers, or consumers. Play with it sometime, and see what you think!

Downloadable resources

Related topics