Create a REST API with PHP One of the latest (sort of) crazes sweeping the net is APIs, more

specifically those that leverage REST. It¶s really no surprise either, as consuming REST APIs is so incredibly easy« in any language. It¶s also incredibly easy to create them as you essentially use nothing more than an HTTP spec that has existed for ages. One of the few things that I give Rails credit for is its well thought-out REST support, both for providing and consuming these APIs (as its been explained by all the Rails fanboys I work with). Seriously, if you¶ve never used REST, but you¶ve ever had to work with (or worse, create) a SOAP API, or simply opened a WSDL and had your head explode, boy do I have good news for you! So, What on Earth is REST? Why Should You Care? Before we get into writing some code, I want to make sure everyone¶s got a good understanding of what REST is and how its great for APIs. First, technically speaking, REST isn¶t specific to just APIs, it¶s more of a generic concept. However, obviously, for the sake of this article we¶ll be talking about it in the context of an API. So, let¶s look at the basic needs of an API and how REST addresses them. Requests All APIs need to accept requests. Typically, with a RESTful API, you¶ll have a well-defined URL scheme. Let¶s say you want to provide an API for users on your site (I know, I always use the ³users´ concept for my examples). Well, your URL structure would probably be something like, ³api/users´ and ³api/users/[id]´ depending on the type of operation being requested against your API. You also need to consider how you want to accept data. These days a lot of people are using JSON or XML, and I personally prefer JSON because it works well with JavaScript, and PHP has easy functionality for encoding and decoding it. If you wanted your API to be really robust, you could accept both by sniffing out the content-type of the request (i.e. application/json or application/xml), but it¶s perfectly acceptable to restrict things to one content type. Heck, you could even use simple key/value pairs if you wanted. The other piece of a request is what it¶s actually meant to do, such as load, save, etc. Normally, you¶d have to come up with some sort of architecture that defines what action the requester (consumer) desires, but REST simplifies that. By using HTTP request methods, or verbs, we don¶t need to define anything. We can just use the GET, POST, PUT, and DELETE methods, and that covers every request we¶d need. You can equate the verbs to your standard crud-style stuff: GET = load/retrieve, POST = create, PUT = update, DELETE = well, delete. It¶s important to note that these verbs don¶t directly translate to CRUD, but it is a good way to think about them. So, going back to the above URL examples, let¶s take a look at what some possible requests could mean:

y y y y y

GET request to /api/users ± List all users GET request to /api/users/1 ± List info for user with ID of 1 POST request to /api/users ± Create a new user PUT request to /api/users/1 ± Update user with ID of 1 DELETE request to /api/users/1 ± Delete user with ID of 1

