使用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代碼

class RestUtils  
{  
    public static function processRequest(){  

    }  

    public static function sendResponse($status = 200, $body = '', $content_type = 'text/html'){  

    }  

    public static function getStatusCodeMessage($status){  
        // these could be stored in a .ini file and loaded  
        // via parse_ini_file()... however, this will suffice  
        // for an example  
        // 這些應該被存儲在一個.ini的文件中,然後通過parse_ini_file()函數來解析出來,然而這樣也足夠了,比如:  
        $codes = Array(  
            100 => 'Continue',  
            101 => 'Switching Protocols',  
            200 => 'OK',  
            201 => 'Created',  
            202 => 'Accepted',  
            203 => 'Non-Authoritative Information',  
            204 => 'No Content',  
            205 => 'Reset Content',  
            206 => 'Partial Content',  
            300 => 'Multiple Choices',  
            301 => 'Moved Permanently',  
            302 => 'Found',  
            303 => 'See Other',  
            304 => 'Not Modified',  
            305 => 'Use Proxy',  
            306 => '(Unused)',  
            307 => 'Temporary Redirect',  
            400 => 'Bad Request',  
            401 => 'Unauthorized',  
            402 => 'Payment Required',  
            403 => 'Forbidden',  
            404 => 'Not Found',  
            405 => 'Method Not Allowed',  
            406 => 'Not Acceptable',  
            407 => 'Proxy Authentication Required',  
            408 => 'Request Timeout',  
            409 => 'Conflict',  
            410 => 'Gone',  
            411 => 'Length Required',  
            412 => 'Precondition Failed',  
            413 => 'Request Entity Too Large',  
            414 => 'Request-URI Too Long',  
            415 => 'Unsupported Media Type',  
            416 => 'Requested Range Not Satisfiable',  
            417 => 'Expectation Failed',  
            500 => 'Internal Server Error',  
            501 => 'Not Implemented',  
            502 => 'Bad Gateway',  
            503 => 'Service Unavailable',  
            504 => 'Gateway Timeout',  
            505 => 'HTTP Version Not Supported'  
        );  

        return (isset($codes[$status])) ? $codes[$status] : '';  
    }  
}  

class RestRequest  
{  
    private $request_vars;  
    private $data;  
    private $http_accept;  
    private $method;  

    public function __construct(){  
        $this->request_vars      = array();  
        $this->data              = '';  
        $this->http_accept       = (strpos($_SERVER['HTTP_ACCEPT'], 'json')) ? 'json' : 'xml';  
        $this->method            = 'get';  
    }  

    public function setData($data){  
        $this->data = $data;  
    }  

    public function setMethod($method){  
        $this->method = $method;  
    }  

    public function setRequestVars($request_vars){  
        $this->request_vars = $request_vars;  
    }  

    public function getData(){  
        return $this->data;  
    }  

    public function getMethod(){  
        return $this->method;  
    }  

    public function getHttpAccept(){  
        return $this->http_accept;  
    }  

    public function getRequestVars(){  
        return $this->request_vars;  
    }  
}  

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代碼

public static function processRequest(){  
    // get our verb 獲取動作  
    $request_method = strtolower($_SERVER['REQUEST_METHOD']);  
    $return_obj     = new RestRequest();  
    // we'll store our data here 在這裏存儲請求數據  
    $data           = array();  

    switch ($request_method){  
        // gets are easy...  
        case 'get':  
            $data = $_GET;  
            break;  
        // so are posts  
        case 'post':  
            $data = $_POST;  
            break;  
        // here's the tricky bit...  
        case 'put':  
            // basically, we read a string from PHP's special input location,  
            // and then parse it out into an array via parse_str... per the PHP docs:  
            // Parses str  as if it were the query string passed via a URL and sets  
            // variables in the current scope.  
            parse_str(file_get_contents('php://input'), $put_vars);  
            $data = $put_vars;  
            break;  
    }  

    // store the method  
    $return_obj->setMethod($request_method);  

    // set the raw data, so we can access it if needed (there may be  
    // other pieces to your requests)  
    $return_obj->setRequestVars($data);  

    if(isset($data['data'])){  
        // translate the JSON to an Object for use however you want  
        $return_obj->setData(json_decode($data['data']));  
    }  
    return $return_obj;  
}  

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代碼

$data = RestUtils::processRequest();  

switch($data->getMethod){  
    case 'get':  
        // retrieve a list of users  
        break;  
    case 'post':  
        $user = new User();  
        $user->setFirstName($data->getData()->first_name);  // just for example, this should be done cleaner  
        // and so on...  
        $user->save();  
        break;  
    // etc, etc, etc...  
}  

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代碼

