Calling a Basic Flickr API Method from PHP

Now that you have used the Flickr API Explorer and documentation to make sense of the details of a given API method and to package a call in the browser, you will now learn how to make a call from a simple third-­party application that you write. In this section, I return to the flickr.photos.search example I used earlier in this chapter:

http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key={api_key}&tags={tag}&per_page={per_page}
      

Specifically, the following:

http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key={api_key}&tags=puppy&per_page=3
      

generates a response similar to this:

 <?xml version="1.0" encoding="utf-8">
 <rsp stat="ok">
 <photos page="1" pages="96293" perpage="3" total="288877">
   <photo id="1153699093" owner="7841384@N07" secret="d1fba451c9" server="1023"
          farm="2" title="willy after bath and haircut" ispublic="1" isfriend="0"
          isfamily="0" />
   <photo id="1154506492" owner="7841384@N07" secret="881ff7c4bc" server="1058"
          farm="2" title="rocky with broken leg" ispublic="1" isfriend="0"
          isfamily="0" />
   <photo id="1153588011" owner="90877382@N00" secret="8a7a559e68" server="1288"
          farm="2" title="DSC 6503" ispublic="1" isfriend="0" isfamily="0" />
 </photos>
 </rsp>
      

In the earlier narrative, I described to you how you can extract from the XML response such quantities as the total number of photos and how to derive from a photo element such as this:

<photo id="1153699093" owner="7841384@N07" secret="d1fba451c9" server="1023"
    farm="2" title="willy after bath and haircut" ispublic="1" isfriend="0"
    isfamily="0" />
      

Here are the URLs for the corresponding photo:

         
        http://www.flickr.com/photos/7841384@N07/1153699093/
    
             
        http://farm2.static.flickr.com/1023/1153699093_d1fba451c9.jpg
    
      

In the following sections, I’ll show you how to instantiate that logic into code. Specifically, we will write a simple third-­party Flickr app in PHP that makes a Flickr API call and converts the response to HTML. We’ll use two important sets of techniques that I will elaborate on in some detail, HTTP clients and XML processing, after which I describe how to use these techniques to make the call to Flickr. Here I focus on PHP, but you can apply these ideas to your language of choice.

[Tip]Tip

