zozlak/rest

REST 控制器和端点类

6.0.1 2023-06-05 12:55 UTC

README

Build Status Coverage Status

用于创建 REST API 的 PHP 类集合

  • zozlak\rest\HttpController 提供路由到正确的端点类,并处理错误
  • zozlak\rest\HttpEndpoint 提供端点实现的基类

特性

  • 简单明了的端点实现。
  • 内置对 HTTP 基本身份验证的支持。
  • 方便地支持大于允许的 PHP 内存 JSON 输出。
  • 自动处理 OPTION 方法。

安装

最简单的方法是使用 composer

  • 准备一个 composer.json 文件
{
    "require": {
        "zozlak/rest": "*"
    }
}
  • 获取 Composer
  • 运行 php composer.phar install
    库现在应该已安装在 vendor 目录中。
  • 通过在代码开头添加 require_once 'vendor/autoload.php'; 行来在您的代码中包含 Composer 的自动加载器。

示例用法

假设您想创建一个提供以下端点的 RESTful API

  • http://yourDomain/person 支持 GET 和 POST 方法的集合
  • http://yourDomain/person/{id} 支持 GET、PUT 和 DELETE 方法的资源
  • http://yourDomain/project 支持 GET 和 POST 方法的集合
  • http://yourDomain/project/{id} 支持 GET、PUT 和 DELETE 方法的资源

HTTP 服务器配置

首先,您可能想要配置您的 HTTP 服务器,将进入您的 API 的请求重定向到一个 PHP 文件。以下是为 Apache 服务器设置的一组示例规则(放入 .htaccess 文件或 <VirtualHost> 配置指令中):

RewriteEngine on

# handle requests for existing files and directories as usual
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]

# redirect everyting else to index.php
RewriteRule ^(.*)$ index.php [QSA]

端点类