public static function sendResponse($status = 200, $body = '', $content_type = 'text/html'){  
    $status_header = 'HTTP/1.1 ' . $status . ' ' . RestUtils::getStatusCodeMessage($status);  
    // set the status  
    header($status_header);  
    // set the content type  
    header('Content-type: ' . $content_type);  

    // pages with body are easy  
    if($body != ''){  
        // send the body  
        echo $body;  
        exit;  
    }  
    // we need to create the body if none is passed  
    else  
    {  
        // create some body messages  
        $message = '';  

        // this is purely optional, but makes the pages a little nicer to read  
        // for your users.  Since you won't likely send a lot of different status codes,  
        // this also shouldn't be too ponderous to maintain  
        switch($status) {  
            case 401:  
                $message = 'You must be authorized to view this page.';  
                break;  
            case 404:  
                $message = 'The requested URL ' . $_SERVER['REQUEST_URI'] . ' was not found.';  
                break;  
            case 500:  
                $message = 'The server encountered an error processing your request.';  
                break;  
            case 501:  
                $message = 'The requested method is not implemented.';  
                break;  
        }  

        // servers don't always have a signature turned on (this is an apache directive "ServerSignature On")  
        $signature = ($_SERVER['SERVER_SIGNATURE'] == '') ? $_SERVER['SERVER_SOFTWARE'] . ' Server at ' . $_SERVER['SERVER_NAME'] . ' Port ' . $_SERVER['SERVER_PORT'] : $_SERVER['SERVER_SIGNATURE'];  

        // this should be templatized in a real-world solution  
        $body = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">  
                    <html>  
                        <head>  
                            <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">  
                            <title>' . $status . ' ' . RestUtils::getStatusCodeMessage($status) . '</title>  
                        </head>  
                        <body>  
                            <h1>' . RestUtils::getStatusCodeMessage($status) . '</h1>  
                            ' . $message . '  

                            <hr />  
                            <address>' . $signature . '</address>  
                        </body>  
                    </html>';  

        echo $body;  
        exit;  
    }  
}  

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代碼

switch($data->getMethod){  
    // this is a request for all users, not one in particular  
    case 'get':  
        $user_list = getUserList(); // assume this returns an array  

        if($data->getHttpAccept == 'json'){  
            RestUtils::sendResponse(200, json_encode($user_list), 'application/json');  
        }else if ($data->getHttpAccept == 'xml') {  
            // using the XML_SERIALIZER Pear Package  
            $options = array  
            (  
                'indent' => '     ',  
                'addDecl' => false,  
                'rootName' => $fc->getAction(),  
                XML_SERIALIZER_OPTION_RETURN_RESULT => true  
            );  
            $serializer = new XML_Serializer($options);  

            RestUtils::sendResponse(200, $serializer->serialize($user_list), 'application/xml');  
        }  

        break;  
    // new user create  
    case 'post':  
        $user = new User();  
        $user->setFirstName($data->getData()->first_name);  // just for example, this should be done cleaner  
        // and so on...  
        $user->save();  

        // just send the new ID as the body  
        RestUtils::sendResponse(201, $user->getId());  
        break;  
}  

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代碼

// figure out if we need to challenge the user  
if(emptyempty($_SERVER['PHP_AUTH_DIGEST']))  
{  
    header('HTTP/1.1 401 Unauthorized');  
    header('WWW-Authenticate: Digest realm="' . AUTH_REALM . '",qop="auth",nonce="' . uniqid() . '",opaque="' . md5(AUTH_REALM) . '"');  

    // show the error if they hit cancel  
    die(RestControllerLib::error(401, true));  
}  

// now, 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));  
}  

// so far, everything's good, let's now check the response a bit more...  
$A1 = md5($data['username'] . ':' . AUTH_REALM . ':' . $auth_pass);  
$A2 = md5($_SERVER['REQUEST_METHOD'] . ':' . $data['uri']);  
$valid_response = md5($A1 . ':' . $data['nonce'] . ':' . $data['nc'] . ':' . $data['cnonce'] . ':' . $data['qop'] . ':' . $A2);  

// last check..  
if($data['response'] != $valid_response)  
{  
    die(RestUtils::sendResponse(401));  
}  

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接口。我並不僅僅是支持這個概念,我已經在我個人的框架裏面實現了這些東西,而這些僅僅花費了半天的時間,然後再花費半天時間添加一些非常酷的東西。如果你(讀者)對我最終的實現感興趣,請在評論中留言,我會非常樂趣和你分享它。同時,如果你有什麼比較酷的想法,也歡迎通過評論和我進行分享。如果我足夠喜歡它,我會邀請你在這裏發表自己的文章。

http://www.gen-x-design.com/archives/making-restful-requests-in-php/

發佈了229 篇原創文章 · 獲贊 148 · 訪問量 91萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章