When debugging web services, I have found it helpful to use a network protocol analyzer such as Wireshark (http://en.wikipedia.org/wiki/Wireshark). Properly formulating a web service call often requires trial and error. Through its support of HTTP, Wireshark lets you see exactly what was sent and what was received, including HTTP headers, response codes, and entity bodies.

HTTP Clients

Let’s consider first the issue of how to perform an HTTP GET request and retrieve the response in PHP. The function file_get_contents takes a URL and returns the corresponding content in a string, provided the allow_url_fopen option is set to true in the system-­wide php.ini. For example:

<?php
// retrieve Atom feed of recent flower-tagged photos in Flickr
$url = "http://api.flickr.com/services/feeds/photos_public.gne?tags=flower&lang=en-us&format=atom";
$content = file_get_contents($url);
echo $content;
?>
         

If you are using an instance of PHP for which URL access for file_get_contents is disabled (which is not uncommon for shared hosting facilities with security concerns), then you might still be able to use the cURL extension for PHP (libcurl) to perform the same function. libcurl is documented here:

http://us3.php.net/curl

The following getResource function does what file_get_contents does. Note the four steps in using the curl library: initializing the call, configuring options, executing the call, and closing down the handle:

<?php
function getResource($url){
// initialize a handle
        $chandle = curl_init();
// set URL
        curl_setopt($chandle, CURLOPT_URL, $url);
// return results a s string
        curl_setopt($chandle, CURLOPT_RETURNTRANSFER, 1);
// execute the call
        $result = curl_exec($chandle);
        curl_close($chandle);

        return $result;
}
?>
         

The many options you can configure in libcurl are documented here:

http://us3.php.net/manual/en/function.curl-setopt.php

In this book, I use libcurl for HTTP access in PHP. Should you not be able to use libcurl, you can use the libcurl Emulator, a pure-­PHP implementation of libcurl:

http://code.blitzaffe.com/pages/phpclasses/files/libcurl_emulator_52-7

[Note]Note

I will often use curl to demonstrate HTTP requests in this book. More information is available at http://curl.haxx.se/.

A Refresher on HTTP

So, how would you configure the many options of a library such as libcurl? Doing so requires some understanding of HTTP. Although HTTP is a foundational protocol, it’s really quite easy to get along, even as programmers, without knowing the subtleties of HTTP. My goal here is not to describe HTTP in great detail. When you need to understand the protocol in depth, I suggest reading the official specifications; here’s the URL for HTTP 1.0 (RFC 1945):

http://tools.ietf.org/html/rfc1945

And here’s the URL for HTTP 1.1:

http://tools.ietf.org/html/rfc2616

You can also consult the official W3C page:

http://www.w3.org/Protocols/

Reading and digesting the specification is not the best way to learn HTTP for most of us, however. Instead of formally learning HTTP all in one go in its formal glory, I’ve learned different aspects of HTTP at different times, and because that partial knowledge was sufficient for the situation at hand, I felt no need to explore the subtleties of the protocol. It was a new situation that prompted me to learn more. My first encounter with HTTP was simply surfing the Web and using URLs that had http for their prefix. For a little while, I didn’t even know that http wasn’t the only possible scheme in a URI—and that, technically, http:// is not a redundant part of a URI, even if on business cards it might be. (People understand www.apress.com means an address for a page in web browser—and the prefix http:// just looks geeky.)

Later when I learned about HTML forms, I learned that there are two possible values for the method attribute for FORM: get and post.[99]get and put since put is often the complement to get.) For a long time, the only difference I perceived between get and post was that a form that uses the get method generates URLs that include the name/value pairs of the submitted form elements, whereas post doesn’t. The practical upshot for me was that get produces addressable URLs, whereas post doesn’t. I thought I had post figured out as a way of changing the state of resources (as opposed to using get for asking for information)—and then I learned about the formal way of distinguishing between safe and idempotent methods (see the “Safe Methods and Idempotent Methods” sidebar for a further explanation of these terms). Even with post, it turns out that there is a difference between two different forms of form encoding stated in the FORM enctype attribute (application/x-www-form-urlencoded vs. multipart/form-data), a distinction that is not technically part of HTTP but that will have a practical effect on how you programmatically make certain HTTP requests.[100]

Formal Structure of HTTP

When I moved from writing HTML to writing basic web applications, I then learned more about the formal structure of HTTP—and how what I had learned fit within a larger structure of what HTTP is capable of doing. For instance, I learned that, in addition to GET and POST, HTTP defines six other methods, among which was a PUT after all. (It’s just that few, if any, web browsers support PUT.) Let me describe the parts of HTTP 1.1 request and response messages. (I draw some terminology in the following discussion from the excellent presentation of HTTP by Leonard Richardson and Sam Ruby in Restful Web Services.)

An HTTP request is composed of the following pieces:

  • The method (also known as verb or action). In addition to GET and POST, there are six others defined in the HTTP specification: OPTIONS, HEAD, PUT, DELETE, TRACE, and CONNECT. GET and POST are widely used and supported in web browsers and programming libraries.

  • The path—the part of the URL to the right of the hostname.

  • A series of request headers. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html.

  • A request body, which may be empty.

The parts of the HTTP response include the following:

Let’s consider the following example:

http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key={api-key}&tags=puppy&per_page=3
            

To track the HTTP traffic, I’m using curl (with the verbose option) to make the call. (You can also use Wireshark to read the parameters of the HTTP request and response):

curl --verbose "http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key={api-key}&tags=puppy&per_page=3"
            

This is an edited version of what I get:

