1. 程式人生 > >使用PHP建立一個REST API(Create a REST API with PHP)

使用PHP建立一個REST API(Create a REST API with PHP)

譯者前言:
首先這是一篇國外的英文文章,非常系統、詳盡的介紹瞭如何使用PHP建立REST API,國內這方面的資料非常非常的有限,而且基本沒有可操作性。這篇文章寫的非常好,只要對PHP稍有了解的程式設計師,看完本文基本可以自己動手寫REST API,花了幾個小時翻譯過來和大家共享,希望可以幫助大家。轉載請註明出處。

本文地址:http://hmw.iteye.com/blog/1190827
原文地址: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).
最近網際網路上比較熱門的一個名詞是APIs(介面),特別是leverage REST。不過考慮到REST APIs在任何語言下都是非常的簡單,也就沒什麼好驚奇的了。同時,它也是非常的容易建立,你基本只需要使用已經存在多年的HTTP規範就可以。我認為Rails語言的為數不多的優點之一就是良好的REST支援,不僅是提供APIs,同時也有很多的客戶端支援(我的一些Rails粉絲同事都向我解釋了這一點)。


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!
認真的講,假如你從來沒有使用過REST,卻曾經使用過SOAP API,或者只是簡單的開啟一個令人頭大的WSDL文件。小夥子,我確實要帶給你一個好訊息!

So, What on Earth is REST? Why Should You Care?
那麼,究竟什麼是REST?為什麼你應該關心?


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.
在我們開始寫程式碼之前,我想要確認每個人都可以很好的理解什麼是REST以及它是如何特別適合APIs的。首先,從技術上來講,REST並不是僅僅特定於APIs應用,它更多的是一個通用的概念。然而,很明顯,我們這篇文章所討論的REST就是在介面應用的環境下。因此,讓我們看看一個API的基本要求已經REST如何處理他們。


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.
所有的APIs都需要接收請求。對於一個RESTful API,你需要一個定義好的URL規則,我們假定你想要提供一個介面給你網站上的使用者(我知道,我總是使用"使用者"這個概念來舉例)。你的URL結構可能類似於這樣:"api/users"或者"api/users/[id]",這取決於請求介面的操作型別。同時,你也需要考慮你想要怎麼樣接收資料。近來一段時間,很多人正在使用JSON或者XML,從我個人來講,我更加喜歡JSON,因為它可以很好的和javascript進行互動操作,同時PHP也可以很簡單的通過json_encode和json_decode兩個函式來對它進行編碼和解碼。如果你希望自己的介面真正強健,你應該通過識別請求的內容型別(比如application/json或者application/xml)同時允許接收兩種格式。但是,限制只接收一種型別的資料也是可以很好的被接受。真見鬼,假如你願意,你甚至可以使用簡單的鍵/值對。

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:
    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
一個請求的其他部分是它真正要做的事情,比如載入、儲存等。通常來說,你應該提供幾種結構來定義請求者(消費者)所希望的操作,但是REST簡化了這些。通過使用HTTP請求方法或者動作,我們不需要去額外定義任何東西。我們可以僅僅使用GET,POST,PUT和DELETE方法,這些方法涵蓋了我們所需要的每一個請求。你可以把它和標準的增刪改查模式對應起來:GET=載入/檢索(查,select),POST=建立(增,Create),PUT=更新(改,update),DELETE=刪除(DELETE)。我們要注意到,這些動詞並沒有直接翻譯成CRUD(增刪改查),但是這個理解它們的一個很好的方法。因此,回到剛才所舉的URL的例子,讓我們看一下一些可能的請求的含義:
    GET request to /api/users – 列舉出所有的使用者
    GET request to /api/users/1 – 列出ID為1的使用者資訊
    POST request to /api/users – 插入一個新的使用者
    PUT request to /api/users/1 – 更新ID為1的使用者資訊
    DELETE request to /api/users/1 – 刪除ID為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…
