免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 4552 | 回复: 2
打印 上一主题 下一主题

使用PHP创建一个REST API(Create a REST API with PHP) [复制链接]

论坛徽章:
0
1 [报告]
发表于 2011-10-13 15:54 |只看该作者
Php代码

  1. public static function 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. public static function 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.                 }else if ($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. }
复制代码

论坛徽章:
0
2 [报告]
发表于 2011-10-13 15:55 |只看该作者
回复 1# 听老歌


   
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(empty($_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
更新:这篇文章的下一篇已经发表了,见:Making RESTful Requests in PHP,使用PHP发送RESTfull请求
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP