[img]http://www.gen-x-design.com/wp-content/uploads/2009/02/rest-apis1.png[/img]
译者前言:
首先这是一篇国外的英文文章,非常系统、详尽的介绍了如何使用PHP创建REST API,国内这方面的资料非常非常的有限,而且基本没有可操作性。这篇文章写的非常好,只要对PHP稍有了解的程序员,看完本文基本可以自己动手写REST API,花了几个小时翻译过来和大家共享,希望可以帮助大家。转载请注明出处。
本文地址:[url]http://hmw.iteye.com/blog/1190827[/url]
原文地址:[url=http://www.gen-x-design.com/archives/create-a-rest-api-with-php]Create a REST API with PHP[/url]
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).
[color=darkred]最近互联网上比较热门的一个名词是APIs(接口),特别是leverage REST。不过考虑到REST APIs在任何语言下都是非常的简单,也就没什么好惊奇的了。同时,它也是非常的容易创建,你基本只需要使用已经存在多年的HTTP规范就可以。我认为Rails语言的为数不多的优点之一就是良好的REST支持,不仅是提供APIs,同时也有很多的客户端支持(我的一些Rails粉丝同事都向我解释了这一点)。[/color]
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!
[color=darkred]认真的讲,假如你从来没有使用过REST,却曾经使用过SOAP API,或者只是简单的打开一个令人头大的WSDL文档。小伙子,我确实要带给你一个好消息![/color]
So, What on Earth is REST? Why Should You Care?
[color=darkred]那么,究竟什么是REST?为什么你应该关心?[/color]
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.
[color=darkred]在我们开始写代码之前,我想要确认每个人都可以很好的理解什么是REST以及它是如何特别适合APIs的。首先,从技术上来讲,REST并不是仅仅特定于APIs应用,它更多的是一个通用的概念。然而,很明显,我们这篇文章所讨论的REST就是在接口应用的环境下。因此,让我们看看一个API的基本要求已经REST如何处理他们。
[/color]
[b][size=large]Requests [color=darkred]请求[/color][/size][/b]
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.
[color=darkred]所有的APIs都需要接收请求。对于一个RESTful API,你需要一个定义好的URL规则,我们假定你想要提供一个接口给你网站上的用户(我知道,我总是使用"用户"这个概念来举例)。你的URL结构可能类似于这样:"api/users"或者"api/users/[id]",这取决于请求接口的操作类型。同时,你也需要考虑你想要怎么样接收数据。近来一段时间,很多人正在使用JSON或者XML,从我个人来讲,我更加喜欢JSON,因为它可以很好的和javascript进行交互操作,同时PHP也可以很简单的通过json_encode和json_decode两个函数来对它进行编码和解码。如果你希望自己的接口真正强健,你应该通过识别请求的内容类型(比如application/json或者application/xml)同时允许接收两种格式。但是,限制只接收一种类型的数据也是可以很好的被接受。真见鬼,假如你愿意,你甚至可以使用简单的键/值对。[/color]
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
[color=darkred]一个请求的其他部分是它真正要做的事情,比如加载、保存等。通常来说,你应该提供几种结构来定义请求者(消费者)所希望的操作,但是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的用户[/color]
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…
[color=darkred]正如你所希望看到的,REST已经解决了很多令人头疼的创建接口的问题,通过一些简单的,容易理解的标准和协议。但是一个好的接口还要另外一个方面...[/color]
[b][size=large]Responses [color=darkred]响应[/color][/size][/b]
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.
[color=darkred]所以,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)不应该失败,而是采用默认的响应类型。[/color]
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.
[color=darkred]但是和请求有关的错误和其他重要的状态信息怎么办呢?简单,使用HTTP的状态码!这是我创建RESTful接口最喜欢的事情之一。通过使用HTTP状态码,你不需要为你的接口想出error/success规则,它已经为你做好。比如:假如一个消费者提交数据(POST)到/api/users,你需要返回一个成功创建的消息,此时你可以简单的发送一个201状态码(201=Created)。如果失败了,服务器端失败就发送一个500(500=内部服务器错误),如果请求中断就发送一个400(400=错误请求)。也许他们会尝试向一个不接受POST请求的接口提交数据,你就可以发送一个501错误(未执行)。又或者你的MySQL服务器挂了,接口也会临时性的中断,发送一个503错误(服务不可用)。幸运的是,你已经知道了这些,假如你想要了解更多关于状态码的资料,可以在维基百科上查找:List of HTTP Status Codes。[/color]
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.
[color=darkred]我希望你能看到REST接口的这些优点。它真的超级酷。在PHP社区社区里没有被广泛的讨论真是非常的遗憾(至少我知道的是这样)。我觉得这主要是由于没有很好的文档介绍如何处理除了GET和POST之后的请求,即PUT和DELETE。不可否认,处理这些是有点傻,但是却不难。我相信一些流行的框架也许已经有了某种REST的实现方式,但是我不是一个框架粉丝(原因有很多),并且即使有人已经为你提供了解决方案,你知道这些也是非常有好处的。[/color]
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!
[color=darkred]如果你还是不太自信这是一个非常有用的API范式,看一下REST已经为Ruby on Rails做了什么。其中最令人称道的就是创建接口的便利性(通过某种RoR voodoo,我确信),而且确实如此。虽然我对RoR了解很少,但是办公室的Ruby粉丝们向我说教过很多次。不好意思跑题了,让我们开始写代码。[/color]
[b][size=large]Getting Started with REST and PHP [color=darkred]开始使用PHP写REST[/color][/size][/b]
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.
[color=darkred]最后一项免责声明:我们接下来提供的代码并不能被用来作为一个稳健的解决方案。我的主要目的是向大家展示如果使用PHP处理REST的每个单独部分,而把最后的解决方案留给你们自己去创建。[/color]
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:
[color=darkred]那么,让我们开始深入代码。我认为做一个实际事情做好的方法就是新建一个class,这个class将提供创建REST API所需要的所有功能性方法。现在我们新建一个小的class来存储我们的数据。你可以把它拿去扩展一下然后应用到自己的需求中。我们现在开始写点东西:[/color]
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
[color=darkred]好,现在我们有了一个简单的class来存储request的一些信息(RestRequest),和一个提供几个静态方法的class来处理请求和响应。就像你能看到的,我们还有两个方法要去写,这才是整个代码的关键所在,让我们继续...[/color]
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.
[color=darkred]处理请求的过程非常直接,但是这才是我们可以有所收获的地方(即PUT/DELETE,大多数是PUT),我们接下来将会讨论这些。但是让我们先来检查一下RestRequest这个class,在构造方法中,你会看到我们已经处理了HTTP_ACCEPT的头信息,并且将JSON作为默认值。这样,我们就只需要处理传入的数据。[/color]
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:
[color=darkred]我们有几个方法可以选择,但是让我们假设在请求信息的总是可以接收到键/值对:'data'=>真实数据。同时假设真实数据是JSON格式的。正如我前文所述,你可以根据请求的内容类型来处理JSON或者XML,但是让我们现在简单一点。那么,我们处理请求的方法将会类似于这样:[/color]
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.).
[color=darkred]正如我刚才所说的,非常的简单直接高效。然后,有几点需要注意:首先,我们不接受DELETE请求,因此我们在switch中不提供相应的case条件。其次,你会注意到我们把请求参数和解析后的JSON数据都存储起来了,这在请求中有其他需要处理的数据时会变得非常有用(API key或者其他),这些并不是请求的数据本身(比如一个新用户的名字、电子邮箱等)。[/color]
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:
[color=darkred]那么,我们如何使用它呢?让我们回到刚才user的例子。假设你已经通过路由把请求对应到正确的users控制器,代码如下:[/color]
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
[color=darkred]请不要在真实的应用中这样做,这是一个非常快速和不干净的示例。你应该使用一个设计良好的控制结构来把它包裹起来,适当的抽象化,但是这样有助于你理解如何使用这些东西。让我们继续代码,发送一个响应信息。[/color]
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:
[color=darkred]既然我们已经可以解析请求,那么接下来我们继续来发送一个响应。我们已经知道我们真正需要去做的是发送一个正确的状态码和一些响应消息体(例如这是一个GET请求),但是对于没有消息体的响应来说有一个重要的catch(译者:不好意思,实在是不知道如何翻译这个词)。假定某个人向我们的user接口发送一个请求某个用户信息的请求,而这个用户却不存在(比如:api/user/123),此时系统发送最合适的状态码是404。但是简单的在头信息中发送状态码是不够的,如果你通过网页浏览器浏览该页面,你会看到一个空白页面。这是因为apache服务器(或者其他服务器)并不会发送此状态码,因此没有状态页面。我们需要在构建方法的时候考虑到这一点。把所有的东西都考虑进去,代码会类似于下面这样:[/color]
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.
[color=darkred]就这样,从技术上来说,我们已经具备了处理请求和发送响应的所有东西。下面我们再讨论以下为什么我们需要一个标准的相应提或者一个自定义的。对于GET请求来说,非常明显,我们需要发送XML/JSON内容而不是一个状态页(假设请求是合法的)。然后,我们还有POST请求要去处理。在你的应用内部,当你创建一个新的实体,你也许需要使用通过类似于mysql_insert_id()这样的函数得到这个实体的ID。那么,当一个用户提交到你的接口,他们将很可能想要知道这个新的ID是什么。在这种情况下,我通常的做法是非常简单的把这个新ID作为响应的消息体发送给用户(同时发送一个201的状态码头信息),但是如果你愿意,你也可以使用XML或者JSON来把它包裹起来。[/color]
So, let’s extend our sample implementation a bit:
[color=darkred]现在,让我们来扩展一下我们的例子,让它更加实际一点:[/color]
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
[color=darkred]再一次说明,这是一个例子,但它确实向我们展示了(至少我认为是)它能轻而易举的实现RESTful接口。[/color]
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.
[color=darkred]所以,这就是它。我非常的自信的说,我已经把这些解释的非常清楚。因此,我就不再赘述你如何具体实现它。[/color]
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.
[color=darkred]在一个真实的MVC应用中,也许你想要做的就是为你的每个接口创建一个单独的控制器。例如,利用上面的东西,我们可以创建一个UserRestController控制器,这个控制器有四个方法,分别为:get(), put(), post(), 和 delete()。接口控制器将会查看请求类型然后决定哪个方法会被执行。这个方法会再使用工具来处理请求,处理数据,然后使用工具发送响应。[/color]
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.
[color=darkred]你也许会比现在更进一步,把你的接口控制器和数据模型抽象出来,而不是明确的为每一个数据模型创建控制器,你可以给你的接口控制器添加一些逻辑,先去查找一个明确定义好的控制器,如果没有,试着去查找一个已经存在的模型。例如:网址"api/user/1"将会首先触发查找一个叫user的最终控制器,如果没有,它会查找应用中叫user的模型,如果找到了,你可以写一个自动化的方法来自动处理所有请求这个模型的请求。[/color]
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.
[color=darkred]再进一步,你可以建立一个通用的"list-all"方法,就像上面一段中的例子一样。假定你的url是"api/usrs",接口控制器首先会查找叫users的控制器,如果没有找到,确认users是复数,把它变成单数,然后查找一个叫user的模型,如果找到了,加载一个用户列表然后把他们发送出去。[/color]
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):
[color=darkred]最后,你可以给你的接口添加简单的身份验证。假定你仅仅希望适当的验证访问你的接口的用户,那么,你可以在处理请求的方法中添加类似于下面的一些代码(借用我的一个现有应用,因此有一些常量和变量在这个代码片段里面并没有被定义):[/color]
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!
[color=darkred]非常酷,对吧?通过少量的代码和一些智能的逻辑,你可以非常快速的给你的应用添加全功能的REST接口。我并不仅仅是支持这个概念,我已经在我个人的框架里面实现了这些东西,而这些仅仅花费了半天的时间,然后再花费半天时间添加一些非常酷的东西。如果你(读者)对我最终的实现感兴趣,请在评论中留言,我会非常乐趣和你分享它。同时,如果你有什么比较酷的想法,也欢迎通过评论和我进行分享。如果我足够喜欢它,我会邀请你在这里发表自己的文章。[/color]
Until next time…
UPDATE: The much-requested follow-up to this article has been posted: Making RESTful Requests in PHP
[color=darkred]更新:这篇文章的下一篇已经发表了,见:[url=http://www.gen-x-design.com/archives/making-restful-requests-in-php/]Making RESTful Requests in PHP,使用PHP发送RESTfull请求[/url][/color]
译者前言:
首先这是一篇国外的英文文章,非常系统、详尽的介绍了如何使用PHP创建REST API,国内这方面的资料非常非常的有限,而且基本没有可操作性。这篇文章写的非常好,只要对PHP稍有了解的程序员,看完本文基本可以自己动手写REST API,花了几个小时翻译过来和大家共享,希望可以帮助大家。转载请注明出处。
本文地址:[url]http://hmw.iteye.com/blog/1190827[/url]
原文地址:[url=http://www.gen-x-design.com/archives/create-a-rest-api-with-php]Create a REST API with PHP[/url]
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).
[color=darkred]最近互联网上比较热门的一个名词是APIs(接口),特别是leverage REST。不过考虑到REST APIs在任何语言下都是非常的简单,也就没什么好惊奇的了。同时,它也是非常的容易创建,你基本只需要使用已经存在多年的HTTP规范就可以。我认为Rails语言的为数不多的优点之一就是良好的REST支持,不仅是提供APIs,同时也有很多的客户端支持(我的一些Rails粉丝同事都向我解释了这一点)。[/color]
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!
[color=darkred]认真的讲,假如你从来没有使用过REST,却曾经使用过SOAP API,或者只是简单的打开一个令人头大的WSDL文档。小伙子,我确实要带给你一个好消息![/color]
So, What on Earth is REST? Why Should You Care?
[color=darkred]那么,究竟什么是REST?为什么你应该关心?[/color]
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.
[color=darkred]在我们开始写代码之前,我想要确认每个人都可以很好的理解什么是REST以及它是如何特别适合APIs的。首先,从技术上来讲,REST并不是仅仅特定于APIs应用,它更多的是一个通用的概念。然而,很明显,我们这篇文章所讨论的REST就是在接口应用的环境下。因此,让我们看看一个API的基本要求已经REST如何处理他们。
[/color]
[b][size=large]Requests [color=darkred]请求[/color][/size][/b]
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.
[color=darkred]所有的APIs都需要接收请求。对于一个RESTful API,你需要一个定义好的URL规则,我们假定你想要提供一个接口给你网站上的用户(我知道,我总是使用"用户"这个概念来举例)。你的URL结构可能类似于这样:"api/users"或者"api/users/[id]",这取决于请求接口的操作类型。同时,你也需要考虑你想要怎么样接收数据。近来一段时间,很多人正在使用JSON或者XML,从我个人来讲,我更加喜欢JSON,因为它可以很好的和javascript进行交互操作,同时PHP也可以很简单的通过json_encode和json_decode两个函数来对它进行编码和解码。如果你希望自己的接口真正强健,你应该通过识别请求的内容类型(比如application/json或者application/xml)同时允许接收两种格式。但是,限制只接收一种类型的数据也是可以很好的被接受。真见鬼,假如你愿意,你甚至可以使用简单的键/值对。[/color]
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
[color=darkred]一个请求的其他部分是它真正要做的事情,比如加载、保存等。通常来说,你应该提供几种结构来定义请求者(消费者)所希望的操作,但是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的用户[/color]
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…
[color=darkred]正如你所希望看到的,REST已经解决了很多令人头疼的创建接口的问题,通过一些简单的,容易理解的标准和协议。但是一个好的接口还要另外一个方面...[/color]
[b][size=large]Responses [color=darkred]响应[/color][/size][/b]
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.
[color=darkred]所以,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)不应该失败,而是采用默认的响应类型。[/color]
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.
[color=darkred]但是和请求有关的错误和其他重要的状态信息怎么办呢?简单,使用HTTP的状态码!这是我创建RESTful接口最喜欢的事情之一。通过使用HTTP状态码,你不需要为你的接口想出error/success规则,它已经为你做好。比如:假如一个消费者提交数据(POST)到/api/users,你需要返回一个成功创建的消息,此时你可以简单的发送一个201状态码(201=Created)。如果失败了,服务器端失败就发送一个500(500=内部服务器错误),如果请求中断就发送一个400(400=错误请求)。也许他们会尝试向一个不接受POST请求的接口提交数据,你就可以发送一个501错误(未执行)。又或者你的MySQL服务器挂了,接口也会临时性的中断,发送一个503错误(服务不可用)。幸运的是,你已经知道了这些,假如你想要了解更多关于状态码的资料,可以在维基百科上查找:List of HTTP Status Codes。[/color]
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.
[color=darkred]我希望你能看到REST接口的这些优点。它真的超级酷。在PHP社区社区里没有被广泛的讨论真是非常的遗憾(至少我知道的是这样)。我觉得这主要是由于没有很好的文档介绍如何处理除了GET和POST之后的请求,即PUT和DELETE。不可否认,处理这些是有点傻,但是却不难。我相信一些流行的框架也许已经有了某种REST的实现方式,但是我不是一个框架粉丝(原因有很多),并且即使有人已经为你提供了解决方案,你知道这些也是非常有好处的。[/color]
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!
[color=darkred]如果你还是不太自信这是一个非常有用的API范式,看一下REST已经为Ruby on Rails做了什么。其中最令人称道的就是创建接口的便利性(通过某种RoR voodoo,我确信),而且确实如此。虽然我对RoR了解很少,但是办公室的Ruby粉丝们向我说教过很多次。不好意思跑题了,让我们开始写代码。[/color]
[b][size=large]Getting Started with REST and PHP [color=darkred]开始使用PHP写REST[/color][/size][/b]
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.
[color=darkred]最后一项免责声明:我们接下来提供的代码并不能被用来作为一个稳健的解决方案。我的主要目的是向大家展示如果使用PHP处理REST的每个单独部分,而把最后的解决方案留给你们自己去创建。[/color]
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:
[color=darkred]那么,让我们开始深入代码。我认为做一个实际事情做好的方法就是新建一个class,这个class将提供创建REST API所需要的所有功能性方法。现在我们新建一个小的class来存储我们的数据。你可以把它拿去扩展一下然后应用到自己的需求中。我们现在开始写点东西:[/color]
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
[color=darkred]好,现在我们有了一个简单的class来存储request的一些信息(RestRequest),和一个提供几个静态方法的class来处理请求和响应。就像你能看到的,我们还有两个方法要去写,这才是整个代码的关键所在,让我们继续...[/color]
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.
[color=darkred]处理请求的过程非常直接,但是这才是我们可以有所收获的地方(即PUT/DELETE,大多数是PUT),我们接下来将会讨论这些。但是让我们先来检查一下RestRequest这个class,在构造方法中,你会看到我们已经处理了HTTP_ACCEPT的头信息,并且将JSON作为默认值。这样,我们就只需要处理传入的数据。[/color]
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:
[color=darkred]我们有几个方法可以选择,但是让我们假设在请求信息的总是可以接收到键/值对:'data'=>真实数据。同时假设真实数据是JSON格式的。正如我前文所述,你可以根据请求的内容类型来处理JSON或者XML,但是让我们现在简单一点。那么,我们处理请求的方法将会类似于这样:[/color]
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.).
[color=darkred]正如我刚才所说的,非常的简单直接高效。然后,有几点需要注意:首先,我们不接受DELETE请求,因此我们在switch中不提供相应的case条件。其次,你会注意到我们把请求参数和解析后的JSON数据都存储起来了,这在请求中有其他需要处理的数据时会变得非常有用(API key或者其他),这些并不是请求的数据本身(比如一个新用户的名字、电子邮箱等)。[/color]
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:
[color=darkred]那么,我们如何使用它呢?让我们回到刚才user的例子。假设你已经通过路由把请求对应到正确的users控制器,代码如下:[/color]
$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
[color=darkred]请不要在真实的应用中这样做,这是一个非常快速和不干净的示例。你应该使用一个设计良好的控制结构来把它包裹起来,适当的抽象化,但是这样有助于你理解如何使用这些东西。让我们继续代码,发送一个响应信息。[/color]
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:
[color=darkred]既然我们已经可以解析请求,那么接下来我们继续来发送一个响应。我们已经知道我们真正需要去做的是发送一个正确的状态码和一些响应消息体(例如这是一个GET请求),但是对于没有消息体的响应来说有一个重要的catch(译者:不好意思,实在是不知道如何翻译这个词)。假定某个人向我们的user接口发送一个请求某个用户信息的请求,而这个用户却不存在(比如:api/user/123),此时系统发送最合适的状态码是404。但是简单的在头信息中发送状态码是不够的,如果你通过网页浏览器浏览该页面,你会看到一个空白页面。这是因为apache服务器(或者其他服务器)并不会发送此状态码,因此没有状态页面。我们需要在构建方法的时候考虑到这一点。把所有的东西都考虑进去,代码会类似于下面这样:[/color]
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.
[color=darkred]就这样,从技术上来说,我们已经具备了处理请求和发送响应的所有东西。下面我们再讨论以下为什么我们需要一个标准的相应提或者一个自定义的。对于GET请求来说,非常明显,我们需要发送XML/JSON内容而不是一个状态页(假设请求是合法的)。然后,我们还有POST请求要去处理。在你的应用内部,当你创建一个新的实体,你也许需要使用通过类似于mysql_insert_id()这样的函数得到这个实体的ID。那么,当一个用户提交到你的接口,他们将很可能想要知道这个新的ID是什么。在这种情况下,我通常的做法是非常简单的把这个新ID作为响应的消息体发送给用户(同时发送一个201的状态码头信息),但是如果你愿意,你也可以使用XML或者JSON来把它包裹起来。[/color]
So, let’s extend our sample implementation a bit:
[color=darkred]现在,让我们来扩展一下我们的例子,让它更加实际一点:[/color]
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
[color=darkred]再一次说明,这是一个例子,但它确实向我们展示了(至少我认为是)它能轻而易举的实现RESTful接口。[/color]
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.
[color=darkred]所以,这就是它。我非常的自信的说,我已经把这些解释的非常清楚。因此,我就不再赘述你如何具体实现它。[/color]
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.
[color=darkred]在一个真实的MVC应用中,也许你想要做的就是为你的每个接口创建一个单独的控制器。例如,利用上面的东西,我们可以创建一个UserRestController控制器,这个控制器有四个方法,分别为:get(), put(), post(), 和 delete()。接口控制器将会查看请求类型然后决定哪个方法会被执行。这个方法会再使用工具来处理请求,处理数据,然后使用工具发送响应。[/color]
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.
[color=darkred]你也许会比现在更进一步,把你的接口控制器和数据模型抽象出来,而不是明确的为每一个数据模型创建控制器,你可以给你的接口控制器添加一些逻辑,先去查找一个明确定义好的控制器,如果没有,试着去查找一个已经存在的模型。例如:网址"api/user/1"将会首先触发查找一个叫user的最终控制器,如果没有,它会查找应用中叫user的模型,如果找到了,你可以写一个自动化的方法来自动处理所有请求这个模型的请求。[/color]
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.
[color=darkred]再进一步,你可以建立一个通用的"list-all"方法,就像上面一段中的例子一样。假定你的url是"api/usrs",接口控制器首先会查找叫users的控制器,如果没有找到,确认users是复数,把它变成单数,然后查找一个叫user的模型,如果找到了,加载一个用户列表然后把他们发送出去。[/color]
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):
[color=darkred]最后,你可以给你的接口添加简单的身份验证。假定你仅仅希望适当的验证访问你的接口的用户,那么,你可以在处理请求的方法中添加类似于下面的一些代码(借用我的一个现有应用,因此有一些常量和变量在这个代码片段里面并没有被定义):[/color]
// figure out if we need to challenge the user
if(empty($_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!
[color=darkred]非常酷,对吧?通过少量的代码和一些智能的逻辑,你可以非常快速的给你的应用添加全功能的REST接口。我并不仅仅是支持这个概念,我已经在我个人的框架里面实现了这些东西,而这些仅仅花费了半天的时间,然后再花费半天时间添加一些非常酷的东西。如果你(读者)对我最终的实现感兴趣,请在评论中留言,我会非常乐趣和你分享它。同时,如果你有什么比较酷的想法,也欢迎通过评论和我进行分享。如果我足够喜欢它,我会邀请你在这里发表自己的文章。[/color]
Until next time…
UPDATE: The much-requested follow-up to this article has been posted: Making RESTful Requests in PHP
[color=darkred]更新:这篇文章的下一篇已经发表了,见:[url=http://www.gen-x-design.com/archives/making-restful-requests-in-php/]Making RESTful Requests in PHP,使用PHP发送RESTfull请求[/url][/color]