zozlak / rest
REST 控制器和端点类
Requires
- php: >=8
- zozlak/util: ^1.5.2
Requires (Dev)
README
用于创建 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/person
和 http://yourDomain/person/{id}
)的所有 HTTP 方法提供默认实现(发出 HTTP 501 方法未实现
错误代码)。对于资源端点的方法名称,只需遵循 HTTP 方法名称(get()
、put()
等)即可,对于集合端点,它们后面加上 collection
(getCollection()
、postCollection()
等)。OPTION
方法的默认实现检查您的类中实现了哪些方法,并相应地发出 Allow
头值(如果没有实现,则发出 404 Not Found
)。
您应该从 zozlak\rest\HttpEndpoint
衍生您的类,并覆盖具有有用实现的方法。
类名应遵循最后一个 API 端点段名称(跳过 {id}
段),并将其转换为 CamelCase,例如:
http://yourDomain/person
、http://yourDomain/person/{id}
或http://yourDomain/pErSoN/{id}
将由Person
类处理。http://yourDomain/person/{id}/order
或http://yourDomain/person/{id}/order/{id}
将由Order
类处理。
这意味着我们必须创建两个类:Person
和 Project
。假设您将它们的代码放入 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');
}
}