huge/rest

用于简单、快速和高效创建RESTful webapp的PHP框架

v1.0.3 2014-09-03 16:54 UTC

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 函数)

限制

测试

  • 单元测试:phpunit -c src/test/resources/phpunit.xml --testsuite TU
  • 与 apache2 集成的集成测试:phpunit -c src/test/resources/phpunit.xml --testsuite IT