* About to connect() to api.flickr.com port 80
*   Trying 68.142.214.24... * connected
* Connected to api.flickr.com (68.142.214.24) port 80
> GET /services/rest/?method=flickr.photos.search&api_key={api-key}&tags=puppy&per_page=3 
HTTP/1.1
User-Agent: curl/7.13.2 (i386-pc-linux-gnu) libcurl/7.13.2 OpenSSL/0.9.7e zlib/1.2.2
libidn/0.5.13
Host: api.flickr.com
Pragma: no-cache
Accept: */*

< HTTP/1.1 200 OK
< Date: Tue, 21 Aug 2007 20:42:54 GMT
< Server: Apache/2.0.52
< Set-Cookie: cookie_l10n=en-us%3Bus; expires=Friday, 20-Aug-10 20:42:54 GMT;
path=/; domain=flickr.com
< Set-Cookie: cookie_intl=deleted; expires=Monday, 21-Aug-06 20:42:53 GMT; path=/;
domain=flickr.com
< Content-Length: 570
< Connection: close
< Content-Type: text/xml; charset=utf-8
<?xml version="1.0" encoding="utf-8" ?>
<rsp stat="ok">
<photos page="1" pages="97168" perpage="3" total="291503">
       <photo id="1196703288" owner="69161261@N00" secret="d4e5a75664"
server="1412" farm="2" title="Pomeranian" ispublic="1" isfriend="0" isfamily="0" />
       <photo id="1196707012" owner="58944004@N00" secret="9d88253b87"
server="1200" farm="2" title="Fraggle" ispublic="1" isfriend="0" isfamily="0" />
       <photo id="1195805641" owner="21877391@N00" secret="311d276ec7"
server="1177" farm="2" title="Blue" ispublic="1" isfriend="0" isfamily="0" />
</photos>
</rsp>
            

Let’s break down the specifics of this request/response exchange, as shown in Table 6-2 and Table 6-3, respectively.

Table 6.2. The HTTP Request Parameters in a Flickr Call
Parameter Value
method GET
path /services/rest/?method=flickr.photos.search&api_key={api-key}&tags=puppy&per_page=3
headers Four headers of the following types: User-Agent, Host (which identifies api.flickr.com), Pragma, and Accept
response Empty (typical of GET requests)
Table 6.3. The HTTP Response Parameters in a Flickr Call
Parameter Value
Response code 200 OK
Response headers Seven headers of the following types: Date, Server, Set-Cookie (twice), Content-Length, Connection, Content-Type
Response body The XML document representing the photos that match the query

Keep this example in mind to see how the HTTP request and response are broken down as you continue through this chapter. Notice the structure of having a document (in the body) and a set of headers in both the HTTP request and response structure.

Even though you now understand the basic structure of HTTP, the point is to not have to understand the intricacies of the protocol. You can shield yourself from the details while still taking advantage of the rich functionality of HTTP with the right choice of tools and libraries. Richardson and Ruby provide a helpful shopping list of desirable features in an HTTP client library:

  • Support for HTTPS and SSL certificate validation.

  • Support for what they consider to be the five main HTTP methods: GET, HEAD, POST, PUT, and DELETE. Some give you only GET. Others let you use GET and POST.

  • Lets you customize the request body of POST and PUT requests.

  • Lets you customize the HTTP request headers.

  • Gives you access to the response code and HTTP response headers—not just the body of the response.

  • Lets you communicate through an HTTP proxy.

They list the following features as nice options:

  • Lets you request and handle data compression. The relevant HTTP request/response headers are Accept-Encoding and Encoding.

  • Lets you deal with caching. The relevant HTTP headers are ETag and If-Modified-Since and ETag and Last-Modified.

  • Lets you deal with the most common forms of HTTP authentication: Basic, Digest, and WSSE.

  • Lets you deal with HTTP redirects.

  • Helps you deal with HTTP cookies.

They also make specific recommendations for what to use in various languages, including the following:

XML Processing

Once you have made the HTTP request to the Flickr API, you are left with the second big task of processing the XML document contained in the response body. The topic of how to process XML is a large subject, especially when you consider techniques in multiple languages. What I show you here is one way of parsing XML in PHP 5 through an example involving a reasonably complicated XML document (with namespaces and attributes).

The simpleXML library is built into PHP 5, which is documented here:

http://us3.php.net/simplexml

I found the following article particularly helpful to me in understanding how to handle namespaces and mixed content in simpleXML:

http://devzone.zend.com/node/view/id/688

In the following example, I parse an Atom feed (an example from Chapter 4) and print various parts of the document. I access XML elements as though they are PHP object properties (using ->element-name) and the attributes as though they are members of an array (using ["attribute-name"]), for example, $xml->title and $entry->link["href"]. First I list the code and then the output from the code:

<?php
// An example to show how to parse an Atom feed (with multiple namespaces)
// with SimpleXML
# create the XML document in the $feed string
$feed=<<<EOT
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"
      xmlns:dc="http://purl.org/dc/elements/1.1/">
  <title>Apress :: The Expert's Voice</title>
  <subtitle>Welcome to Apress.com. Books for Professionals,
    by Professionals(TM)...with what the
    professional needs to know(TM)</subtitle>
  <link rel="alternate" type="text/html" href="http://www.apress.com/"/>
  <link rel="self"
        href="http://examples.mashupguide.net/ch06/Apress.Atom.with.DC.xml"/>
  <updated>2007-07-25T12:57:02Z</updated>
  <author>
    <name>Apress, Inc.</name>
    <email>support@apress.com</email>
  </author>
  <id>http://apress.com/</id>
  <entry>
    <title>Excel 2007: Beyond the Manual</title>
    <link href="http://www.apress.com/book/bookDisplay.html?bID=10232"/>
    <id>http://www.apress.com/book/bookDisplay.html?bID=10232</id>
    <updated>2007-07-25T12:57:02Z</updated>
    <dc:date>2007-03</dc:date>
    <summary type="html"
      >&lt;p&gt;&lt;i&gt;Excel 2007: Beyond the Manual&lt;/i&gt; will introduce
those who are already familiar with Excel basics to more advanced features, like
consolidation, ?what-?if analysis, PivotTables, sorting and filtering, and some
commonly used functions. You'll learn how to maximize your efficiency at producing
professional-looking spreadsheets and charts and become competent at analyzing data
using a variety of tools. The book includes practical examples to illustrate
advanced features.&lt;/p&gt;</summary>
  </entry>
  <entry>
    <title>Word 2007: Beyond the Manual</title>
    <link href="http://www.apress.com/book/bookDisplay.html?bID=10249"/>
    <id>http://www.apress.com/book/bookDisplay.html?bID=10249</id>
    <updated>2007-07-25T12:57:10Z</updated>
    <dc:date>2007-03-01</dc:date>
    <summary type="html"
      >&lt;p&gt;&lt;i&gt;Word 2007: Beyond the Manual&lt;/i&gt; focuses on new
features of Word 2007 as well as older features that were once less accessible than
they are now. This book also makes a point to include examples of practical
applications for all the new features. The book assumes familiarity with Word 2003
or earlier versions, so you can focus on becoming a confident 2007
user.&lt;/p&gt;</summary>
  </entry>
</feed>
EOT;

# instantiate a simpleXML object based on the $feed XML
$xml = simplexml_load_string($feed);

# access the title and subtitle elements
print "title: {$xml->title}\n";
print "subtitle: {$xml->subtitle}\n";

# loop through the two link elements, printing all the attributes for each link.

print "processing links\n";
foreach ($xml->link as $link) {
  print "attribute:\t";
  foreach ($link->attributes() as $a => $b) {
    print "{$a}=>{$b}\t";
  }
  print "\n";
}
print "author: {$xml->author->name}\n";

# let's check out the namespace situation

$ns_array = $xml->getDocNamespaces(true);

# display the namespaces that are in the document
print "namespaces in the document\n";
foreach ($ns_array as $ns_prefix=>$ns_uri) {
  print "namespace: ${ns_prefix}->${ns_uri}\n";
}
print "\n";

# loop over all the entry elements
foreach ($xml->entry as $entry) {
  print "entry has the following elements in the global namespace: \t";

  // won't be able to access tags that aren't in the global namespace.
  foreach ($entry->children() as $child) {
    print $child->getName(). " ";
  }
  print "\n";
  print "entry title: {$entry->title}\t link: {$entry->link["href"]}\n";

  // show how to use xpath to get date
  // note dc is registered already to $xml.
  $date = $entry->xpath("./dc:date");
  print "date (via XPath): {$date[0]}\n";

  // use children() to get at date
  $date1 = $entry->children("http://purl.org/dc/elements/1.1/");
  print "date (from children()): {$date[0]}\n";

  }

# add <category term="books" /> to feed -- adding the element will work
# but the tag is in the wrong place to make a valid Atom feed.
# It is supposed to go before the entry elements
$category = $xml->addChild("category");
$category->addAttribute('term','books');

# output the XML to show that category has been added.
$newxmlstring = $xml->asXML();
print "new xml (with category tag): \n$newxmlstring\n";
?>
         

The output from the code is as follows:

title: Apress :: The Expert's Voice
subtitle: Welcome to Apress.com. Books for Professionals,
by Professionals(TM)...with what the professional needs to know(TM)
processing links
attribute: rel=>alternate type=>text/html href=>http://www.apress.com/
attribute: rel=>self
href=>http://examples.mashupguide.net/ch06/Apress.Atom.with.DC.xml
author: Apress, Inc.
namespaces in the document
namespace: ->http://www.w3.org/2005/Atom
namespace: dc->http://purl.org/dc/elements/1.1/

entry has the following elements in the global namespace: title link id
updated summary
entry  title: Excel 2007: Beyond the Manual link:
http://www.apress.com/book/bookDisplay.html?bID=10232
date (via XPath): 2007-03
date (from children()): 2007-03
entry has the following elements in the global namespace: title link id
updated summary
entry  title: Word 2007: Beyond the Manual link:
http://www.apress.com/book/bookDisplay.html?bID=10249
date (via XPath): 2007-03-01
date (from children()): 2007-03-01
new xml (with category tag):
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"
       xmlns:dc="http://purl.org/dc/elements/1.1/">
  <title>Apress :: The Expert's Voice</title>
  <subtitle>Welcome to Apress.com. Books for Professionals,
by Professionals(TM)...with what the professional needs to know(TM)</subtitle>
  <link rel="alternate" type="text/html" href="http://www.apress.com/"/>
  <link rel="self"
        href="http://examples.mashupguide.net/ch06/Apress.Atom.with.DC.xml"/>
  <updated>2007-07-25T12:57:02Z</updated>
  <author>
    <name>Apress, Inc.</name>
    <email>support@apress.com</email>
  </author>
  <id>http://apress.com/</id>
  <entry>
    <title>Excel 2007: Beyond the Manual</title>
    <link href="http://www.apress.com/book/bookDisplay.html?bID=10232"/>
    <id>http://www.apress.com/book/bookDisplay.html?bID=10232</id>
    <updated>2007-07-25T12:57:02Z</updated>
    <dc:date>2007-03</dc:date>
    <summary type="html">&lt;p&gt;&lt;i&gt;Excel 2007: Beyond the
Manual&lt;/i&gt; will introduce those who are already familiar with Excel basics to
more advanced features, like consolidation, what-if analysis, PivotTables, sorting
and filtering, and some commonly used functions. You'll learn how to maximize your
efficiency at producing professional-looking spreadsheets and charts and become
competent at analyzing data using a variety of tools. The book includes practical
examples to illustrate advanced features.&lt;/p&gt;</summary>
  </entry>
  <entry>
    <title>Word 2007: Beyond the Manual</title>
    <link href="http://www.apress.com/book/bookDisplay.html?bID=10249"/>
    <id>http://www.apress.com/book/bookDisplay.html?bID=10249</id>
    <updated>2007-07-25T12:57:10Z</updated>
    <dc:date>2007-03-01</dc:date>
    <summary type="html">&lt;p&gt;&lt;i&gt;Word 2007: Beyond the
Manual&lt;/i&gt; focuses on new features of Word 2007 as well as older features that
were once less accessible than they are now. This book also makes a point to include
examples of practical applications for all the new features. The book assumes
familiarity with Word 2003 or earlier versions, so you can focus on becoming a
confident 2007 user.&lt;/p&gt;</summary>
  </entry>
<category term="books"/></feed>
         

There are certainly alternatives to simpleXML for processing XML in PHP 5, but it provides a comfortable interface for a PHP programmer to XML documents.

[Note]Note

When trying to figure out the structures of PHP objects, consider using one of the following functions: print_r, var_dump, or var_export.

Pulling It All Together: Generating Simple HTML Representations of the Photos

Now we have the two pieces of technology to send an HTTP request to Flickr and parse the XML in the response:

  • The getResource function I displayed earlier that uses the libcurl library of PHP 5

  • The simpleXML library to parse the XML response

I’ll now show you a PHP script that uses these two pieces of functionality to prompt a user for a tag and that returns the list of five HTML-­formatted photos for that tag.

Here’s a breakdown of the logical steps that take place in the following script:

  1. It displays the total number of pictures ($xml->photos['total']).

  2. It iterates through the array of photos through an elaboration of the following loop:

    foreach ($xml->photos->photo as $photo) {
      $id = $photo['id'];
    }
                   
  3. It forms the URL of the thumbnail and the URL of the photo page through the logic contained in the following line:

    $thumb_url ="http://farm{$farmid}.static.flickr.com/{$serverid}/{$id}_{$secret}_t.jpg";
                   

The following is one possible version of such a script.[101]Content-Type HTTP response header of text/html to keep Internet Explorer happy with XHTML, but the output is XHTML 1.0 Strict.)

<?php
header("Content-Type:text/html");
echo '<?xml version="1.0" encoding="utf-8"?>';
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
    <title>flickrsearch.php</title>
  </head>
  <body>
<?php
if (isset($_GET['tag'])) {
   do_search($_GET['tag']);
} else {
?>
     <form action="<?php echo $_SERVER['PHP_SELF']?>" method="get">
     <p>Search for photos with the following tag:
    <input type="text" size="20" name="tag"/> <input type="submit" value="Go!"/></p>
     </form>
<?php
}
?>
<?php

# uses libcurl to return the response body of a GET request on $url
function getResource($url){
  $chandle = curl_init();
  curl_setopt($chandle, CURLOPT_URL, $url);
  curl_setopt($chandle, CURLOPT_RETURNTRANSFER, 1);
  $result = curl_exec($chandle);
  curl_close($chandle);

  return $result;
}

function do_search($tag) {
  $tag = urlencode($tag);

#insert your own Flickr API KEY here

  $api_key = "[API-Key]";
  $per_page="5";
  $url = "http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key={$api_key}&tags={$tag}&per_page={$per_page}";

  $feed = getResource($url);
  $xml = simplexml_load_string($feed);
  print "<p>Total number of photos for {$tag}: {$xml->photos['total']}</p>";

# http://www.flickr.com/services/api/misc.urls.html
# http://farm{farm-id}.static.flickr.com/{server-id}/{id}_{secret}.jpg
foreach ($xml->photos->photo as $photo) {
  $title = $photo['title'];
  $farmid = $photo['farm'];
  $serverid = $photo['server'];
  $id = $photo['id'];
  $secret = $photo['secret'];
  $owner = $photo['owner'];
  $thumb_url = "http://farm{$farmid}.static.flickr.com/{$serverid}/{$id}_{$secret}_t.jpg";
  $page_url = "http://www.flickr.com/photos/{$owner}/{$id}";
  $image_html= "<a href='{$page_url}'><img alt='{$title}' src='{$thumb_url}'/></a>";
  print "<p>$image_html</p>";
}

} # do_search
?>
  </body>
</html>
         

Where Does This Leave Us?

This code allows you to search and display some pictures from Flickr. More important, it is an example of a class of Flickr methods: those that require neither signing nor authorization to be called. You will see in the next section how to determine which of the Flickr API methods fall in that category. In the following sections, you’ll look at generalizing the techniques you have used in studying flickr.photos.search to the other capabilities of the Flickr API.