Developing an API [ 362 ] $registry->getObject('template')->getPage()->addTag( 'siteurl', $registry->getSetting('siteurl') ); $registry->getObject('template')- >buildFromTemplates('header.tpl.php', 'main.tpl.php', 'footer.tpl.php'); $controllers = array(); $controllersSQL = "SELECT * FROM controllers WHERE active=1"; $registry->getObject('db')->executeQuery( $controllersSQL ); Next, we need to change the code that gets active controllers from the database. Previously, it set the $controller variable for temporary use. This wasn't a problem initially, because we reset the variable to the active controller after this. However, now it is overriding our default controller. This is simply altered by changing $controller to $cttrlr. while( $cttrlr = $registry->getObject('db')->getRows() ) { $controllers[] = $cttrlr['controller']; } The nal change is to only add authentication-related template bits to the view, if the active controller isn't API. if( $registry->getObject('authenticate')->isLoggedIn() && $controller != 'api') { $registry->getObject('template')->addTemplateBit('userbar', 'userbar_loggedin.tpl.php'); $registry->getObject('template')->getPage()->addTag( 'username', $registry->getObject('authenticate')->getUser()->getUsername() ); } elseif( $controller != 'api' ) { $registry->getObject('template')->addTemplateBit('userbar', 'userbar.tpl.php'); } if( in_array( $controller, $controllers ) ) { require_once( FRAMEWORK_PATH . 'controllers/' . $controller . '/controller.php'); $controllerInc = $controller.'controller'; $controller = new $controllerInc( $registry, true ); } else Download from Wow! eBook <www.wowebook.com> Chapter 11 [ 363 ] { // default controller, or pass control to CMS type system? } $registry->getObject('template')->parseOutput(); print $registry->getObject('template')->getPage()- >getContentToPrint(); ?> Delegating control: API controllers for our features With the basic API controller in place, we can now add delegate controllers for the features on our site. As discussed earlier, in this chapter we will only look at adding support for proles; however, it is easy to extend should you wish to. Prole's delegate Our delegate controller simply needs to store the registry and caller objects, and then depending on the nature of the user's request, either output a list of data, output the data from one instance of a model, update a record via a model, or delete a record via a model. In this instance, we can't process create or delete requests, as creating a prole is done on signup, and this feature requires a logged-in user. Deleting a user has lots of implications, and shouldn't be done easily—it should be something the user can do via the site itself, after a number of conrmations. <?php /** * API Delegate: Profiles * Proof of concept */ class APIDelegate{ private $registry; private $caller; Download from Wow! eBook <www.wowebook.com> Developing an API [ 364 ] The constructor sets the registry and caller objects if a prole ID has been passed, and then it calls the aProfile method. If no prole ID has been passed, it calls the listProfiles method. public function __construct( Registry $registry, $caller ) { $this->caller = $caller; $this->registry = $registry; $urlBits = $this->registry->getObject('url')->getURLBits(); if( isset( $urlBits[2] ) ) { $this->aProfile( intval( $urlBits[2] ) ); } else { $this->listProfiles(); } } The listProfiles method rst calls the APIController's requireAuthentication method. If authentication fails, that method will exit, preventing the rest of the method from being executed. Since we can't create a prole, we should prohibit submission of POST data. If the request is valid (and there isn't any POST data), then we can query the proles table, convert it to JSON, and display it for the consumer. This method should either be optimized to allow pagination or ltering (based on searching for a user's name) or just to show members a user has a connection with (otherwise it could return a lot of data). private function listProfiles() { $this->caller->requireAuthentication(); if( $_SERVER['REQUEST_METHOD'] == 'POST' ) { // we can't create a profile as we already have one! header('HTTP/1.0 405 Method Not Allowed'); exit(); } else { // ideally, we would paginate this, and/or put some filtering in i.e. filter by name starting with A,B,C, etc. $sql = "SELECT user_id, name FROM profile"; $this->registry->getObject('db')->executeQuery( $sql ); $r = array(); Download from Wow! eBook <www.wowebook.com> Chapter 11 [ 365 ] while( $row = $this->registry->getObject('db')->getRows() ) { $r[] = $row; } header('HTTP/1.0 200 OK'); echo json_encode( $r ); exit(); } } If the URL dictates that the consumer is doing something with a specic user prole, then the aProfile method is called. As with the listProfiles method, it rst requires authentication, and then includes the prole model path. private function aProfile( $pid ) { $this->caller->requireAuthentication(); require_once( FRAMEWORK_PATH . 'models/profile.php' ); if( $_SERVER['REQUEST_METHOD'] == 'PUT' ) { If the request method is PUT, it assumes the consumer is trying to update the prole. It veries the logged-in user owns the prole, and if they don't the appropriate HTTP response code is issued. If they do own the prole, the validity of the prole is checked, and then the prole is updated based on the PUT data. if( $pid == $this->registry->getObject('authenticate')- >getUser()->getUserID() ) { $profile = new Profile( $this->registry, $pid ); if( $profile->isValid() ) { $data = $this->caller->getRequestData(); $profile->setName( $this->registry->getObject('db')- >sanitizeData( $data['name'] ) ); $profile->setDinoName( $this->registry->getObject('db')- >sanitizeData( $data['dino_name'] ) ); // etc, set all appropriate methods $profile->save(); header('HTTP/1.0 204 No Content'); exit(); } else { header('HTTP/1.0 404 Not Found'); exit(); Download from Wow! eBook <www.wowebook.com> Developing an API [ 366 ] } } else { header('HTTP/1.0 403 Forbidden'); exit(); } } else { If the request method isn't PUT, then it simply checks that the prole is valid, and returns the prole data to the consumer. Depending on privacy settings, and the relationship between the logged-in user and the user prole, we may want to restrict the data that is presented. $profile = new Profile( $this->registry, $pid ); if( $profile->isValid() ) { header('HTTP/1.0 200 OK'); echo json_encode( $profile->toArray() ); exit(); } else { header('HTTP/1.0 404 Not Found'); exit(); } } } } ?> Tweaking the proles model: validity and data One thing our prole model doesn't do at the moment is provide any indication if a particular prole was found within the database. This can be changed with a new variable, a change to the constructor, and a getter method to return if it is valid or not. It also doesn't have a simple method for returning all of the properties in an array, which can return to the consumer from our API. Download from Wow! eBook <www.wowebook.com> Chapter 11 [ 367 ] Revised controller The additions to the model are highlighted in the code below: /** * Profile constructor * @param Registry $registry the registry * @param int $id the profile ID * @return void */ public function __construct( Registry $registry, $id=0 ) { $this->registry = $registry; if( $id != 0 ) { $this->id = $id; // if an ID is passed, populate based off that $sql = "SELECT * FROM profile WHERE user_id=" . $this->id; $this->registry->getObject('db')->executeQuery( $sql ); if( $this->registry->getObject('db')->numRows() == 1 ) { $this->valid = true; $data = $this->registry->getObject('db')->getRows(); // populate our fields foreach( $data as $key => $value ) { $this->$key = $value; } } else { $this->valid = false; } } else { $this->valid = false; } } Download from Wow! eBook <www.wowebook.com> Developing an API [ 368 ] New getter: isValid() We use a simple getter method to return the value of the valid variable. /** * Is the profile valid * @return bool */ public function isValid() { return $this->valid; } New getter: toArray() This is almost a copy of the toTags() method: /** * Return the users data * @return array */ public function toArray( $prefix='' ) { $r = array(); foreach( $this as $field => $data ) { if( ! is_object( $data ) && ! is_array( $data ) ) { $r[ $field ] = $data; } } return $r; } Depending on privacy settings, we may want to lter the information that is returned to the consumer, depending on their status. An Application Framework API The API we have developed allows other websites and web services to interact with our social network; it doesn't allow any provisions for our users to interact with third-party applications within our site. Let's briey discuss what would be involved in creating such an API, and the practical implications it presents. Download from Wow! eBook <www.wowebook.com> Chapter 11 [ 369 ] To allow third-party developers to build functionality that runs on our social network, we would either need to provide a mechanism for them to upload code to our servers (which is a big security risk, and should never be done), or a way for them to host their code externally, but for our site to interact with it, communicating login information between the two systems, taking the output generated and rendering it through the Dino Space social network. Even by having the code hosted externally, there are still security implications: we would have to ensure no sensitive data (such as passwords) was passed to the application. This would require an alternative method of authentication within the API, either one using API keys or something like the OAuth standard. The developer could also add in malicious HTML or JavaScript, such as code to trigger download of a virus on the user's computer, something to trick the user into entering their password, or any number of other things. Social networks like Facebook get around this problem by allowing two methods of integration: • iFrame: The application is embedded through an iFrame. However, the use of JavaScript is restricted. • Alternatively, the code is written to generate special markup, which Facebook then parses (ensuring it is clean). The other problem with providing such a system is that developers would only use it when the site starts to become popular. Until the site has proven itself developing applications for it will be seen as a waste of developer's time, as there is no guarantee that the site would be successful. One solution: use OpenSocial One potential solution is to use the OpenSocial API we discussed earlier. Because the API is standard across all sites which use it, developers only need to develop their application once, allowing it to be installed and used on any website that makes use of the API. The API also provides a common way to authenticate and access data. Consuming As we have learned from this chapter, creating an API is a very large topic, one which is covered in greater detail in a number of books dedicated to the subject. Let's look at how we can quickly consume our new API using cURL (http://www.php.net/manual/en/intro.curl.php). Download from Wow! eBook <www.wowebook.com> Developing an API [ 370 ] With cURL, we can pass our username and password, and it will handle passing the appropriate values to authenticate against the basic authentication we have in place. <?php First, we need to set our username, password, and the URL we wish to connect to. $username = 'michael'; $password = 'password'; $url = "http://localhost/api/users"; Next, we initialize a connection to the URL. $ch = curl_init($url); We then set a number of options, including the username and password, and if we wish to have the headers returned to us. curl_setopt($ch, CURLOPT_USERPWD, $username.':'.$password); curl_setopt($ch, CURLOPT_VERBOSE, 1); curl_setopt($ch, CURLOPT_NOBODY, 0); curl_setopt($ch, CURLOPT_HEADER, 1); curl_setopt($ch, CURLOPT_FOLLOWLOCATION,1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, array()); We then execute the cURL request and assign the data returned to a variable. $response = curl_exec($ch); We then store any additional information and close the cURL connection. $responseInfo=curl_getinfo($ch); curl_close($ch); ?> In the code above, we have chosen to store the returned headers. For us to be able to process the data returned from the API, we would need to strip out the headers and process them accordingly. Since we use mod_rewrite to make search engine-friendly URLs, we have a redirect header set before the API's header. So we would want to ignore the rst header, and then process the second header. Everything after this would be the response data to the request. If we just want to work with the data returned, we can change the CURLOPT_HEADER option to 0. Download from Wow! eBook <www.wowebook.com> Chapter 11 [ 371 ] POSTing data to our API with cURL To send POST data to our API using cURL, we simply build an array of the POST data we wish to submit, convert the array into a suitable string, and then pass these variables to our cURL request, as illustrated by the following code. First, we set our POST elds: $fields = array( 'field' => urlencode( 'some data' ); $fields_string = ''; foreach( $fields as $key => $value ) { $fields_string .= $key.'='.$value.'&'; } rtrim($fields_string,'&'); We then pass the POST elds to our cURL request: curl_setopt($ch,CURLOPT_POST,count($fields)); curl_setopt($ch,CURLOPT_POSTFIELDS,$fields_string); Summary In this chapter we have looked into the APIs that other social networks offer, and discussed the advantages in providing APIs to our users. We then discussed the various types of APIs available, before settling on REST as an API architecture and developing our API in a RESTful way. Finally, we discussed the implications involved in creating a third-party application API and how OpenSocial, an API we discussed earlier in the chapter, could be used to integrate third-party applications. Download from Wow! eBook <www.wowebook.com> . $this->caller->getRequestData(); $profile->setName( $this->registry->getObject('db' )- >sanitizeData( $data['name'] ) ); $profile->setDinoName( $this->registry->getObject('db' )-. $this->registry->getObject('db' )-& gt;executeQuery( $sql ); if( $this->registry->getObject('db' )-& gt;numRows() == 1 ) { $this->valid = true; $data = $this->registry->getObject('db' )-& gt;getRows(); . 'userbar_loggedin.tpl .php& apos;); $registry->getObject('template' )-& gt;getPage( )-& gt;addTag( 'username', $registry->getObject('authenticate' )-& gt;getUser( )-& gt;getUsername()