zozlak\rest\HttpEndpoint 是实现 REST API 端点的基类。它为资源和集合端点(例如 http://yourDomain/personhttp://yourDomain/person/{id})的所有 HTTP 方法提供默认实现(发出 HTTP 501 方法未实现 错误代码)。对于资源端点的方法名称,只需遵循 HTTP 方法名称(get()put() 等)即可,对于集合端点,它们后面加上 collectiongetCollection()postCollection() 等)。OPTION 方法的默认实现检查您的类中实现了哪些方法,并相应地发出 Allow 头值(如果没有实现,则发出 404 Not Found)。

您应该从 zozlak\rest\HttpEndpoint 衍生您的类,并覆盖具有有用实现的方法。

类名应遵循最后一个 API 端点段名称(跳过 {id} 段),并将其转换为 CamelCase,例如:

  • http://yourDomain/personhttp://yourDomain/person/{id}http://yourDomain/pErSoN/{id} 将由 Person 类处理。
  • http://yourDomain/person/{id}/orderhttp://yourDomain/person/{id}/order/{id} 将由 Order 类处理。

这意味着我们必须创建两个类:PersonProject。假设您将它们的代码放入 src 目录,并遵循 PSR-4 命名规则(意味着文件名遵循类名)。

src\Person.php

<?php
namespace myRestEndpoint;

use \zozlak\rest\HttpEndpoint;
use \zozlak\rest\DataFormatter;
use \zozlak\rest\HeadersFormatter;

class Person extends HttpEndpoint {
    public function getCollection(DataFormatter $f, HeadersFormatter $h){
        $f->data('you executed a GET action on a person collection');
    }

    public function postCollectio(DataFormatter $f, HeadersFormatter $h){
        $f->data('you executed a POST action on a person collection');
    }

    public function get(DataFormatter $f, HeadersFormatter $h){
        $f->data('you executed a GET action on a person resource with id ' . $this->personId);
    }

    public function put(DataFormatter $f, HeadersFormatter $h){
        $f->data('you executed a PUT action on a person resource with id ' . $this->personId);
    }

    public function delete(DataFormatter $f, HeadersFormatter $h){
        $f->data('you executed a DELETE action on a person resource with id ' . $this->personId);
    }

}

src\Project.php

只需复制粘贴并修改 src\Person.php

index.php

<?php
namespace myRestEndpoint;

use \Throwable;
use \zozlak\rest\HttpController;

try{
    header('Access-Control-Allow-Origin: *');
    require_once 'vendor/autoload.php';
    // you should probably use autoloader but to make it simpler we will explicitely include them
    require_once 'src/Person.php';
    require_once 'src/Project.php';
    set_error_handler('\zozlak\rest\HttpController::errorHandler');
    $controller = new HttpController('myRestEndpoint');
    $controller->handleRequest();
}catch(Throwable $ex){
    HttpController::reportError($ex);
}

测试您的 API

尝试访问您的 API 端点,例如使用 curl

  • curl -i -X GET http://yourDomain/person - 200 OK "你执行了对人员集合的 GET 操作"
  • curl -i -X GET http://yourDomain/person/5 - 200 OK "你执行了对 ID 为 5 的人员资源的 GET 操作"
  • curl -i -X PUT http://yourDomain/person/5 - 200 OK "你执行了对 ID 为 5 的人员资源的 PUT 操作"
  • curl -i -X PUT http://yourDomain/person - 501 Not implemented

高级主题

授权

要检查用户提供的凭据,请在端点类的代码中使用 getAuthUser()getAuthPswd()。如果凭据错误,则简单地抛出 zozlak\rest\UnauthorizedException 异常。

示例

<?php
namespace myRestEndpoint;

use \zozlak\rest\HttpEndpoint;
use \zozlak\rest\DataFormatter;
use \zozlak\rest\HeadersFormatter;
use \zozlak\rest\UnauthorizedException;

class Person extends HttpEndpoint {
    static private $users = ['user1' => 'pswd1', 'user2' => 'pswd2'];

    public function getCollection(DataFormatter $f, HeadersFormatter $h) {
        $user = $this->getAuthUser();
        if (!isset(self::$users[$user]) || self::$users[$user] !== $this->getAuthPswd()) {
            throw new UnauthorizedException();
        }
        $f->data('Login successful');
    }
}

更改 HTTP 状态码和头部

使用传递给所有方法的 HeadersFormatter 对象来更改响应 HTTP 状态和头部。

假设您想为在集合上运行的 POST 方法返回一个具有 201 Created 响应代码和一个指向新创建项的 Location 头部。

<?php
namespace myRestEndpoint;

use \zozlak\rest\HttpEndpoint;
use \zozlak\rest\DataFormatter;
use \zozlak\rest\HeadersFormatter;

class Person extends HttpEndpoint {
    public function postCollection(DataFormatter $f, HeadersFormatter $h) {
        $id = rand();
        // ...everyting else to be done to create the new person...
        $h->setStatus(201);
        $h->addHeader('Location', $this->getUrl() . '/' . $id);
    }
}

还有简写用于重定向

<?php
namespace myRestEndpoint;

use \zozlak\rest\HttpEndpoint;
use \zozlak\rest\DataFormatter;
use \zozlak\rest\HeadersFormatter;

class Person extends HttpEndpoint {
    public function get(DataFormatter $f, HeadersFormatter $h) {
        $h->setRedirect('https://other/location', 302);
    }
}

返回非 JSON 数据

您不应直接使用 echo 等返回数据,因为这可能会破坏一些库功能(精确地说,在打印了超过 PHP 输出缓冲区字节的更多内容后,PHP 将自动发出 HTTP 200 状态码和默认的 HTTP 头部集,所有对 HeadersFormatter 的设置都将被丢弃)。

DataFormatter 对象为所有方法提供了两个辅助方法,帮助您处理非 JSON 数据。

  • raw($data, $contentType, $filename = null) - 返回一个带有指定 Content-Type 头部的任意字符串。如果提供了 $filename 参数,则发出相应的 Content-Disposition: attachment; filename="$filename" 头部。
  • file($path, $contentType = null, $filename = null) - 返回给定的文件。默认情况下,使用 mime_content_type() PHP 函数猜测 Content-Type 头部值,但您可以通过传递 contentType 参数值来覆盖它。如果提供了 $filename 参数,则发出相应的 Content-Disposition: attachment; filename="$filename" 头部。

要更改 HTTP 状态码和/或添加其他头部,请在 HeadersFormatter 对象上使用 setStatus()setHeader() 方法。

示例

<?php
namespace myRestEndpoint;

use \zozlak\rest\HttpEndpoint;
use \zozlak\rest\DataFormatter;
use \zozlak\rest\HeadersFormatter;

class Person extends HttpEndpoint {
    public function postCollection(DataFormatter $f, HeadersFormatter $h) {
        // ...create a file...
        $path = '/tmp/tmpName.csv';
        $h->setStatus(201);
        $f->file($path, '', 'niceName.csv');
    }
}