正如你所希望看到的,REST已經解決了很多令人頭疼的建立介面的問題,通過一些簡單的,容易理解的標準和協議。但是一個好的介面還要另外一個方面...

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.
所以,REST可以很簡單的處理請求,同時它也可以簡單的處理響應。和請求類似,一個RESTful的響應主要包括兩個主要部分:響應體和狀態碼。響應體非常容易去處理。就像請求,大部分的REST響應通常是JSON或者XML格式(也許對POST操作來說僅僅是純文字,我們稍後會討論),和請求類似,消費者可以通過設定HTTP規範的"Accept"選項來規定自己做希望接收到的響應資料型別。如果一個消費者希望接收到XML響應,他們僅僅需要傳送一個包含類似於(”Accept: application/xml”)這樣的頭資訊請求。不可否認,這種方式並沒有被廣泛的採用(即使應該這樣),因此你也可以使用URL字尾的形式,例如:/api/users.xml意味著消費者希望得到XML響應,同樣,/api/users.json意味著JSON格式的響應(/api/users/1.json/xml也是一樣)。不管你採用哪一種方法,你都需要設定一個預設的響應型別,因為很多時候人們並不會告訴你他們希望什麼格式。再次地,我會選擇JSON來討論。所以,沒有Accept頭資訊或者擴充套件(例如:/api/users)不應該失敗,而是採用預設的響應型別。

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.
但是和請求有關的錯誤和其他重要的狀態資訊怎麼辦呢?簡單,使用HTTP的狀態碼!這是我建立RESTful介面最喜歡的事情之一。通過使用HTTP狀態碼,你不需要為你的介面想出error/success規則,它已經為你做好。比如:假如一個消費者提交資料(POST)到/api/users,你需要返回一個成功建立的訊息,此時你可以簡單的傳送一個201狀態碼(201=Created)。如果失敗了,伺服器端失敗就傳送一個500(500=內部伺服器錯誤),如果請求中斷就傳送一個400(400=錯誤請求)。也許他們會嘗試向一個不接受POST請求的介面提交資料,你就可以傳送一個501錯誤(未執行)。又或者你的MySQL伺服器掛了,介面也會臨時性的中斷,傳送一個503錯誤(服務不可用)。幸運的是,你已經知道了這些,假如你想要了解更多關於狀態碼的資料,可以在維基百科上查詢: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.
我希望你能看到REST介面的這些優點。它真的超級酷。在PHP社群社群裡沒有被廣泛的討論真是非常的遺憾(至少我知道的是這樣)。我覺得這主要是由於沒有很好的文件介紹如何處理除了GET和POST之後的請求,即PUT和DELETE。不可否認,處理這些是有點傻,但是卻不難。我相信一些流行的框架也許已經有了某種REST的實現方式,但是我不是一個框架粉絲(原因有很多),並且即使有人已經為你提供瞭解決方案,你知道這些也是非常有好處的。

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!
如果你還是不太自信這是一個非常有用的API正規化,看一下REST已經為Ruby on Rails做了什麼。其中最令人稱道的就是建立介面的便利性(通過某種RoR voodoo,我確信),而且確實如此。雖然我對RoR瞭解很少,但是辦公室的Ruby粉絲們向我說教過很多次。不好意思跑題了,讓我們開始寫程式碼。

Getting Started with REST and PHP 開始使用PHP寫REST

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. My main goal here is to show how to deal with the individual components of REST in PHP, and leave creating the final solution up to you.
最後一項免責宣告:我們接下來提供的程式碼並不能被用來作為一個穩健的解決方案。我的主要目的是向大家展示如果使用PHP處理REST的每個單獨部分,而把最後的解決方案留給你們自己去建立。

So, 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. We’ll also create a small class for storing our data. You could also then take this, extend it, and apply it to your own needs. So, let’s stub some stuff out:
那麼,讓我們開始深入程式碼。我認為做一個實際事情做好的方法就是新建一個class,這個class將提供建立REST API所需要的所有功能性方法。現在我們新建一個小的class來儲存我們的資料。你可以把它拿去擴充套件一下然後應用到自己的需求中。我們現在開始寫點東西:

Php程式碼  收藏程式碼
  1. class RestUtils  
  2. {  
  3.     publicstaticfunction processRequest(){  
  4.     }  
  5.     publicstaticfunction sendResponse($status = 200, $body = ''$content_type = 'text/html'){  
  6.     }  
  7.     publicstaticfunction getStatusCodeMessage($status){  
  8.         // these could be stored in a .ini file and loaded
  9.         // via parse_ini_file()... however, this will suffice
  10.         // for an example
  11.         // 這些應該被儲存在一個.ini的檔案中,然後通過parse_ini_file()函式來解析出來,然而這樣也足夠了,比如:
  12.         $codes = Array(  
  13.             100 => 'Continue',  
  14.             101 => 'Switching Protocols',  
  15.             200 => 'OK',  
  16.             201 => 'Created',  
  17.             202 => 'Accepted',  
  18.             203 => 'Non-Authoritative Information',  
  19.             204 => 'No Content',  
  20.             205 => 'Reset Content',  
  21.             206 => 'Partial Content',  
  22.             300 => 'Multiple Choices',  
  23.             301 => 'Moved Permanently',  
  24.             302 => 'Found',  
  25.             303 => 'See Other',  
  26.             304 => 'Not Modified',  
  27.             305 => 'Use Proxy',  
  28.             306 => '(Unused)',  
  29.             307 => 'Temporary Redirect',  
  30.             400 => 'Bad Request',  
  31.             401 => 'Unauthorized',  
  32.             402 => 'Payment Required',  
  33.             403 => 'Forbidden',  
  34.             404 => 'Not Found',  
  35.             405 => 'Method Not Allowed',  
  36.             406 => 'Not Acceptable',  
  37.             407 => 'Proxy Authentication Required',  
  38.             408 => 'Request Timeout',  
  39.             409 => 'Conflict',  
  40.             410 => 'Gone',  
  41.             411 => 'Length Required',  
  42.             412 => 'Precondition Failed',  
  43.             413 => 'Request Entity Too Large',  
  44.             414 => 'Request-URI Too Long',  
  45.             415 => 'Unsupported Media Type',  
  46.             416 => 'Requested Range Not Satisfiable',  
  47.             417 => 'Expectation Failed',  
  48.             500 => 'Internal Server Error',  
  49.             501 => 'Not Implemented',  
  50.             502 => 'Bad Gateway',  
  51.             503 => 'Service Unavailable',  
  52.             504 => 'Gateway Timeout',  
  53.             505 => 'HTTP Version Not Supported'
  54.         );  
  55.         return (isset($codes[$status])) ? $codes[$status] : '';  
  56.     }  
  57. }  
  58. class RestRequest  
  59. {  
  60.     private$request_vars;  
  61.     private$data;  
  62.     private$http_accept;  
  63.     private$method;  
  64.     publicfunction __construct(){  
  65.         $this->request_vars      = array();  
  66.         $this->data              = '';  
  67.         $this->http_accept       = (strpos($_SERVER['HTTP_ACCEPT'], 'json')) ? 'json' : 'xml';  
  68.         $this->method            = 'get';  
  69.     }  
  70.     publicfunction setData($data){  
  71.         $this->data = $data;  
  72.     }  
  73.     publicfunction setMethod($method){  
  74.         $this->method = $method;  
  75.     }  
  76.     publicfunction setRequestVars($request_vars){  
  77.         $this->request_vars = $request_vars;  
  78.     }  
  79.     publicfunction getData(){  
  80.         return$this->data;  
  81.     }  
  82.     publicfunction getMethod(){  
  83.         return$this->method;  
  84.     }  
  85.     publicfunction getHttpAccept(){  
  86.         return$this->http_accept;  
  87.     }  
  88.     publicfunction getRequestVars(){  
  89.         return$this->request_vars;  
  90.     }  
  91. }  


OK, so what we’ve got is a simple class for storing some information about our request (RestRequest), and a class with some static functions we can use to deal with requests and responses. As you can see, we really only have two functions to write… which is the beauty of this whole thing! Right, let’s move on…
Processing the Request
好,現在我們有了一個簡單的class來儲存request的一些資訊(RestRequest),和一個提供幾個靜態方法的class來處理請求和響應。就像你能看到的,我們還有兩個方法要去寫,這才是整個程式碼的關鍵所在,讓我們繼續...

