We interrupt our regularly scheduled series to bring you this article about Representational State Transfer, better known as REST. Yes, this is David Shirey’s series on the subject, but he’s graciously invited me to write this installment and share some insight into supporting REST requests in PHP.
我们打断我们定期安排的系列,为您带来有关代表性状态转移(又称为REST)的文章。 是的,这是David Shirey的系列文章,但是他很荣幸地邀请我撰写本期文章,并分享一些对在PHP中支持REST请求的见解。
In the first article of his series, David explained how REST is more than an architectural pattern. It’s a set of guiding principles that, if followed, can help you write scalable and robust applications. In the following articles, David will resume the discussion by looking at REST from the client-side of the equation. In this article though I’d like to focus on the server-side. You’ll learn how to shift your thinking from the action-focused mindset that’s prevalent in web development today to a more RESTful, resource oriented, approach, and see one way to structure your code and route URI requests in PHP.
在他的系列的第一篇文章中,David解释了REST不仅是一种架构模式。 这是一组指导原则,可以遵循这些指导原则来帮助您编写可伸缩且健壮的应用程序。 在以下文章中,David将通过从等式的客户端查看REST来继续讨论。 不过,在本文中,我想重点介绍服务器端。 您将学习如何将思想从当今Web开发中流行的以行动为中心的思维方式转变为一种更具RESTful,面向资源的方法,并了解一种在PHP中构建代码和路由URI请求的方法。
The first thing you should do after making the decision to develop your application following the REST principles is to brainstorm a list of resources that will be made available.
在决定遵循REST原则开发应用程序之后,您应该做的第一件事就是集思广益将要使用的资源列表。
An all-to-common way of thinking about web applications focuses on functionality. For example, a restaurant rating system written in PHP might have an editRestaurant.php script that presents a form to update an entry, and the form might submit to a script named saveRestaurant.php. Notice how the actions performed by the scripts are exposed as part of their file names. REST on the other-hand focuses on resources… in this case the restaurant post itself.
关于Web应用程序的一种通用思考方式着眼于功能。 例如,用PHP编写的饭店评分系统可能具有editRestaurant.php脚本,该脚本呈现用于更新条目的表单,并且该表单可能会提交到名为saveRestaurant.php的脚本。 请注意,脚本执行的操作如何作为文件名的一部分公开。 另一方面,REST专注于资源……在这种情况下,餐厅发布本身。
A user will still be able to create, view, and update resturant entries in a REST-based rating system, but just not through links like example.com/viewRestaurant.php?id=42. Instead, the post would be created by sending an HTTP POST request to example.com/restaurant, viewed by an HTTP GET to example.com/restaurant/42, and updated by an HTTP PUT to example.com/restaurant/42. Regardless if they are thick-client apps, web apps, mobile apps, or whatever else, all programs allow the users to accomplish tasks (actions). RESTful applications are no different, it’s just the core actions are pre-defined by the protocol in use. And since the actions are already defined, you’re free to focus on what the target resource of those actions will be (i.e. the restaurant).
用户仍将能够在基于REST的评分系统中创建,查看和更新餐厅条目,但不能通过example.com/viewRestaurant.php?id=42链接进行。 相反,该帖子将通过以下方式创建:发送HTTP POST请求到example.com/restaurant ,通过HTTP GET查看到example.com/restaurant/42 ,并通过HTTP PUT更新到example.com/restaurant/42 。 无论它们是胖客户端应用程序,Web应用程序,移动应用程序还是任何其他应用程序,所有程序都允许用户完成任务(动作)。 RESTful应用程序没有什么不同,只是核心操作由使用的协议预先定义。 而且,由于已经定义了动作,因此您可以自由地专注于这些动作的目标资源(即餐厅)。
At this point you should only be brainstorming the resources that are exposed to the user of the application, not necessarily the objects that make up your app. Continuing with the rating example, the system could have a User object to authenticate the application users, Restaurant and Comment objects for the restaurant and user-supplied comments, and maybe even a RestApp container object with static convenience methods (Restaurant App, REST App, see my pun there?). A user wouldn’t interact with these objects himself, though so they’re not categorized as resources.
在这一点上,您仅应集思广益,以展示给应用程序用户的资源,而不一定是组成应用程序的对象。 继续进行评分示例,系统可以具有一个User对象,以验证应用程序用户的身份,为餐厅和用户提供的评论验证Restaurant和Comment对象,甚至还可以使用带有静态便捷方法的RestApp容器对象(Restaurant App,REST App,在那看到我的双关语?)。 用户不会自己与这些对象进行交互,因此不会将它们归类为资源。
The restaurant maintainer wants to write up information about the eatery and approve all the wonderful comments left by users; the reader wants to read reviews and leave comments. If you’re a Agile Development shop then you can probably glean a list of resources right from your user stories. Care to take a guess what the resources will be? Yep… you guessed it! A restaurant and a comment!
饭店维护人员希望撰写有关饭店的信息,并批准用户留下的所有精彩评论; 读者想阅读评论并发表评论。 如果您是一家敏捷开发商店,则可以直接从用户案例中收集资源列表。 小心猜测资源将是什么? 是的...你猜对了! 餐厅和评论!
Most REST-focused code will probably employ some sort of resource mapping. This lets you have clean resource URLs and hide the actual architecture and implementation from your end users, a very good thing indeed!
大多数针对REST的代码可能会采用某种资源映射。 这样一来,您便拥有了干净的资源URL,并向最终用户隐藏了实际的体系结构和实现,这确实是一件好事!
A common practice is to route all requests to a central index or controller file, and then invoke the appropriate code logic from there depending on the requested resource and any parameters or properties of the request. If you’re using a LAMP stack, routing requests to a single entry point is as easy as enabling mod_rewrite, allowing htaccess overrides, and placing a .htaccess file in your web directory with the following:
通常的做法是将所有请求路由到中央索引或控制器文件,然后根据请求的资源以及请求的任何参数或属性从那里调用适当的代码逻辑。 如果您使用的是LAMP堆栈,则将请求路由到单个入口点就像启用mod_rewrite一样简单,允许htaccess覆盖,并使用以下命令将.htaccess文件放置在您的Web目录中:
RewriteEngine on RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ /index.phpAll resources that map to an existing file (such as images, CSS files, etc.) are served directly by Apache. Otherwise the index.php file will be used.
映射到现有文件的所有资源(例如图像,CSS文件等)均由Apache直接提供。 否则,将使用index.php文件。
For the sake of illustration, let’s say you have index.php, css/style.css, and img/logo.png in your web root directory. Apache would serve css/style.css in response to a request for example.com/css/style.css; img/logo.png would be served for example.com/img/logo.png; and Apache would invoke index.php to handle a request for example.com/restaurant and example.com/restaurant/42.
为了说明起见,假设您在Web根目录中具有index.php , css/style.css和img/logo.png 。 Apache将响应对example.com/css/style.css的请求提供css/style.css ; img/logo.png将作为example.com/img/logo.png ; 并且Apache将调用index.php来处理对example.com/restaurant和example.com/restaurant/42的请求。
The index.php file needs to know through which URL it was reached through know what the user was really requesting, what PHP code in your application should be executed for the particular request, and what do to when the request cannot be satisfied.
index.php文件需要知道通过哪个URL到达,它知道用户真正在请求什么,应针对特定请求执行应用程序中PHP代码,以及在无法满足请求时该怎么做。
The natural “hierarchy” that I’ve subtly used in my example URLs up until now (like www.example.com/restaurant/42) follows a pretty common pattern you’ll see in REST-based applications around the ‘net. The path of the URL (/restaurant/42) is easily parsable and understood as a request for a restaurant resource, and specifically the instance of a restaurant resource identified by the ID 42. Thus, the codebase could have a Restaurant class, an appropriate instance could be instantiated, and manipulated accordingly. For example:
到目前为止,我一直在示例URL中巧妙使用的自然“层次结构”(例如www.example.com/restaurant/42 )遵循一种非常常见的模式,您将在基于NET的基于REST的应用程序中看到这种模式。 URL的路径( /restaurant/42 )易于解析,可以理解为对餐厅资源的请求,尤其是ID 42标识的餐厅资源实例。因此,代码库可以具有一个Restaurant类,一个适当的实例可以实例化,并进行相应的操作。 例如:
<?php // assume autoloader available and configured $path = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH); $path = trim($path, "/"); @list($resource, $params) = explode("/", $path, 2); $resource = ucfirst(strtolower($resource)); $method = strtolower($_SERVER["REQUEST_METHOD"]); $params = !empty($params) ? explode("/", $params) : array(); if (class_exists($resource)) { try { $resource = new $resource($params); $resource->{$method}(); } catch (Exception $e) { header("HTTP/1.1 500 Internal Server Error"); } } else { header("HTTP/1.1 404 File Not Found"); }This simple example illustrates one way of parsing the URL’s path into a resource and its possible parameters, and then using that information to create an instance of the class with the same name as the resource. The parameters from the path are injected into the object through its constructor, and then the responsibility for handling the request is handed off to the object’s method by the same name as the HTTP method used to request it.
这个简单的示例说明了一种方法,该方法将URL的路径解析为资源及其可能的参数,然后使用该信息创建与该资源同名的类的实例。 路径中的参数通过其构造函数注入到对象中,然后通过与用于请求该请求的HTTP方法相同的名称将处理请求的责任移交给该对象的方法。
One drawback of the above code however is that you are limited to a shallow namespace hierarchy of objects. If you wish to pursue a dynamic dispatching mechanism like this, I suggest parsing the URI from right-to-left and testing whether a suitable class exists. This should allow you to have more depth in the hierarchy.
但是,以上代码的一个缺点是您仅限于对象的浅名称空间层次结构。 如果您希望采用这种动态分配机制,则建议从右到左解析URI,并测试是否存在合适的类。 这应该使您可以更深入地了解层次结构。
The routing strategy you choose will affect how resource objects should be structured. The preceding routing code assumes the class will have post(), get(), put(), head(), etc. methods defined. Since all possible resource objects must follow this convention, it would be a good idea to define a master interface, a contract that promises an object will offer a specific API (and thus hopefully specific functionality).
您选择的路由策略将影响资源对象的结构。 前面的路由代码假定该类将定义post() , get() , put() , head()等方法。 由于所有可能的资源对象都必须遵循此约定,因此定义一个主接口是一个好主意,承诺一个对象的协定将提供特定的API(并因此希望具有特定的功能)。
You could also write an abstract class with a partial implementation of the standard methods. An abstract class cannot be instantiated on it’s own, and must be extended by a concrete class, but it helps you to avoid repetitive method definitions. With a little bit of reflection, a very powerful and robust base for other resource objects to build upon can be put in place.
您也可以编写带有标准方法的部分实现的抽象类。 抽象类不能单独实例化,而必须由具体类扩展,但是它可以帮助您避免重复的方法定义。 稍加思考,就可以为其他资源对象建立非常强大的基础。
<?php abstract class Resource { protected static $httpMethods = array("GET", "POST", "HEAD", "PUT", "OPTIONS", "DELETE", "TRACE", "CONNECT"); protected $params; public function __construct(array $params) { $this->params = $params; } protected function allowedHttpMethods() { $myMethods = array(); $r = new ReflectionClass($this); foreach ($r->getMethods(ReflectionMethod::IS_PUBLIC) as $rm) { $myMethods[] = strtoupper($rm->name); } return array_intersect(self::$httpMethods, $myMethods); } public function __call($method, $arguments) { header("HTTP/1.1 405 Method Not Allowed", true, 405); header("Allow: " . join($this->allowedHttpMethods(), ", ")); } }The HTTP/1.1 spec says that a 405 code should be returned to the client if an unsupported request method is used, and that a list of valid methods should be returned as well. Suppose the example.com/restaurant resource should support GET and POST options, but obviously not ones like DELETE. The response to a DELETE request should look like this:
HTTP / 1.1规范指出,如果使用了不受支持的请求方法,则应将405代码返回给客户端,并且还应返回有效方法的列表。 假设example.com/restaurant资源应支持GET和POST选项,但显然不支持DELETE这样的选项。 对DELETE请求的响应应如下所示:
HTTP/1.1 405 Method Not Allowed Allow: GET, POSTThe abstract class uses the magic method __call() to catch any undefined object methods, and then uses reflection to determine the list of HTTP methods supported by the concrete class. The concrete class must have a public method by the same name as the HTTP method it handles for it to work… a palatable convention. The classes only need to implement such function/methods for the HTTP request methods they intend to support, and the error handling for unsupported requests is left up to the functionality encapsulated within the abstract parent.
抽象类使用魔术方法__call()来捕获任何未定义的对象方法,然后使用反射来确定具体类支持的HTTP方法的列表。 具体类必须具有与其处理的HTTP方法同名的公共方法,这是可口的约定。 这些类仅需要为它们打算支持的HTTP请求方法实现此类功能/方法,而对不支持的请求的错误处理则由封装在抽象父级中的功能来决定。
A sample Restaurant class that extends Resource might then look like this:
扩展Resource示例Restaurant类可能如下所示:
<?php class Restaurant extends Resource { public function __construct($params) { parent::__construct($params); } public function get() { // logic to handle an HTTP GET request goes here // ... } public function post() { // logic to handle an HTTP POST request goes here // ... } }While REST is awesome, a purely REST-full approach may not be practical given user expectations on the web. The world has grown up with a forced RPC-style paradigm on top of HTTP, and we’re stuck with it now; that’s what people expect. They want a pretty login page, log in/out functionality, etc. which makes no sense in the world of REST. Remember, all information the server needs to satisfy a request must accompany that request; server-side sessions and tracking logins/logouts don’t fit the REST paradigm.
尽管REST很棒,但考虑到用户对Web的期望,纯REST完全的方法可能不切实际。 在基于HTTP的强制RPC样式范式的背景下,世界已经成长起来,我们现在仍然坚持下去。 这就是人们的期望。 他们想要漂亮的登录页面,登录/注销功能等,这在REST的世界中毫无意义。 请记住,服务器满足请求所需的所有信息都必须伴随该请求。 服务器端会话和跟踪登录/注销跟踪不符合REST范式。
That’s not to say your REST-based projects have to be insecure; strategies exist like HTTP AUTH, DIGEST AUTH, etc. which permit user credentials to accompany the request. I’m merely saying its best to understand the benefits and the limitations of REST and apply it to projects that make the most sense. Instead of websites, REST-full systems tend to be web services like the Atom Publishing Protocol for publishing blog posts, or APIs like the one exposed by CouchDB.
这并不是说您基于REST的项目必须不安全; 存在诸如HTTP AUTH,DIGEST AUTH等策略,这些策略允许用户凭据伴随请求。 我只是在说说最好的方法是了解REST的优点和局限性,并将其应用于最有意义的项目。 完全基于REST的系统倾向于使用Web服务(例如用于发布博客帖子的Atom发布协议)或API(例如CouchDB公开的API)之类的Web服务。
We now return you to your regularly scheduled series, already in progress. :)
现在,我们将返回到您正在进行的定期排定的系列。 :)
Image via littlesam / Shutterstock
图片来自littlesam / Shutterstock
翻译自: https://www.sitepoint.com/rest-can-you-do-more-than-spell-it-2/
相关资源:jdk-8u281-windows-x64.exe