jacwright/restserver

超轻量级的 PHP REST 服务器

v1.2.0 2017-11-26 23:16 UTC

README

A PHP REST server for providing a very light-weight REST API. Very easy to set up and get going. Independent from other libraries and frameworks. Supports HTTP authentication.

PHP 简单 REST 服务器

在使用了 Zend Framework 构建了几个 RESTful 服务之后,我决定创建一个极其简单的 REST 服务器,这样我就可以跳过所有不需要的功能,以及随 Zend Framework MVC 一起提供的 大量类。还有一些有用的功能可以添加(例如 XML 支持),但总的来说,我对我的成果非常满意。

我的解决方案,RestServer,目前是一个 JSON REST 服务器。添加对 XML 或其他格式的支持应该很简单,但需要对对象在 XML 中的样子有所假设(XML-RPC 风格,自定义 XML 格式等)。首先,我们将看看您编写的处理请求的类,然后我们将看看如何在 index.php 文件中将它们组合在一起。

REST 控制器

RestServer 类假定您正在使用 URL 重写,并查看请求中的 URL 以映射到必要的操作。将请求从 URL 映射到类方法的映射全部在类的文档注释中。以下是一个处理一些用户操作的类的示例:

class TestController
{
    /**
     * Returns a JSON string object to the browser when hitting the root of the domain
     *
     * @url GET /
     */
    public function test()
    {
        return "Hello World";
    }

    /**
     * Logs in a user with the given username and password POSTed. Though true
     * REST doesn't believe in sessions, it is often desirable for an AJAX server.
     *
     * @url POST /login
     */
    public function login()
    {
        $username = $_POST['username'];
        $password = $_POST['password'];
        // validate input and log the user in
    }

    /**
     * Gets the user by id or current user
     *
     * @url GET /users/$id
     * @url GET /users/current
     */
    public function getUser($id = null)
    {
        if ($id) {
            $user = User::load($id); // possible user loading method
        } else {
            $user = $_SESSION['user'];
        }

        return $user; // serializes object into JSON
    }

    /**
     * Saves a user to the database
     *
     * @url POST /users
     * @url PUT /users/$id
     */
    public function saveUser($id = null, $data)
    {
        // ... validate $data properties such as $data->username, $data->firstName, etc.
        $data->id = $id;
        $user = User::saveUser($data); // saving the user to the database
        return $user; // returning the updated or newly created user object
    }

    /**
     * Gets user list
     *
     * @url GET /users
     */
    public function listUsers($query)
    {
        $users = array('Andra Combes', 'Valerie Shirkey', 'Manda Douse', 'Nobuko Fisch', 'Roger Hevey');
        if (isset($query['search'])) {
          $users = preg_grep("/$query[search]/i", $users);
        }
        return $users; // serializes object into JSON
    }
}

让我们逐步分析上述 TestController 类,来讨论展示的功能。首先,我们将看看 test 方法。您会注意到在 docblock 中出现了一种新的文档注释标签。@url 将 URL 映射到下面的方法,其形式为:

@url <请求方法> <URL>

在这个特定的例子中,当有人对 http://www.example.com/(假设 example.com 是我们的服务所在的位置)执行 GET 操作时,它将打印出

"Hello World"

这是 JSON 中字符串的有效表示。

接下来是下一个方法,login,我们看到 @url 将对 http://www.example.com/login 的任何 POST 操作映射到 login 方法。从常规的 Web 类型的 POST 中获取数据与任何 PHP 应用程序相同,允许您使用自己的验证或其他框架与这个 REST 服务器一起使用。如果需要,也可以保持会话。尽管保持会话不是真正的 REST 风格,但通常我们想要的只是一个 REST 服务器来为我们的 AJAX 应用程序提供数据,使用会话可能更容易一些。

接下来是我们的getUser方法(你会发现我的方法命名并不重要,因为我们的@url指令定义了哪些URL映射到方法)。这里有几个要点。首先,这个方法有几个@url映射。其次,第一个URL映射中有一个奇怪的/$id。RestServer将任何:keyword占位符视为URL中的通配符,并将该URL部分传递到方法中具有相同名称的参数。在这个例子中,当访问http://www.example.com/users/1234时,$id将等于1234。当访问http://www.example.com/users/current时,$id将等于null。只要参数与占位符(:id$id:username$username)同名,参数的顺序无关紧要。如果你有多个URL映射,其中并非所有都需要参数,那么请确保你的参数是可选的($id = null)。否则,你会收到一个错误,告诉你没有传递所需的参数。

getUser中还有一个需要注意的最后一点,这个方法仅仅返回一个User对象。它被序列化为JSON(或可能是XML)并打印出来供应用程序消费。

接下来是saveUser。你在这里又看到了多个URL映射。这一次,它们还有不同的HTTP方法(POST和PUT)用于创建和更新用户。这里的新特点是$data变量。这是一个特殊的参数,它将包含发送到服务器的任何POST或PUT的内容。这与常规的web POST不同,因为它不仅可以是键值对,还可以像JSON一样强大,发送复杂对象。例如,常规web POST的正文,比如登录请求,可能看起来像这样