As you hopefully see, REST has already taken care of a lot of the major headaches of creating your own API through some simple, well-understood standards and protocols, but there¶s one other piece to a good API« Responses So, REST handles requests very easily, but it also makes generating responses easy. Similar to requests, there are two main components of a RESTful response: the response body, and a status code. The response body is pretty easy to deal with. Like requests, most responses in REST are usually either JSON or XML (perhaps just plain text in the case of POSTs, but we¶ll cover that later). And, like requests, the consumer can specify the response type they¶d like through another part of the HTTP request spec, ³Accept´. If a consumer wishes to receive an XML response, they¶d just send an Accept header as a part of their request saying as much (´Accept: application/xml´). Admittedly, this method isn¶t as widely adopted (tho it should be), so you have can also use the concept of an extension in the URL. For example, /api/users.xml means the consumer wants XML as a response, similarly /api/users.json means JSON (same for things like /api/users/1.json/xml). Either way you choose (I say do both), you should pick a default response type as a lot of the time people wont¶ even tell you what they want. Again, I¶d say go with JSON. So, no Accept header or extension (i.e. /api/users) should not fail, it should just fail-over to the default response-type. But what about errors and other important status messages associated with requests? Easy, use HTTP status codes! This is far and above one of my favorite things about creating RESTful APIs. By using HTTP status codes, you don¶t need to come up with a error / success scheme for your API, it¶s already done for you. For example, if a consumer POSTS to /api/users and you want to report back a successful creation, simply send a 201 status code (201 = Created). If it failed, send a 500 if it failed on your end (500 = Internal Server Error), or perhaps a 400 if they screwed up (400 = Bad request). Maybe they¶re trying to POST against an API endpoint that doesn¶t accept posts« send a 501 (Not implemented). Perhaps your MySQL server is down, so your API is temporarily borked« send a 503 (Service unavailable). Hopefully, you get the idea. If you¶d like to read up a bit on status codes, check them out on wikipedia: List of HTTP Status Codes. I¶m hoping you see all the advantages you get by leveraging the concepts of REST for your APIs. It really is super-cool, and its a shame its not more widely talked about in the PHP community (at least as far as I can tell). I think this is likely due to the lack of good documentation on how to deal with requests that aren¶t GET or POST, namely PUT and DELETE. Admittedly, it is a bit goofy dealing with these, but it certainly isn¶t hard. I¶m also sure some of the popular frameworks out there probably have some sort of REST implementation, but I¶m not a huge framework fan (for a lot of reasons that I won¶t get into), and it¶s also good to know these things even if somebody¶s already created the solution for you. If you¶re still not convinced that this is a useful API paradigm, take a look at what REST has done for Ruby on Rails. One of its major claims to fame is how easy it is to create APIs (through some sort of RoR voodoo, I¶m sure), and rightly so. Granted I know very little about RoR, but the fanboys around the office have preached this point to me many times. But, I digress« let¶s write some code!

So. $body = ''. this will suffice // for an example $codes = Array( 100 => 'Continue'. $content_type = 'text/html') { } public static function getStatusCodeMessage($status) { // these could be stored in a .ini file and loaded // via parse_ini_file(). 400 => 'Bad Request'. 416 => 'Requested Range Not Satisfiable'. and leave creating the final solution up to you. 408 => 'Request Timeout'. 500 => 'Internal Server Error'.Getting Started with REST and PHP One last disclaimer: the code we¶re about to go over is in no way intended to be used as an example of a robust solution. 402 => 'Payment Required'. 306 => '(Unused)'. 300 => 'Multiple Choices'. 204 => 'No Content'. 203 => 'Non-Authoritative Information'. 404 => 'Not Found'. 405 => 'Method Not Allowed'. . and apply it to your own needs. So. 417 => 'Expectation Failed'. } } class RestRequest { private $request_vars. 205 => 'Reset Content'. 503 => 'Service Unavailable'. 301 => 'Moved Permanently'.. 101 => 'Switching Protocols'. 307 => 'Temporary Redirect'. 502 => 'Bad Gateway'. 303 => 'See Other'. 415 => 'Unsupported Media Type'. 504 => 'Gateway Timeout'. 407 => 'Proxy Authentication Required'. 201 => 'Created'. You could also then take this. 505 => 'HTTP Version Not Supported' ). let¶s stub some stuff out: class RestUtils{ public static function processRequest() { } public static function sendResponse($status = 200. 409 => 'Conflict'. 202 => 'Accepted'. 501 => 'Not Implemented'. 414 => 'Request-URI Too Long'. 200 => 'OK'. 412 => 'Precondition Failed'. My main goal here is to show how to deal with the individual components of REST in PHP.. 401 => 'Unauthorized'. 406 => 'Not Acceptable'. 304 => 'Not Modified'. We¶ll also create a small class for storing our data. 411 => 'Length Required'. 206 => 'Partial Content'. 305 => 'Use Proxy'. 302 => 'Found'. let¶s dig in! I think the best way to do something practical is to create a class that will provide all the utility functions we need to create a REST API. 413 => 'Request Entity Too Large'. 403 => 'Forbidden'. extend it. return (isset($codes[$status])) ? $codes[$status] : ''. however. 410 => 'Gone'.