Processing the request is pretty straight-forward, but this is where we can run into a few catches (namely with PUT and DELETE… mostly PUT). We’ll go over those in a moment, but let’s examine the RestRequest class a bit. If you’ll look at the constructor, you’ll see that we’re already interpreting the HTTP_ACCEPT header, and defaulting to JSON if none is provided. With that out of the way, we need only deal with the incoming data.
處理請求的過程非常直接,但是這才是我們可以有所收穫的地方(即PUT/DELETE,大多數是PUT),我們接下來將會討論這些。但是讓我們先來檢查一下RestRequest這個class,在構造方法中,你會看到我們已經處理了HTTP_ACCEPT的頭資訊,並且將JSON作為預設值。這樣,我們就只需要處理傳入的資料。

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. Let’s also assume that the actual data will be JSON. As stated in my previous explanation of REST, 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. So, our process request function will end up looking something like this:
我們有幾個方法可以選擇,但是讓我們假設在請求資訊的總是可以接收到鍵/值對:'data'=>真實資料。同時假設真實資料是JSON格式的。正如我前文所述,你可以根據請求的內容型別來處理JSON或者XML,但是讓我們現在簡單一點。那麼,我們處理請求的方法將會類似於這樣:

Php程式碼  收藏程式碼
  1. publicstaticfunction processRequest(){  
  2.     // get our verb 獲取動作
  3.     $request_method = strtolower($_SERVER['REQUEST_METHOD']);  
  4.     $return_obj     = new RestRequest();  
  5.     // we'll store our data here 在這裡儲存請求資料
  6.     $data           = array();  
  7.     switch ($request_method){  
  8.         // gets are easy...
  9.         case'get':  
  10.             $data = $_GET;  
  11.             break;  
  12.         // so are posts
  13.         case'post':  
  14.             $data = $_POST;  
  15.             break;  
  16.         // here's the tricky bit...
  17.         case'put':  
  18.             // basically, we read a string from PHP's special input location,
  19.             // and then parse it out into an array via parse_str... per the PHP docs:
  20.             // Parses str  as if it were the query string passed via a URL and sets
  21.             // variables in the current scope.
  22.             parse_str(file_get_contents('php://input'), $put_vars);  
  23.             $data = $put_vars;  
  24.             break;  
  25.     }  
  26.     // store the method
  27.     $return_obj->setMethod($request_method);  
  28.     // set the raw data, so we can access it if needed (there may be
  29.     // other pieces to your requests)
  30.     $return_obj->setRequestVars($data);  
  31.     if(isset($data['data'])){  
  32.         // translate the JSON to an Object for use however you want
  33.         $return_obj->setData(json_decode($data['data']));  
  34.     }  
  35.     return$return_obj;  
  36. }  


Like I said, pretty straight-forward. However, a few things to note… First, you typically don’t accept data for DELETE requests, so we don’t have a case for them in the switch. Second, you’ll notice that we store both the request variables, and the parsed JSON data. 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, email, etc.).
正如我剛才所說的,非常的簡單直接高效。然後,有幾點需要注意:首先,我們不接受DELETE請求,因此我們在switch中不提供相應的case條件。其次,你會注意到我們把請求引數和解析後的JSON資料都儲存起來了,這在請求中有其他需要處理的資料時會變得非常有用(API key或者其他),這些並不是請求的資料本身(比如一個新使用者的名字、電子郵箱等)。

So, how would we use this? Let’s go back to the user example. Assuming you’ve routed your request to the correct controller for users, we could have some code like this:
那麼,我們如何使用它呢?讓我們回到剛才user的例子。假設你已經通過路由把請求對應到正確的users控制器,程式碼如下:

Php程式碼  收藏程式碼
  1. $data = RestUtils::processRequest();  
  2. switch($data->getMethod){  
  3.     case'get':  
  4.         // retrieve a list of users
  5.         break;  
  6.     case'post':  
  7.         $user = new User();  
  8.         $user->setFirstName($data->getData()->first_name);  // just for example, this should be done cleaner
  9.         // and so on...
  10.         $user->save();  
  11.         break;  
  12.     // etc, etc, etc...
  13. }  


Please don’t do this in a real app, this is just a quick-and-dirty example. You’d want to wrap this up in a nice control structure with everything abstracted properly, but this should help you get an idea of how to use this stuff. But I digress, let’s move on to sending a response.
Sending the Response
請不要在真實的應用中這樣做,這是一個非常快速和不乾淨的示例。你應該使用一個設計良好的控制結構來把它包裹起來,適當的抽象化,但是這樣有助於你理解如何使用這些東西。讓我們繼續程式碼,傳送一個響應資訊。

