使用Toro APIfy您的旧版应用程序

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会组织该计划并支付津贴,但是在计划进行期间的任何时候都不会雇用学生。

使用Toro进行Web路由 (Web Routing with Toro)

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方法GETPOSTPUTDELETE 。 也支持基于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存储库自述文件中说明了服务器配置的这一步骤。

你好世界计划 (Hello World program)

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”。 对于POSTPUTDELETE请求,我们分别在处理程序类中定义函数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数组。

将URL与其余逻辑分开 (Separating URLs from rest of the logic)

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.phprouter_classes.php 。 根据您的项目,也可能会创建其他文件,例如tests.phphelper_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
));

URL前缀的需要 (The need of a URL Prefix)

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 。 您可以在我的核心帮助器功能下进行查看。

用户认证 (User authentication)

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都必须能够对用户进行身份验证。 一种方法是使用令牌。 以下是有关用户身份验证令牌的一些注意事项。

  • Generate token on successful login — In this case, I generate a token by taking the hash of a combination of the user name, timestamp and a random number.

    成功登录时生成令牌-在这种情况下,我通过获取用户名,时间戳和随机数的组合的哈希值来生成令牌。
  • Set the validity of the token by defining an expiry timestamp after which the token would be unusable.

    通过定义过期时间戳记来设置令牌的有效性,此后令牌将无法使用。
  • The token is present in the header of every request.

    令牌出现在每个请求的标头中。
  • Modify the validity of the token on each successful request and update the expiry timestamp.

    在每个成功的请求上修改令牌的有效性,并更新到期时间戳。
  • If the token has expired, delete the entry or perform an equivalent task (like set an option in the database that mentions the token as expired).

    如果令牌已过期,请删除条目或执行等效任务(例如在数据库中设置一个将令牌提到已过期的选项)。

开发骨干功能 (Developing a backbone function)

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查询(或更多)并打印结果。 因此,可以编写骨干函数来执行所有此类查询。 这是此功能的功能。

  • Check the validity of the token and determine which user corresponds to that token

    检查令牌的有效性,并确定哪个用户对应于该令牌
  • Check if the resource being accessed is available to the user; raise an error in all other cases.

    检查正在访问的资源是否对用户可用; 在所有其他情况下都会引发错误。
  • In case of creating objects, return the ID of the newly created object.

    如果创建对象,则返回新创建的对象的ID。
  • In case of requests to modify or delete objects, check if the object exists before deleting or modifying it.

    如果请求修改或删除对象,请在删除或修改对象之前检查该对象是否存在。
  • Print the response and log the request.

    打印响应并记录请求。

The function that I developed for my project can be found on GitHub.

我为项目开发的功能可以在GitHub上找到

重用类以检索相似的对象 (Re-using classes for retrieving similar objects)

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子句。

最后的想法 (Final thoughts)

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.

如果您对此处讨论的内容有更好的想法-从较短到更高效的方式执行某些任务,请在下面随意评论。

翻译自: https://www.sitepoint.com/apify-legacy-app-toro/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值