= ''. Let¶s also assume that the actual data will be JSON. = 'get'. There are a few ways we could go about doing this.. but let¶s just assume that we¶ll always get a key/value pair in our request: µdata¶ => actual data. we really only have two functions to write« which is the beauty of this whole thing! Right. our process request function will end up looking something like this: public static function processRequest() { // get our verb $request_method = strtolower($_SERVER['REQUEST_METHOD']). private $http_accept. With that out of the way. let¶s move on« Processing the Request Processing the request is pretty straight-forward. } public function getData() { return $this->data. } public function getRequestVars() { return $this->request_vars. We¶ll go over those in a moment. } public function getHttpAccept() { return $this->http_accept. you¶ll see that we¶re already interpreting the HTTP_ACCEPT header. and a class with some static functions we can use to deal with requests and responses. OK. switch ($request_method) { // gets are easy. so what we¶ve got is a simple class for storing some information about our request (RestRequest). = (strpos($_SERVER['HTTP_ACCEPT']. } public function setMethod($method) { $this->method = $method. } public function setRequestVars($request_vars) { $this->request_vars = $request_vars.. but this is where we can run into a few catches (namely with PUT and DELETE« mostly PUT).private $data. you could look at the content-type of the request and deal with either JSON or XML. but let¶s keep it simple for now. $return_obj = new RestRequest(). we need only deal with the incoming data. If you¶ll look at the constructor. 'json')) ? 'json' : 'xml'. } public function getMethod() { return $this->method. } } = array(). As you can see. case 'get': . and defaulting to JSON if none is provided. public function __construct() { $this->request_vars $this->data $this->http_accept $this->method } public function setData($data) { $this->data = $data. So. // we'll store our data here $data = array(). but let¶s examine the RestRequest class a bit. private $method. As stated in my previous explanation of REST.

$body = ''. } Please don¶t do this in a real app. We already know that all we really need to do is send the correct status code.. break. we could have some code like this: $data = RestUtils::processRequest(). but simply sending the status code in the headers isn¶t enough. we read a string from PHP's special input location. for example). here¶s what the code should look like: public static function sendResponse($status = 200. } return $return_obj. $content_type = 'text/html') { $status_header = 'HTTP/1. // set the raw data. so there¶s no status page. pretty straight-forward. etc. but this should help you get an idea of how to use this stuff. break. The appropriate status code to send is a 404 in this case. RestUtils::getStatusCodeMessage($status). this should be done cleaner // and so on. Assuming you¶ve routed your request to the correct controller for users. Second. case 'put': // basically. a few things to note« First. etc. However. But I digress. switch($data->getMethod) { case 'get': // retrieve a list of users break. This is useful as you may have other stuff as a part of your request (say an API key or something) that isn¶t truly the data itself (like a new user¶s name.. if(isset($data['data'])) { // translate the JSON to an Object for use however you want $return_obj->setData(json_decode($data['data'])). you typically don¶t accept data for DELETE requests. $data = $put_vars. let¶s move on to sending a response. $user->setFirstName($data->getData()->first_name). but there is an important catch to responses that have no body. Sending the Response Now that we can interpret the request. email. So. $user->save(). and the parsed JSON data.. // and then parse it out into an array via parse_str.1 ' . $put_vars). // here's the tricky bit.. this is just a quick-and-dirty example. $status . api/user/123). and maybe some body (if this were a GET request. Say somebody made a request against our sample user API for a user that doesn¶t exist (i. you would get a blank screen. how would we use this? Let¶s go back to the user example. parse_str(file_get_contents('php://input').e. Keeping all that in mind. case 'post': $user = new User().. break. // etc.. break. This is because Apache (or whatever your web server runs on) isn¶t sending the status code. let¶s move on to sending the response. If you viewed that page in your web browser. // set the status header($status_header). } // store the method $return_obj->setMethod($request_method). // just for example. We¶ll need to take this into account when we build out our function.).. so we can access it if needed (there may be // other pieces to your requests) $return_obj->setRequestVars($data). . ' ' .$data = $_GET.. You¶d want to wrap this up in a nice control structure with everything abstracted properly. you¶ll notice that we store both the request variables. so we don¶t have a case for them in the switch. etc. per the PHP docs: // Parses str as if it were the query string passed via a URL and sets // variables in the current scope. // so are posts case 'post': $data = $_POST. } Like I said.

$_SERVER['REQUEST_URI'] . } // we need to create the body if none is passed else { // create some body messages $message = ''. '</title> </head> <body> <h1>' . Well. // pages with body are easy if($body != '') { // send the body echo $body. break. // assume this returns an array if($data->getHttpAccept == 'json') { .dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html. break. Let¶s talk a bit more about why we need to have a standard body response or a custom one. there¶s also POSTs to deal with. For GET requests. we need to send XML / JSON content instead of a status page (provided the request was valid). // this is purely optional. ' was not found. '</h1> <p>' . $message . charset=iso-8859-1"> <title>' . break. ' ' . exit.w3. So. '</address> </body> </html>'.org/TR/html4/strict.// set the content type header('Content-type: ' . $_SERVER['SERVER_NAME'] . What I¶ll usually do in this case is simply send the new ID as the body (with a 201 status code). $content_type).'. ' Server at ' . Inside of your apps. let¶s extend our sample implementation a bit: switch($data->getMethod) { // this is a request for all users. exit. they¶ll probably want that new ID as well. RestUtils::getStatusCodeMessage($status) . break. case 500: $message = 'The server encountered an error processing your request. $status . $signature .01//EN" "http://www.'. not one in particular case 'get': $user_list = getUserList(). when you create a new entity.'. RestUtils::getStatusCodeMessage($status) . but makes the pages a little nicer to read // for your users. '</p> <hr /> <address>' . this is pretty obvious. } // servers don't always have a signature turned on (this is an apache directive "ServerSignature On") $signature = ($_SERVER['SERVER_SIGNATURE'] == '') ? $_SERVER['SERVER_SOFTWARE'] . However. case 404: $message = 'The requested URL ' . $_SERVER['SERVER_PORT'] : $_SERVER['SERVER_SIGNATURE']. // this should be templatized in a real-world solution $body = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4. } } That¶s It! We technically have everything we need now to process requests and send responses. echo $body. if a user posts to your API. but you could also wrap that in XML or JSON if you¶d like. Since you won't likely send a lot of different status codes. you probably fetch the new entity¶s ID via something like mysql_insert_id(). ' Port ' . // this also shouldn't be too ponderous to maintain switch($status) { case 401: $message = 'You must be authorized to view this page.'. case 501: $message = 'The requested method is not implemented.