Now that we can interpret the request, let’s move on to sending the response. We already know that all we really need to do is send the correct status code, and maybe some body (if this were a GET request, for example), but there is an important catch to responses that have no body. Say somebody made a request against our sample user API for a user that doesn’t exist (i.e. api/user/123). The appropriate status code to send is a 404 in this case, but simply sending the status code in the headers isn’t enough. If you viewed that page in your web browser, you would get a blank screen. This is because Apache (or whatever your web server runs on) isn’t sending the status code, so there’s no status page. We’ll need to take this into account when we build out our function. Keeping all that in mind, here’s what the code should look like:
既然我們已經可以解析請求,那麼接下來我們繼續來發送一個響應。我們已經知道我們真正需要去做的是傳送一個正確的狀態碼和一些響應訊息體(例如這是一個GET請求),但是對於沒有訊息體的響應來說有一個重要的catch(譯者:不好意思,實在是不知道如何翻譯這個詞)。假定某個人向我們的user介面傳送一個請求某個使用者資訊的請求,而這個使用者卻不存在(比如:api/user/123),此時系統傳送最合適的狀態碼是404。但是簡單的在頭資訊中傳送狀態碼是不夠的,如果你通過網頁瀏覽器瀏覽該頁面,你會看到一個空白頁面。這是因為apache伺服器(或者其他伺服器)並不會傳送此狀態碼,因此沒有狀態頁面。我們需要在構建方法的時候考慮到這一點。把所有的東西都考慮進去,程式碼會類似於下面這樣:

Php程式碼  收藏程式碼
  1. publicstaticfunction sendResponse($status = 200, $body = ''$content_type = 'text/html'){  
  2.     $status_header = 'HTTP/1.1 ' . $status . ' ' . RestUtils::getStatusCodeMessage($status);  
  3.     // set the status
  4.     header($status_header);  
  5.     // set the content type
  6.     header('Content-type: ' . $content_type);  
  7.     // pages with body are easy
  8.     if($body != ''){  
  9.         // send the body
  10.         echo$body;  
  11.         exit;  
  12.     }  
  13.     // we need to create the body if none is passed
  14.     else
  15.     {  
  16.         // create some body messages
  17.         $message = '';  
  18.         // this is purely optional, but makes the pages a little nicer to read
  19.         // for your users.  Since you won't likely send a lot of different status codes,
  20.         // this also shouldn't be too ponderous to maintain
  21.         switch($status) {  
  22.             case 401:  
  23.                 $message = 'You must be authorized to view this page.';  
  24.                 break;  
  25.             case 404:  
  26.                 $message = 'The requested URL ' . $_SERVER['REQUEST_URI'] . ' was not found.';  
  27.                 break;  
  28.             case 500:  
  29.                 $message = 'The server encountered an error processing your request.';  
  30.                 break;  
  31.             case 501:  
  32.                 $message = 'The requested method is not implemented.';  
  33.                 break;  
  34.         }  
  35.         // servers don't always have a signature turned on (this is an apache directive "ServerSignature On")
  36.         $signature = ($_SERVER['SERVER_SIGNATURE'] == '') ? $_SERVER['SERVER_SOFTWARE'] . ' Server at ' . $_SERVER['SERVER_NAME'] . ' Port ' . $_SERVER['SERVER_PORT'] : $_SERVER['SERVER_SIGNATURE'];  
  37.         // this should be templatized in a real-world solution
  38.         $body = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">  
  39.                     <html>  
  40.                         <head>  
  41.                             <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">  
  42.                             <title>' . $status . '' . RestUtils::getStatusCodeMessage($status) . '</title>  
  43.                         </head>  
  44.                         <body>  
  45.                             <h1>' . RestUtils::getStatusCodeMessage($status) . '</h1>  
  46.                             ' . $message . '
  47.                             <hr />  
  48.                             <address>' . $signature . '</address>  
  49.                         </body>  
  50.                     </html>';  
  51.         echo$body;  
  52.         exit;  
  53.     }  
  54. }  


