For the Google Summer of Code 2014, I was selected for a project to create a REST API for ATutor. ATutor has hundreds of thousands of lines of code, yet is written in core PHP. Introducing a PHP router class for the API was necessary, but we needed something unintrusive. In this post, we discuss the essential parts of the project. For this post, all code examples would correspond to my fork of ATutor’s repository (links to files will be provided whenever necessary).
在Google Summer of Code 2014中,我被选中参加一个为ATutor创建REST API的项目。 ATutor有成千上万的代码行,但使用核心PHP编写。 为该API引入一个PHP路由器类是必要的,但是我们需要一些非侵入性的东西。 在本文中,我们讨论了项目的关键部分。 对于本篇文章,所有代码示例将与我的ATutor存储库的分支相对应(必要时将提供文件链接)。
Note – Google Summer of Code is a program where students all around the world can participate in open source projects of mentoring organizations. Google organizes the program and pays the stipends, but the students are not employed by Google at any point during the program.
注意– Google Summer of Code是一个计划,全世界的学生都可以参与到指导组织的开源项目中。 Google会组织该计划并支付津贴,但是在计划进行期间的任何时候都不会雇用学生。
The first step in the process was to create or write a PHP class to perform the routing. After considering a few options, we decided to go with Toro, a the light weight PHP router. It performs just the routing – nothing more, nothing less. The syntax is pretty intuitive and you will get started in minutes.
该过程的第一步是创建或编写PHP类来执行路由。 考虑了一些选择之后,我们决定选择Toro ,这是一款轻量级PHP路由器。 它仅执行路由-仅此而已。 语法非常直观,您将在几分钟内开始使用。
Toro is RESTful- it has support for the standard HTTP methods- GET
, POST
, PUT
and DELETE
. There is support for JSON based requests too. All of this is packed in a 120 odd line file.
Toro是RESTful的-它支持标准的HTTP方法GET
, POST
, PUT
和DELETE
。 也支持基于JSON的请求。 所有这些都打包在120个奇数行文件中。
Before we proceed, one more step was to configure the server to redirect all requests to the router. This can be performed by adding an .htaccess file in Apache, or changing the configuration file of Nginx. This step of server configuration is explained on the README of Toro’s GitHub repository.
在我们继续之前,还有一个步骤是配置服务器以将所有请求重定向到路由器。 这可以通过在Apache中添加.htaccess文件或更改Nginx的配置文件来执行。 Toro的GitHub存储库的自述文件中说明了服务器配置的这一步骤。
After you have successfully configured your web server, you just need to include the Toro.php
file to perform the routing. Toro matches the incoming URI pattern and sends the request over to a handler for processing. Let us look at the simplest ‘Hello World’ program (from the Toro official site).
成功配置Web服务器后,只需包含Toro.php
文件即可执行路由。 Toro匹配传入的URI模式,并将请求发送到处理程序进行处理。 让我们看一下最简单的“ Hello World”程序(来自Toro官方网站 )。
class MainHandler {
function get() {
echo "Hello, world";
}
}
Toro::serve(array(
"/" => "MainHandler",
));
There are two blocks of code, which seem pretty intuitive. You provide an associative array to Toro to serve, with the keys matching URL patterns and values containing the handler classes. Toro tries to match the request URI with this array and executes the corresponding handler, depending on the HTTP method. In our example, you will get the desired output of “Hello, World” if you send a GET request to “/” (or the root directory). For POST
, PUT
and DELETE
requests, we define functions post()
, put()
and delete()
, respectively in the handler classes.
有两段代码,看起来很直观。 您可以为Toro提供一个关联数组,以与URL模式和包含处理程序类的值匹配的键一起使用。 Toro尝试将请求URI与该数组匹配,并根据HTTP方法执行相应的处理程序。 在我们的示例中,如果将GET请求发送到“ /”(或根目录),则将获得所需的输出“ Hello,World”。 对于POST
, PUT
和DELETE
请求,我们分别在处理程序类中定义函数post()
, put()
和delete()
。
Toro gives you the freedom to code the way you like. You could write all the handlers in the same index.php
file and a long list of items in the associative array for Toro to serve. However, the ideal was is to separate the logic into semi-projects and have their own directories, files for handlers and URLs. In index.php
, you could include all those files and use array_merge($urls1, $urls2, ...)
to provide the final array of URLs to Toro.
Toro使您可以自由地以自己喜欢的方式进行编码。 您可以在同一个index.php
文件中编写所有处理程序,并在关联数组中编写一长串项目供Toro服务。 但是,理想的做法是将逻辑分为半项目,并拥有自己的目录,处理程序文件和URL。 在index.php
,您可以包括所有这些文件,并使用array_merge($urls1, $urls2, ...)
为Toro提供最终的URL数组。
Theoretically, you could kept all your code (handlers, routes, helper classes and functions) in the same page, but that would kill the readability. For the sake of simplicity, it’s imperative that we separate the handlers, routes and helper classes and functions. In fact, a good practice is to create separate directories for the different apps that you are going to create in the project, each having urls.php
and router_classes.php
. Additional files like tests.php
or helper_functions.php
may also be created depending on your project.
从理论上讲,您可以将所有代码(处理程序,路由,帮助程序类和函数)保存在同一页面中,但这会降低可读性。 为了简单起见,必须将处理程序,路由以及帮助程序类和函数分开。 实际上,一个好的做法是为要在项目中创建的不同应用程序创建单独的目录,每个目录都具有urls.php
和router_classes.php
。 根据您的项目,也可能会创建其他文件,例如tests.php
或helper_functions.php
。
In the index.php
file, we include the files containing the routes and use array_merge
to merge all the URLs into one array for Toro. Here’s how it looks.
在index.php
文件中,我们包含了包含路由的文件,并使用array_merge
将所有URL合并为一个Toro数组。 这是它的样子 。
Toro::serve(array_merge(
$base_urls,
$course_urls,
$student_urls,
$instructor_urls,
// Include the url array of your app
$boilerplate_urls
));
We have seen that it is useful to create separate urls.php
files, but how should one such file be structured?
我们已经看到创建单独的urls.php
文件很有用,但是如何构造这样一个文件呢?
One small fact that you might notice is that the URLs of every app start with the app’s name. A prefix is therefore common to all the URLs of a single app. You should define a prefix for an app and append it to URLs so that you don’t have to repeat it. Here is how a typical urls.php
in my project looks like.
您可能会注意到的一个小事实是,每个应用程序的URL均以应用程序名称开头。 因此,前缀是单个应用程序的所有URL通用的。 您应该为应用程序定义一个前缀并将其附加到URL,这样就不必重复它。 这是我项目中典型的urls.php
样子。
$instructor_url_prefix = "/instructors";
$instructor_base_urls = array(
"/" => "Instructors",
"/:number/" => "Instructors",
"/:number/courses/" => "InstructorCourses",
"/:number/courses/:number" => "InstructorCourses",
"/:number/courses/:number/instructors" => "CourseInstructorList",
"/:number/courses/:number/students" => "CourseEnrolledList"
);
$instructor_urls = generate_urls($instructor_base_urls, $instructor_url_prefix);
You should notice that I have occasionally added a :number
to my URL pattern. A :number
, :string
or :alpha
tells Toro to match the pattern to a number, string or alphanumeric characters to the incoming requests. For instance, /instructors/5/
would match to the second URL and /instructors/5/courses/6/
would match to the fourth.
您应该注意到,我偶尔在URL模式中添加了:number
。 :number
, :string
或:alpha
告诉Toro将模式与数字,字符串或字母数字字符匹配,以适应传入的请求。 例如, /instructors/5/
将与第二个URL匹配,而/instructors/5/courses/6/
将与第四个URL匹配。
For the purpose of prepending a prefix to all the URLs, I have created a custom function generate_urls
. You can have a look at it under my core helper functions.
为了在所有URL之前添加前缀,我创建了一个自定义函数generate_urls
。 您可以在我的核心帮助器功能下进行查看。
Any API that you develop must be able to authenticate users. One way to do so is through the use of tokens. Here are a few things to note about tokens for user authentication.
您开发的任何API都必须能够对用户进行身份验证。 一种方法是使用令牌。 以下是有关用户身份验证令牌的一些注意事项。
When you develop an API, you would notice after the implementation of the first few functions that the process for every API call is quite similar. In the simplest of terms, every API call checks if a token is valid, checks if the user has access to that resource, performs an SQL query (or more) and prints a result. A backbone function therefore can therefore be written for executing all such queries. Here are features of such a function.
开发API时,您会在实施前几个功能后注意到,每个API调用的过程都非常相似。 用最简单的术语来说,每个API调用都会检查令牌是否有效,检查用户是否有权访问该资源,执行SQL查询(或更多)并打印结果。 因此,可以编写骨干函数来执行所有此类查询。 这是此功能的功能。
The function that I developed for my project can be found on GitHub.
我为项目开发的功能可以在GitHub上找到 。
Imagine you are making an API where you need to retrieve users in your system. Look at the following URL types.
想象一下,您正在制作一个需要在系统中检索用户的API。 查看以下URL类型。
"/members/" => "Class1",
"/members/:number/" => "Class2",
The first URL gives you a list of members, whereas the second gives you the details of a certain member. The SQL queries that would be executed in these two cases are going to be very similar — with a simple WHERE
clause in the latter case. We can reuse the same class, MySameClass
for both the URLs, which can be defined as follows —
第一个URL为您提供成员列表,而第二个URL为您提供特定成员的详细信息。 在这两种情况下将执行SQL查询将非常相似-在后一种情况下使用简单的WHERE
子句。 我们可以为两个URL重用相同的类MySameClass
,它们可以定义如下:
class MyClass {
function get($member_id) {
...
if ($member_id) {
$query = $connection->prepare("SELECT col1, col2, col3 FROM table_name WHERE id = :member_id");
$query->bindParam(':member_id', $member_id, PDO::PARAM_INT);
}
// Execute the query or do something else
$query->execute();
$result = $query->fetchAll();
...
}
}
The simple logic here is that in case of the first URL, $member_id
in my function would be empty, whereas in the latter case, the WHERE
clause is appended.
这里的简单逻辑是,在第一个URL的情况下,我函数中的$member_id
将为空,而在后一种情况下,将附加WHERE
子句。
In this post, I have discussed the basic idea of creating an API in PHP using Toro as the router. I focused on a few areas that might challenge the efficiency and readability of the code that you write. What I have have talked about may not be the only way of doing certain things, and you might have even better ideas of performing the same action.
在本文中,我讨论了使用Toro作为路由器在PHP中创建API的基本思想。 我专注于可能挑战您编写的代码的效率和可读性的几个领域。 我所谈论的可能不是做某些事情的唯一方法,并且您可能对执行相同的操作有更好的想法。
If you have better ideas about what we have discussed here — from shorter to more efficient ways of doing certain tasks, feel free to comment below.
如果您对此处讨论的内容有更好的想法-从较短到更高效的方式执行某些任务,请在下面随意评论。