analayze the PHP_AUTH_DIGEST var if(!($data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) || $auth_username != $data['username']) { // show the error due to bad auth die(RestUtils::sendResponse(401)). Say you only wanted properly authenticated users to access your API.. would first trigger a lookup for a ³user´ rest controller. header('WWW-Authenticate: Digest realm="' . using the above stuff. For example. } // now. so there¶s some constants and variables referenced that aren¶t defined in this snippet): // figure out if we need to challenge the user if(empty($_SERVER['PHP_AUTH_DIGEST'])) { header('HTTP/1. 'application/xml').nonce="' . Finally. That method would then use the utils to process the request. If none is found. } Again. The API controller would look at the request and determine which method to invoke on that controller. $serializer = new XML_Serializer($options). the url ³api/user/1 . In a real-world MVC application. RestUtils::sendResponse(200.1 401 Unauthorized'). $user->setFirstName($data->getData()->first_name).RestUtils::sendResponse(200. it could then look for a model called ³user´ in your app. '". and delete(). and then look for a ³user´ model. and abstract out your API controller and data models a bit more. that¶s about it. If one¶s found. I¶m pretty confident that I¶ve beaten the point that this should be quite easy into the ground.opaque="' . at least) how little effort it takes to implement RESTful stuff. json_encode($user_list). The API controller could first check for a ³users´ rest controller. recognize that users is pluaralized. // just send the new ID as the body RestUtils::sendResponse(201. and if none is found. $user->save(). what you would probably want to do is set up a controller for your API that loads individual API controllers. you could write up a bit of automated voodoo to automatically process all the requests against those models. true)). XML_SERIALIZER_OPTION_RETURN_RESULT => true ). then use the utils to send a response. AUTH_REALM . } break. You could also take it a step further than that. '"'). // just for example. } else if ($data->getHttpAccept == 'xml') { // using the XML_SERIALIZER Pear Package $options = array ( 'indent' => ' '. you could throw some code like this into your process request functionality (borrowed from an existing app of mine. $user->getId()). Going even further. you could add some logic into your API controller to first look for an explicitly defined controller. do what it needs to do data-wise. md5(AUTH_REALM) . and if none was found. // new user create case 'post': $user = new User(). post(). 'addDecl' => false. this is just an example.qop="auth". you could add digest authentication to your API quite easily as well. but it does show off (I think. load a list the list of users and send that off. Rather than explicitly creating a controller for every data model in your app. Wrapping Up So. For example. Say your url was ³api/users´. put(). this should be done cleaner // and so on. try to look for an existing model. 'rootName' => $fc->getAction(). If one is found. break. } . depluralize it. '". so I¶d like to close with how you can take this stuff further and perhaps properly implement it. you could then make a generic ³list-all´ method that works similar to the previous paragraph¶s example.. // show the error if they hit cancel die(RestControllerLib::error(401. uniqid() . we¶d possibly create a UserRestController which had four methods: get(). well. 'application/json'). $serializer->serialize($user_list).

PUT. & DELETE in PHP APIs have become a very commonplace part of many popular web sites and services« especially REST APIs. well. or DELETE« there are some others out there. $data['nc'] . I want to make sure you¶ve got a basic understanding of how REST APIs work. everything's good. ':' . Credentials ± Username and password« we¶ll cover HTTP Digest auth credentials. on to the answer to the most important question: ³How the heck do you actually make the request?!?!?´ REST & PHP ± Curl to the Rescue You may have already guessed we¶d be using curl (or suspected it perhaps)« and if you didn¶t. The Params ± The parameters we¶ll be supplying to the API. just read the first part of the article). but I¶ve also received countless requests to go over how to actually make RESTful requests. . if you¶ve ever worked with Curl in PHP. $A2). ':' . protected $verb. ':' .. I¶m not just saying that to cheerlead the concept either. $valid_response = md5($A1 . Before we dive in. but these are the 4 most common ones. let's now check the response a bit more. often referred to as the request body. $auth_pass). the problem is that they¶re very poorly documented.. without any further delay. be sure to drop those in the comments as well« if I like it enough. do it« this article is written with the assumption you¶re familiar with the concepts of REST. That¶s exactly what we¶ll take a look at in this article. POST. let¶s take a look at what we¶re going to cover« The Basics of a RESTful Request Every REST request consists of essentially the same basic parts: y y y y The URL ± This is the URL we¶ll be making a request against (often referred to as a resource). Nothing really unexpected here. you can add a fully functional REST API to your apps very quickly. but let¶s get started with a little code. } Pretty cool stuff. however. I¶d even let you guest author your own article on the subject! Making RESTful Requests in PHP How to GET. ':' .// so far. ':' . $data['uri']). if($data['response'] != $valid_response) { die(RestUtils::sendResponse(401)). We¶re going to be building a class that we can use to make requests and deal with their responses. and then spent another half day adding all sorts of cool magic to it. $data['cnonce'] . ':' . $data['qop'] . The Verb ± GET. and then start filling things in: class RestRequest { protected $url. of course. $A1 = md5($data['username'] . so if you¶re a bit shaky or unfamiliar with the concept. I¶ve already discussed how you can roll your own REST API for your PHP apps. but the PUT and DELETE requests are a little less obvious. Anyway. so why don¶t we build up a bit of a base class. huh? With a little bit of code and some clever logic. AUTH_REALM . head on over to my previous article on REST and read up (you don¶t need to go over the implementation stuff if you don¶t want. Seriously. if you¶ve got any cool ideas you¶d like to share. $A2 = md5($_SERVER['REQUEST_METHOD'] . If you (the reader) are interested in seeing my final implementation. Luckily. // last check. drop me a note in the comments and I¶d be happy to share it with you! Also. that¶s what we¶ll be using Anyway. you¶re probably aware that making GET and POST requests are pretty straight-forward. ':' . protected $username. I implemented this stuff into one of my personal frameworks in about half a day. We¶ll go over them soon. The Response Status Code ± The HTTP status code the API responded with. protected $requestLength. ':' . And. Other Response Info ± We¶ll also have some other interesting info in the response. they¶re pretty easy once you know how to do them. So. $data['nonce'] . PUT. we¶ll have a few pieces for our response as well: y y y The Response Body ± The actual response body the API gave us. everything¶s probably looking like what you¶d expect: a request and a response.. protected $requestBody. POST.