That’s It! We technically have everything we need now to process requests and send responses. Let’s talk a bit more about why we need to have a standard body response or a custom one. For GET requests, this is pretty obvious, we need to send XML / JSON content instead of a status page (provided the request was valid). However, there’s also POSTs to deal with. Inside of your apps, when you create a new entity, you probably fetch the new entity’s ID via something like mysql_insert_id(). Well, if a user posts to your API, they’ll probably want that new ID as well. What I’ll usually do in this case is simply send the new ID as the body (with a 201 status code), but you could also wrap that in XML or JSON if you’d like.
就這樣,從技術上來說,我們已經具備了處理請求和傳送響應的所有東西。下面我們再討論以下為什麼我們需要一個標準的相應提或者一個自定義的。對於GET請求來說,非常明顯,我們需要傳送XML/JSON內容而不是一個狀態頁(假設請求是合法的)。然後,我們還有POST請求要去處理。在你的應用內部,當你建立一個新的實體,你也許需要使用通過類似於mysql_insert_id()這樣的函式得到這個實體的ID。那麼,當一個使用者提交到你的介面,他們將很可能想要知道這個新的ID是什麼。在這種情況下,我通常的做法是非常簡單的把這個新ID作為響應的訊息體傳送給使用者(同時傳送一個201的狀態碼頭資訊),但是如果你願意,你也可以使用XML或者JSON來把它包裹起來。

So, let’s extend our sample implementation a bit:
現在,讓我們來擴充套件一下我們的例子,讓它更加實際一點:

Php程式碼  收藏程式碼
  1. switch($data->getMethod){  
  2.     // this is a request for all users, not one in particular
  3.     case'get':  
  4.         $user_list = getUserList(); // assume this returns an array
  5.         if($data->getHttpAccept == 'json'){  
  6.             RestUtils::sendResponse(200, json_encode($user_list), 'application/json');  
  7.         }elseif ($data->getHttpAccept == 'xml') {  
  8.             // using the XML_SERIALIZER Pear Package
  9.             $options = array
  10.             (  
  11.                 'indent' => '     ',  
  12.                 'addDecl' => false,  
  13.                 'rootName' => $fc->getAction(),  
  14.                 XML_SERIALIZER_OPTION_RETURN_RESULT => true  
  15.             );  
  16.             $serializer = new XML_Serializer($options);  
  17.             RestUtils::sendResponse(200, $serializer->serialize($user_list), 'application/xml');  
  18.         }  
  19.         break;  
  20.     // new user create
  21.     case'post':  
  22.         $user = new User();  
  23.         $user->setFirstName($data->getData()->first_name);  // just for example, this should be done cleaner
  24.         // and so on...
  25.         $user->save();  
  26.         // just send the new ID as the body
  27.         RestUtils::sendResponse(201, $user->getId());  
  28.         break;  
  29. }  


Again, this is just an example, but it does show off (I think, at least) how little effort it takes to implement RESTful stuff.
Wrapping Up
再一次說明,這是一個例子,但它確實向我們展示了(至少我認為是)它能輕而易舉的實現RESTful介面。

So, that’s about it. I’m pretty confident that I’ve beaten the point that this should be quite easy into the ground, so I’d like to close with how you can take this stuff further and perhaps properly implement it.
所以,這就是它。我非常的自信的說,我已經把這些解釋的非常清楚。因此,我就不再贅述你如何具體實現它。

In a real-world MVC application, what you would probably want to do is set up a controller for your API that loads individual API controllers. For example, using the above stuff, we’d possibly create a UserRestController which had four methods: get(), put(), post(), and delete(). The API controller would look at the request and determine which method to invoke on that controller. That method would then use the utils to process the request, do what it needs to do data-wise, then use the utils to send a response.
在一個真實的MVC應用中,也許你想要做的就是為你的每個介面建立一個單獨的控制器。例如,利用上面的東西,我們可以建立一個UserRestController控制器,這個控制器有四個方法,分別為:get(), put(), post(), 和 delete()。介面控制器將會檢視請求型別然後決定哪個方法會被執行。這個方法會再使用工具來處理請求,處理資料,然後使用工具傳送響應。

You could also take it a step further than that, and abstract out your API controller and data models a bit more. Rather than explicitly creating a controller for every data model in your app, you could add some logic into your API controller to first look for an explicitly defined controller, and if none is found, try to look for an existing model. For example, the url “api/user/1″, would first trigger a lookup for a “user” rest controller. If none is found, it could then look for a model called “user” in your app. If one is found, you could write up a bit of automated voodoo to automatically process all the requests against those models.
你也許會比現在更進一步,把你的介面控制器和資料模型抽象出來,而不是明確的為每一個數據模型建立控制器,你可以給你的介面控制器新增一些邏輯,先去查詢一個明確定義好的控制器,如果沒有,試著去查詢一個已經存在的模型。例如:網址"api/user/1"將會首先觸發查詢一個叫user的最終控制器,如果沒有,它會查詢應用中叫user的模型,如果找到了,你可以寫一個自動化的方法來自動處理所有請求這個模型的請求。

Going even further, you could then make a generic “list-all” method that works similar to the previous paragraph’s example. Say your url was “api/users”. The API controller could first check for a “users” rest controller, and if none was found, recognize that users is pluaralized, depluralize it, and then look for a “user” model. If one’s found, load a list the list of users and send that off.
再進一步,你可以建立一個通用的"list-all"方法,就像上面一段中的例子一樣。假定你的url是"api/usrs",介面控制器首先會查詢叫users的控制器,如果沒有找到,確認users是複數,把它變成單數,然後查詢一個叫user的模型,如果找到了,載入一個使用者列表然後把他們傳送出去。

Finally, you could add digest authentication to your API quite easily as well. Say you only wanted properly authenticated users to access your API, well, you could throw some code like this into your process request functionality (borrowed from an existing app of mine, so there’s some constants and variables referenced that aren’t defined in this snippet):
最後,你可以給你的介面新增簡單的身份驗證。假定你僅僅希望適當的驗證訪問你的介面的使用者,那麼,你可以在處理請求的方法中新增類似於下面的一些程式碼(借用我的一個現有應用,因此有一些常量和變數在這個程式碼片段裡面並沒有被定義):

Php程式碼  收藏程式碼
  1. // figure out if we need to challenge the user
  2. if(emptyempty($_SERVER['PHP_AUTH_DIGEST']))  
  3. {  
  4.     header('HTTP/1.1 401 Unauthorized');  
  5.     header('WWW-Authenticate: Digest realm="' . AUTH_REALM . '",qop="auth",nonce="' . uniqid() . '",opaque="' . md5(AUTH_REALM) . '"');  
  6.     // show the error if they hit cancel
  7.     die(RestControllerLib::error(401, true));  
  8. }  
  9. // now, analayze the PHP_AUTH_DIGEST var
  10. if(!($data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) || $auth_username != $data['username'])  
  11. {  
  12.     // show the error due to bad auth
  13.     die(RestUtils::sendResponse(401));  
  14. }  
  15. // so far, everything's good, let's now check the response a bit more...
  16. $A1 = md5($data['username'] . ':' . AUTH_REALM . ':' . $auth_pass);  
  17. $A2 = md5($_SERVER['REQUEST_METHOD'] . ':' . $data['uri']);  
  18. $valid_response = md5($A1 . ':' . $data['nonce'] . ':' . $data['nc'] . ':' . $data['cnonce'] . ':' . $data['qop'] . ':' . $A2);  
  19. // last check..
  20. if($data['response'] != $valid_response)  
  21. {  
  22.     die(RestUtils::sendResponse(401));  
  23. }  


Pretty cool stuff, huh? With a little bit of code and some clever logic, you can add a fully functional REST API to your apps very quickly. I’m not just saying that to cheerlead the concept either, I implemented this stuff into one of my personal frameworks in about half a day, and then spent another half day adding all sorts of cool magic to it. If you (the reader) are interested in seeing my final implementation, drop me a note in the comments and I’d be happy to share it with you! Also, if you’ve got any cool ideas you’d like to share, be sure to drop those in the comments as well… if I like it enough, I’d even let you guest author your own article on the subject!
非常酷,對吧?通過少量的程式碼和一些智慧的邏輯,你可以非常快速的給你的應用新增全功能的REST介面。我並不僅僅是支援這個概念,我已經在我個人的框架裡面實現了這些東西,而這些僅僅花費了半天的時間,然後再花費半天時間新增一些非常酷的東西。如果你(讀者)對我最終的實現感興趣,請在評論中留言,我會非常樂趣和你分享它。同時,如果你有什麼比較酷的想法,也歡迎通過評論和我進行分享。如果我足夠喜歡它,我會邀請你在這裡發表自己的文章。

Until next time…

UPDATE: The much-requested follow-up to this article has been posted: Making RESTful Requests in PHP