username=bob&password=supersecretpassw0rd

但是,为saveUser方法POST一个新用户对象可能看起来像这样

{ "username": "bob", "password": "supersecretpassword", "firstName": "Bob", "lastName": "Smith" }

所以你可以允许除了常规的web POST外,还可以POST JSON。

最后,我们来到listUsers方法。它和test方法一样简单,但新增了$query参数。这个特殊参数可以用来读取查询字符串,并将查询字符串参数作为关联数组持有。例如,如果客户端使用URL /users?search=Manda请求这个API,那么$query参数将持有[search => Manada]

我把处理请求的这些类称为Controllers。它们可以完全自包含,具有URL映射、数据库配置等,因此你可以将它们无缝地添加到其他RestServer服务中。

REST index.php

为了启动整个服务器,你需要创建一个index.php文件,将URL重写定向到它(这是一个其他地方可以学习的主题),创建RestServer并添加处理请求的控制器类。RestServer将使用APC或文件缓存URL映射以加快请求速度。如果你使用自动加载和这个缓存,你不必在每次请求时都加载每个控制器文件,只需加载所需的那个。这个缓存只在生产模式下运行。下面是一个示例index.php文件

spl_autoload_register(); // don't load our classes unless we use them

$mode = 'debug'; // 'debug' or 'production'
$server = new RestServer($mode);
// $server->refreshCache(); // uncomment momentarily to clear the cache if classes change in production mode

$server->addClass('TestController');
$server->addClass('ProductsController', '/products'); // adds this as a base to all the URLs in this class

$server->handle();

就是这样。你可以添加任意多的类。如果有冲突,后来添加的类将覆盖早期添加的重复URL映射。并且addClass的第二个参数可以是一个基础URL,它将被添加到给定类中的URL映射之前,这允许你更模块化。

您可以通过查看RestServer类,复制并用于您自己的目的。该项目遵循MIT许可。即将添加的功能包括XML支持和HTTP认证支持。如果您使这个类更优秀,请通过留言与大家分享您的更新。我会尽量保持这个类的新功能更新。希望您喜欢!

祝您好运,如果最终使用了它,请告诉我!

更新:我为可能需要的人提供了一个示例 .htaccess 文件。它只会重写对不存在文件的请求,因此您可以在您的webroot中包含图像、CSS或其他PHP文件,它们仍然可以工作。任何会导致404的请求都将重定向到您的index.php文件。

DirectoryIndex index.php
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule ^$ index.php [QSA,L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>

认证

认证对每个应用程序都是独特的。但是,将您的认证机制集成到RestServer中很容易。只需简单地在您的Controller中添加一个名为authorize的方法,所有请求都会首先调用该方法。如果authorize()返回false,服务器将发出401未授权响应。如果authorize()返回true,则请求将继续调用正确的控制器操作。除非您在操作文档中添加@noAuth,否则所有操作都将首先运行认证(我通常将其放在@url映射之上)。

在您的认证方法中,您可以使用PHP的getallheaders函数或$_COOKIE来根据需要授权用户。这是您从数据库加载用户对象并将其设置为$this->user = getUserFromDatabase()的地方,这样操作就可以在以后被调用时访问它。

RestServer旨在将您的应用程序映射到一个REST API的简单机制。其余的细节由您决定。

跨域资源共享

出于安全原因,浏览器限制了从脚本内部发起的跨源HTTP或REST请求。因此,使用REST API的Web应用程序只能向其自己的域名发送API请求。要覆盖此限制,可以在REST index.php文件中包含以下代码来配置RestServer以允许跨域请求。

    /**
     *
     * include following lines after $server = new RestServer($mode); 
     */
     $server->useCors = true;
     $server->allowedOrigin = 'http://example.com';
     // or use array of multiple origins
     $server->allowedOrigin = array('http://example.com', 'https://example.com');
     // or a wildcard
     $server->allowedOrigin = '*';

抛出和处理错误

您可以通过抛出一个带有RestException类的异常来轻松地向您的API用户提供错误。示例

    /**
     * Gets the user by id or current user
     *
     * @url GET /users/$id
     * @url GET /users/current
     */
    public function getUser($id = null)
    {
        if ($id) {
            $user = User::load($id); // possible user loading method

            if (!$user) {
                throw new RestException(404, 'User not found');
            }

        } else {
            $user = $_SESSION['user'];
        }

        return $user; // serializes object into JSON
    }

您可以对您的REST服务如何处理错误进行控制。您可以使用$server->addErrorClass('ErrorController');添加错误控制器。此控制器可以定义名为handle401handle404的方法,以添加您自己的自定义错误处理逻辑。

安装

通过手工

cd <your project>
mkdir -p vendor/jacwright/RestServer
cd vendor/jacwright/RestServer
git clone https://github.com/jacwright/RestServer .
composer install

通过Packagist

cd <your project>
composer require 'jacwright/restserver:dev-master'