huge / rest
用于简单、快速和高效创建RESTful webapp的PHP框架
v1.0.3
2014-09-03 16:54 UTC
Requires
- php: >=5.3.3
- doctrine/annotations: ~1.2.0
- doctrine/cache: ~1.3.0
- fuelphp/validation: 0.1
- huge/ioc: ~2.2.0
- psr/log: 1.0.0
- willdurand/negotiation: ~1.3.3
Requires (Dev)
- apache/log4php: ~2.3.0
- guzzle/guzzle: ~3.9.1
- phpunit/phpunit: ~4.1.4
This package is not auto-updated.
Last update: 2024-09-24 07:06:56 UTC
README
用于简单、快速和高效创建RESTful webapp的PHP框架 示例:https://github.com/ffremont/HugeRest-samples
##安装 使用composer安装
{ "require": { "huge/rest": "...", "doctrine/cache" : "v1.3.0" } }
.htaccess
<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>
$loader = require(__DIR__.'/../../../vendor/autoload.php'); // nécessaire charger les annotations \Huge\IoC\Container\SuperIoC::registerLoader(array($loader, 'loadClass'));
功能
- 通过 @Resource / @Path("CHEMIN") 定义资源和服务路径
- 管理HTTP方法:@Get, @Put, @Post, @Delete
- 自定义Mime类型:@Consumes({"...", "..."})
- 允许定义接受的(GET, Delete)或内容类型(POST, PUT)
- 自定义响应内容类型:@Produces({"...", "..."})
- 自定义内容
- 请求内容解释:实现 Huge\Rest\Process\IBodyReader 接口
- 响应内容解释:实现 Huge\Rest\Process\IBodyWriter 接口
- 内容验证:实现 Huge\Rest\Data\IValidator 接口
- 错误处理:实现 Huge\Rest\Process\IExceptionMapper 接口
- 请求过滤:实现 Huge\Rest\Process\IFilter 接口
- 请求拦截:实现 Huge\Rest\Process\IInterceptor 接口
- 缓存:基于 doctrine cache
- 注解:基于 doctrine annotations
配置
$ioc = new \Huge\Rest\WebAppIoC('1.1', array( 'maxBodySize' => 1024 // taille max en octet des body (par défaut ini_get('post_max_size')). Un flux Json en PUT / POST ne pourra pas faire + d'1Ko dans cet exemple ));
创建资源
-
REST资源通过一个注解的PHP类表示。这是一个 Huge\IoC 的组件。
-
使用注解
- @Resource 必需
- @Path 可选
- @Consumes 可选
- 如果是POST, PUT,则对应于请求的contentType
- 否则,对应于请求的accept头
- @Produces 可选
- 定义输出类型Mime
- 如果是POST, PUT,则对应于请求的accept头
- 否则,对应于响应的contentType
-
@Path是正则表达式
- 找到的字符串被添加到函数的参数中
-
令牌列表
- ':mString' => '([a-zA-Z]+)'
- ':mNumber' => '([0-9]+)'
- ':mAlpha' => '([a-zA-Z0-9-_]+)'
- ':oString' => '([a-zA-Z]*)'
- ':oNumber' => '([0-9]*)'
- ':oAlpha' => '([a-zA-Z0-9-_]*)'
/** * EXEMPLE * Ressource "Person" qui a pour chemin "person". Notre ressource produit en retour une structure JSON en v1 par défaut. * Chaque opération de la classe prend par défaut du "application/vnd.person.v1+json" / "application/json". * Si on surcharge sur la fonction @Consumes / @Produces alors la configuration de la fonction primera. * * @Component * @Resource * @Path("person") * * @Consumes({"application/vnd.person.v1+json", "application/json"}) * @Produces({"application/vnd.person.v1+json"}) */ class Person { /** * @Autowired("Huge\Rest\Http\HttpRequest") * @var \Huge\Rest\Http\HttpRequest */ private $request; /** * @Autowired("Huge\IoC\Factory\ILogFactory") * @var \Huge\IoC\Factory\ILogFactory */ private $loggerFactory; public function __construct() {} /** * @Get * @Consumes({"text/plain"}) * @Produces({"text/plain"}) */ public function ping() { return HttpResponse::ok(); } /** * @Get * @Path(":mNumber") */ public function get($id = '') { $person = new \stdClass(); $person->id = $id; return HttpResponse::ok()->entity($person); } /** * @Delete * @Path(":mNumber") */ public function delete($id = '') { $person = new \stdClass(); $person->id = $id; return HttpResponse::ok()->entity($person); } /** * @Put * @Path(":mNumber") */ public function put($id = '') { // @Consumes retenu est celui de la classe (du json) $requestBody = (object)$this->request->getEntity(); $requestBody->id = $id; return HttpResponse::ok()->entity($requestBody); } /** * Accepte le content-type application/json * @Post */ public function post() { $person = new \stdClass(); $person->id = uniqid(); return HttpResponse::ok()->code(201)->entity($person); } /** * @Get * @Path("search/?:oNumber/?:oNumber") */ public function search($numberA = '', $numberB = '') { $query = $this->request->getParam('query'); $list = array(); for ($i = 0; $i < 5; $i++) { $person = new \stdClass(); $person->id = uniqid(); $person->query = $query; $person->a = $numberA; $person->b = $numberB; $list[] = $person; } return HttpResponse::ok()->entity($list); } public function getRequest() { return $this->request; } public function setRequest($request) { $this->request = $request; } public function getLoggerFactory() { return $this->loggerFactory; } public function setLoggerFactory(\Huge\IoC\Factory\ILogFactory $loggerFactory) { $this->loggerFactory = $loggerFactory; } }
处理请求内容
- 要处理HTTP请求的Mime类型,您可以选择实现自己的 "IBodyReader"
- 要实现的接口:Huge\Rest\Process\IBodyReader
$ioc = new \Huge\Rest\WebAppIoC('1.0'); $ioc->addBodyReaders(array( 'application/vnd.github.v1+json' => 'Huge\Rest\Process\Readers\JsonReader' ));
- 可用的reader列表和配置(实例 HttpResquet = $r)
- 'application/x-www-form-urlencoded' => 'Huge\Rest\Process\Readers\FormReader', => $r->getBody() : $_REQUEST
- 'application/json' => 'Huge\Rest\Process\Readers\JsonReader', => $r->getBody() : json_decode
- 'text/plain' => 'Huge\Rest\Process\Readers\TextReader', => $r-getBody() => au body de la request
- 'multipart/form-data' => 'Huge\Rest\Process\Readers\UploadReader', => $r->getBody() : instance Huge\Rest\Http\HttpFiles
- 'multipart/octet-stream' => 'Huge\Rest\Process\Readers\UploadReader', // idem
- 'application/octet-stream' => 'Huge\Rest\Process\Readers\BinaryReader' => $r->getBody() : instance Huge\Rest\Data\TempFile
处理响应内容
- 资源函数返回一个Huge\Rest\Http\HttpResponse对象实例。该对象可以有"entity"属性,该属性将根据所需的HTTP响应contentType进行转换。
- 要实现的接口:Huge\Rest\Process\IBodyWriter
$ioc = new \Huge\Rest\WebAppIoC('1.0'); $ioc->addBodyWriters(array( 'application/vnd.github.v1+json' => 'Huge\Rest\Process\Writers\JsonWriter' ));
- 可用的writer列表和配置
- 'application/x-www-form-urlencoded' => 'Huge\Rest\Process\Writers\FormWriter', => encode $entity avec urlencode
- 'application/json' => 'Huge\Rest\Process\Writers\JsonWriter' => 使用 json_encode 编码 $entity
- 'text/plain' => 'Huge\Rest\Process\Writers\TextWriter' => 转换为字符串
过滤请求和响应
- 过滤器允许在处理 REST 之前进行控制。过滤器在 Huge\IoC 的意义上是一个组件。
- 要实现的接口:Huge\Rest\Process\IRequestFilter
- 要实现的接口:Huge\Rest\Process\IResponseFilter
$ioc = new \Huge\Rest\WebAppIoC('1.0'); $ioc->addDefinitions(array( array( 'class' => 'MyWebApi\Security\Authorization', 'factory' => \Huge\IoC\Factory\SimpleFactory::getInstance() ),array( 'class' => 'MyWebApi\Security\AuthorizationBis', 'factory' => \Huge\IoC\Factory\SimpleFactory::getInstance() ),array( 'class' => 'MyWebApi\PowerByFilter', 'factory' => \Huge\IoC\Factory\SimpleFactory::getInstance() ) )); $ioc->addRequestFiltersMapping(array( 'MyWebApi\Security\Authorization' => '.*', /* applique le filtre sur toutes les ressources */ 'MyWebApi\Security\AuthorizationBis' /* on ne tient pas compte des paths */ )); $ioc->addResponseFiltersMapping(array( 'MyWebApi\PowerByFilter' => '.*' ));
拦截 REST 处理
- 由于不同的原因,您可能需要知道您的 API 处理的开始和结束。拦截器在 Huge\IoC 的意义上是一个组件。
- 要实现的接口:Huge\Rest\Process\IInterceptor
$ioc = new \Huge\Rest\WebAppIoC('1.0'); $ioc->addDefinitions(array( array( 'class' => 'MyWebApi\Interceptors\Custom', 'factory' => \Huge\IoC\Factory\SimpleFactory::getInstance() ) ));
模型上的验证器
-
基于 fuelphp 验证 https://github.com/fuelphp/validation
-
可以验证通过请求体传递的数据。
-
模型必须实现的接口:Huge\Rest\Data\IValidator
// dans votre classe ressource /** * @Autowired("Huge\Rest\Http\BodyReader") */ private $bodyReader; // dans votre fonction $this->bodyReader->validateEntity('...nom_de_la_classe_modele...'); // ou $this->bodyReader->validateEntityList('...nom_de_la_classe_modele...'); // si le contenu est une liste
- 抛出异常:Huge\Rest\Exceptions\ValidationException
-
自定义 fuelPhp 验证器:Huge\Rest\Data\IFuelValidatorFactory
$webAppIoC->setFuelValidatorFactory($votre_factory)
自定义错误
- 您的 webapp 将能够抛出异常,这些异常需要转换为 HTTP 响应。为此,需要注册以下格式的键值对:"异常名称" => "实现该接口的类名"。
- 要实现的接口:Huge\Rest\Process\IExceptionMapper
- 可以定义默认异常映射器 "Exception" => "MonMapper"
$ioc = new \Huge\Rest\WebAppIoC('1.0'); $ioc->addDefinitions(array( array( 'class' => 'MyWebApi\Exceptions\LogicMapper', 'factory' => \Huge\IoC\Factory\SimpleFactory::getInstance() ) // définition des autres composants qui implémentes IExceptionMapper... )); $ioc->addExceptionsMapping(array( 'LogicException' => 'MyWebApi\Exceptions\LogicMapper', 'Huge\Rest\Exceptions\NotFoundResourceException' => null, // désactivation du mapper 'Exception' => 'MyWebApi\Exceptions\DefaultExceptionMapper' ));
- 映射器列表
- 'Huge\Rest\Exceptions\NotFoundResourceException' => 'Huge\Rest\Exceptions\Mappers\NotFoundResourceExceptionMapper',
- 'Huge\Rest\Exceptions\InvalidResponseException' => 'Huge\Rest\Exceptions\Mappers\InvalidResponseExceptionMapper',
- 'Huge\Rest\Exceptions\ValidationException' => 'Huge\Rest\Exceptions\Mappers\ValidationExceptionMapper',
- 'Huge\Rest\Exceptions\WebApplicationException' => 'Huge\Rest\Exceptions\Mappers\WebApplicationExceptionMapper',
- 'Huge\Rest\Exceptions\SizeLimitExceededException' => 'Huge\Rest\Exceptions\Mappers\SizeLimitExceededExceptionMapper',
- 'Exception' => 'Huge\Rest\Exceptions\Mappers\DefaultExceptionMapper'
日志记录器
- 实现工厂:Huge\IoC\Factory\ILogFactory
- 将组件添加到最高级别的容器中
- 如果您的容器有多个,并且每个容器都有自己的实现,那么注入 (@Autowired ILogFactory) 将不会工作,因为会检测到多个实现。
- 通常,WebApp 容器包含实现和测试类
- 空日志记录器工厂(组件):Huge\Rest\Log\NullLoggerFactory
顺序
- 分析 HTTP 请求
- 从组件 Huge\Rest\Http\HttpRequest 开始
- 确定路由:Huge\Rest\Routing\Route(组件)
- 如果没有找到任何路由,则抛出 Huge\Rest\Exceptions\NotFoundResourceException
- 分析请求内容(POST 或 PUT)
- 使用 IBodyReader
- 执行 Huge\Rest\Process\IRequestFilter
- 执行拦截器 Huge\Rest\Process\IInterceptor 的 start 函数
- 执行与资源相关的处理
- 确定在 HTTP 响应中应用的 contentType
- 使用 IBodyWriter
- 执行 Huge\Rest\Process\IResponseFilter
- 执行拦截器 Huge\Rest\Process\IInterceptor 的 end 函数
- 构建响应:Huge\Rest\Http\HttpResponse(build 函数)
限制
- 错误处理无法利用异常的继承
- 基于 Psr\Log 接口的日志记录器:https://packagist.org.cn/packages/psr/log
- 基于 Huge\IoC
- 基于 fuel validation 的验证器
测试
- 单元测试:phpunit -c src/test/resources/phpunit.xml --testsuite TU
- 与 apache2 集成的集成测试:phpunit -c src/test/resources/phpunit.xml --testsuite IT