if ($this->requestBody !== null) { $this->buildPostBody(). . $this->username = null. $this->requestBody = $requestBody. $this->password = null. protected $responseInfo. $requestBody = null) { $this->url = $url. = null. $verb = 'GET'. protected $responseBody. protected $acceptType. $this->responseInfo = null. $this->verb = $verb. public function __construct ($url = null. = 0. $this->acceptType = 'application/json'. = null.protected $password. } } public function flush () { $this->requestBody $this->requestLength $this->verb $this->responseBody $this->responseInfo } public function execute () { } public function buildPostBody ($data = null) { } protected function executeGet ($ch) { } protected function executePost ($ch) { } protected function executePut ($ch) { } protected function executeDelete ($ch) { } protected function doExecute (&$curlHandle) { } protected function setCurlOpts (&$curlHandle) { } protected function setAuth (&$curlHandle) { } } = null. $this->responseBody = null. $this->requestLength = 0. = 'GET'.

The final class will have them. CURLOPT_TIMEOUT. $this->username . buildPostBody This function will take an array and prepare it for being posted (or put as well): public function buildPostBody ($data = null) { $data = ($data !== null) ? $data : $this->requestBody. CURLAUTH_DIGEST). } } . } $data = http_build_query($data. } setAuth If we¶ve got a username and password set on the class. but check out the PHP SPL for more info. let¶s take a look at these ³init´ functions. CURLOPT_HTTPHEADER. CURLOPT_URL. etc. CURLOPT_USERPWD. it is (if you¶re using PHP 5). $this->requestBody = $data. We¶ve also got a flush function. and then we¶ll dig into making the requests. what we¶ve got here is essentially all of the functions we¶ll need to make this whole thing work right. } Notice how we will throw exceptions if we receive anything other than an array? This is important. ''. curl_setopt($curlHandle. curl_setopt($curlHandle. you may be thinking that I¶m using an exception type that isn¶t defined anywhere« well.. Making Requests ± The Prep Work Let¶s go ahead and fill in some of the functions that do prep work for us. $this->url). Array expected'). as we don¶t want our data to be malformed (or an error to be thrown by http_build_query). curl_setopt($curlHandle. you¶ll see things are pretty straight-forward as well. 10). but we¶ll be robust) $responseBody ± The body of our response $responseInfo ± All the other goodness from our response (status code. You may have also noticed we¶re missing getters / setters« this is simply to keep the code a little shorter. '&'). Anyway. CURLOPT_RETURNTRANSFER. but let¶s take a look at the class members: y y y y y y y y y $url ± The URL we¶ll be requesting against $verb ± The type of request we¶ll be making (what verb to use) $requestBody ± The request body we¶ll send with PUT and POST requests $requestLength ± An internally used variable for PUT requests (more on this later) $username ± The username to use for this request $password ± I¶ll let you guess $acceptType ± What kind of content we¶ll accept as a response (not all APIs use this to determine the response format. We¶ll cover each of the function individually. right? And if you take a look at the constructor. we¶ll set up the auth options on the curl request with this function: protected function setAuth (&$curlHandle) { if ($this->username !== null && $this->password !== null) { curl_setopt($curlHandle. I won¶t go off on a tangent.So. This allows us to use the same object to make multiple requests by only clearing out certain member variables (notice username / password aren¶t touched). true). $this->acceptType)). Also.) Not too rough. All you need to know is that the InvalidArgumentException is already defined for you setCurlOpts This function will take care of all the curl options common to all our requests: protected function setCurlOpts (&$curlHandle) { curl_setopt($curlHandle. ':' . curl_setopt($curlHandle. if (!is_array($data)) { throw new InvalidArgumentException('Invalid data input for postBody. CURLOPT_HTTPAUTH. array ('Accept: ' . $this->password).

we throw an exception. First. We¶ll take a look at those in a bit. but it¶s actually pretty simple stuff. and then run the setAuth function. We¶ll go ahead and create our doExecute and execute functions now: public function execute () { $ch = curl_init(). the functions will directly manipulate the variable without any need to return it. throw $e. default: throw new InvalidArgumentException('Current verb (' .I mentioned earlier that we¶re only going to cover HTTP Digest authentication in this class. break. but you could adapt this function to suit your needs however you wanted depending on what the API(s) you¶re consuming require. } Looks like a lot of code. close our curl handle. you¶ll see that we set up a curl handle variable ($ch). } } protected function doExecute (&$curlHandle) { $this->setCurlOpts($curlHandle). Since your params will be a part of the URL. $this->setAuth($ch). So our code is nice and easy: . $this->verb . If we don¶t find a matching case. looking at execute. try { switch (strtoupper($this->verb)) { case 'GET': $this->executeGet($ch). $this->responseBody = curl_exec($curlHandle). break. throw $e. Now. Since we pass $ch around by reference.'). } catch (Exception $e) { curl_close($ch). but let¶s get to the individual request functions. there isn¶t much to do outside of actually making the request. Moving on to the doExecute function. you¶ll see all we really do here is set all the common curl options with setCurlOpts. case 'DELETE': $this->executeDelete($ch). } } catch (InvalidArgumentException $e) { curl_close($ch). but for the sake of this example. break. Anyway. curl_close($curlHandle). I could make the $ch a member variable. then throw the exception again (presumably for some other error handler or try / catch block elsewhere in the code). $this->responseInfo = curl_getinfo($curlHandle). execute the request. GET Requests These requests are about as easy as they get. the next thing we do is normalize the verb. I wanted to show how everything gets passed around a little more explicitly. case 'PUT': $this->executePut($ch). and run through our switch statement to determine what function will get executed. case 'POST': $this->executePost($ch). break. Making the REST Requests We¶re about ready to cover how to make the individual verb requests. but we need to do just a little bit more work filling in the common functionality. and get the response body and info. We also wrap the whole thing in a try / catch block so we can properly catch exceptions. ') is an invalid REST verb.

CURLOPT_POSTFIELDS. CURLOPT_PUT. fwrite($fh. this boils down to the way PUT requests are technically supposed to work. $this->doExecute($ch). as long as you trust that it works DELETE Requests in PHP Deletes. are easy. fclose($fh). 1). } µNuff said POST Requests These too are pretty easy to accomplish. you¶re not really supposed to send any body for a delete request. } $this->requestLength = strlen($this->requestBody). As such. Remember. curl_setopt($ch. we need to stream the data to the web server. right? Well.e. $this->requestLength). Here¶s the code: protected function executeDelete ($ch) { . write our request body to it. as you¶re merely sending a command to a resource (i. $this->requestBody). So we open an internal php memory resource. there are a few articles out there covering how to do them. That¶s all there is to it! Moving on« PUT Requests in PHP Ah. and then pass the handle for that memory resource to curl. which is for file uploads. } All we really do here is make sure the request body is a string. Nonetheless. but they¶re not super-easy to come across. PUT requests« These are the big mystery. so that our full body will be received on the API side of things. and if it isn¶t that means we need to prepare it« so we do. however. Admittedly. curl_setopt($ch. manage to find a few and here¶s what I came up with: protected function executePut ($ch) { if (!is_string($this->requestBody)) { $this->buildPostBody(). and POSTing with Curl is well-documented.protected function executeGet ($ch) { $this->doExecute($ch). $this->requestBody). $fh = fopen('php://memory'. Then we tell Curl to make a POST request with the provided POST body. CURLOPT_POST. curl_setopt($ch. $fh). /api/user/1). curl_setopt($ch. 'rw'). We also calculate the size of it (in bytes). RESTful APIs don¶t quite use a PUT the way they were originally intended. CURLOPT_INFILESIZE. rewind($fh). } A little funky. } curl_setopt($ch. true). when done as they¶re intended (no post body). $this->doExecute($ch). I suppose that doesn¶t necessarily need to make any sense to you. CURLOPT_INFILE. here¶s what our function looks like: protected function executePost ($ch) { if (!is_string($this->requestBody)) { $this->buildPostBody(). I did.

Which gives us output similar to: RestRequest Object ( [url:protected] => http://example. but swap out the CURLOPT_PUT line for the existing CURLOPT_CUSTOMREQUEST line that we¶ve got in this function.curl_setopt($ch.015693 [namelookup_time] => 0. you¶ve got a good idea of how REST APIs work from the server-side. there¶s a lot left for you to do. making a request against an imaginary API looks something like: $request = new RestRequest('http://example. you can do it by adding the same stuff you use for a PUT request. let¶s take a look at a response. we¶re done! Wrapping Up Of course. but it works like a champ if you need it. believe it or not. } Now. And.com/api/user/1'. and all sorts of other good stuff.015655 [redirect_time] => 0 ) ) Take a look at the responseInfo« we¶ve got our status code. and you¶re ready to jump on the REST bandwagon. You¶ll probably want to do some processing of the response body and info (such as extracting the status code). I don¶t advocate this.com/api/user/1 [verb:protected] => GET [requestBody:protected] => [requestLength:protected] => 0 [username:protected] => [password:protected] => [acceptType:protected] => application/json [responseBody:protected] => [RESPONSE BODY HERE] [responseInfo:protected] => Array ( [url] => http://example. 'GET'). Hopefully. complete with getters / setters at the link below: . $request->execute().0004 [connect_time] => 0. if you need to send a body for some reason. So. or make a few things more robust. you can grab the final copy of the class. echo '<pre>' .000571 [pretransfer_time] => 0. true) . as with all my samples. If you¶re not yet convinced« go play with some SOAP APIs. The Response Now that we¶ve got a fully functioning REST requester. that ought to change your mind Finally. but this is all you should need to get started. CURLOPT_CUSTOMREQUEST.com/api/user/1 [content_type] => application/json [http_code] => 200 [header_size] => 232 [request_size] => 192 [filetime] => -1 [ssl_verify_result] => 0 [redirect_count] => 0 [total_time] => 0. 'DELETE'). after this article and the previous one. $this->doExecute($ch). '</pre>'. print_r($request. to the client-side.000619 [size_upload] => 0 [size_download] => 4276 [speed_download] => 272478 [speed_upload] => 0 [download_content_length] => 4276 [upload_content_length] => 0 [starttransfer_time] => 0.

Sign up to vote on this title
UsefulNot useful