firehed/api

一个API框架

3.2.2 2019-02-12 22:14 UTC

README

Build Status codecov

安装

API通过composer提供

composer require firehed/api

使用

设置一个.apiconfig文件,它包含框架的JSON格式设置。您可以通过运行vendor/bin/api generate:config来执行此操作。有关更多信息,请参阅下面的配置。

生成默认的前端控制器

vendor/bin/api generate:frontController

在创建、修改或删除端点后,运行编译器

vendor/bin/api compile:all

这一步是必需的:框架依赖于生成的文件,而不是在运行时尝试执行相同的步骤。预计您将在每次构建/部署时使用上述命令重建文件。请参阅以下最佳实践部分。

测试

为了方便起见,包含了一个特性,其中包含对端点描述方法的测试。在您的测试用例类中(通常扩展PHPUnit\Framework\TestCase),使用特性

\Firehed\API\Traits\EndpointTestCases

并添加一个返回待测端点实例的getEndpoint方法。

示例

<?php

namespace MyApp\API\Endpoints\User;

use Firehed\API\Traits\EndpointTestCases;

/**
 * @covers MyApp\API\Endpoints\User\Create
 */
class CreateTest extends \PHPUnit\Framework\TestCase
{

    use EndpointTestCases;

    protected function getEndpoint()
    {
        return new Create();
    }
}

配置

在项目根目录中放置一个名为.apiconfig的文件。它使用JSON格式。包含了一个控制台命令,可以引导您进行配置,可以通过运行vendor/bin/api generate:config来调用。

选项

source: 必需 字符串

扫描API端点的源代码目录。通常是src

namespace: 必需 字符串

在搜索端点时过滤的命名空间。

webroot: 必需 字符串

放置生成的前端控制器的目录。该值应为项目根目录的相对路径。

container: 可选 字符串

返回PSR-11兼容容器以配置值的文件路径。

容器

如果在.apiconfig中设置了container值,API将了解容器(如果您不使用生成的前端控制器,也可以手动执行)。这是在运行时配置API端点的方式。按照惯例,如果容器has()端点的完全限定类名,则分发器将get()并使用该值在路由分发时使用。如果没有配置容器,或者容器没有路由端点的配置,则路由端点将通过new $routedEndpointClassName简单地实例化。

其他自动检测的容器条目

示例

.apiconfig:

{
    "webroot": "public",
    "namespace": "Your\\Application",
    "source": "src",
    "container": "config.php"
}

config.php:

<?php
use Firehed\API;
use Psr\Log\LoggerInterface;
use Your\Application\Endpoints;

$container = new Pimple\Container();
// Endpoint config
$container[Endpoints\UserPost::class] = function ($c) {
    return new Endpoints\UserPost($c['some-dependency']);
};

// Other services
$container[API\Authentication\ProviderInterface::class] = function ($c) {
    // return your provider
};
$container[API\Authorization\ProviderInterface::class] = function ($c) {
    // return your provider
};
$container[LoggerInterface::class] = function ($c) {
    return new Monolog\Logger('your-application');
};

// ...
return new Pimple\Psr11\Container($container);

在此示例中,当您的UserPost端点被路由时,它将使用容器中定义的端点 - 这允许端点具有必需的构造函数参数或其他配置。

如果您有一个UserGet端点,它不在容器中,分发器将自动尝试用new实例化它。如果该端点没有构造函数参数,这将很好。但是,这意味着如果它没有,您的应用程序将在运行时崩溃 - 因此,具有必需构造函数的任何端点必须在容器中配置。

身份验证和授权

为身份验证(谁在执行请求)和授权(是否允许执行请求)的过程分别定义了两个接口,分别命名为 Authentication\ProviderInterfaceAuthorization\ProviderInterface。这两个接口将在容器中自动检测,并且必须提供。如果这两个接口都没有提供,则使用应用范围的处理程序将永远不会执行任何身份验证或授权。

任何实现 Interfaces\AuthenticatedEndpointInterface 的端点在执行之前将执行这些过程,并由身份验证提供者返回的容器提供。如果一个端点没有实现 Interfaces\AuthenticatedEndpointInterface(即它只实现了 Interfaces\EndpointInterface),则 将跳过应用范围内的身份验证。端点当然可以在其 execute() 方法中实现自己的身份验证协议,但除了登录页面之外(见下文),这被不推荐。

一般来说,上述接口的实现应该查找(几乎)每个请求中存在的身份验证数据:cookies、OAuth Bearer 令牌、HTTP 基本认证等,并验证其真实性。通常用于获取身份验证数据(例如 OAuth 授权)的端点通常不会进行身份验证,但会设置或返回用于对其他请求进行身份验证的数据。

示例提供者,实现了上述两个接口

<?php
declare(strict_types=1);

namespace Your\Project;

use Firehed\API\Authentication\ProviderInterface as AuthnProvider;
use Firehed\API\Authorization\Exception as AuthException;
use Firehed\API\Authorization\ProviderInterface as AuthzProvider;
use Firehed\API\Authorization\Ok;
use Firehed\API\Container;
use Firehed\API\Interfaces\AuthenticatedEndpointInterface;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ServerRequestInterface;

// This elides a lot of details and error handling for simplicity
class AuthProvider implements AuthnProvider, AuthzProvider
{
    public function authenticate(ServerRequestInterface $request): ContainerInterface
    {
        list($_, $token) = explode(' ', $request->getHeaderLine('Authorization'), 2);
        // Find a user, app, etc. from the token string
        return new Container([
            App::class => $app,
            User::class => $user,
            // ...
            'oauth_scopes' => $scopes,
        ]);
    }

    public function authorize(AuthenticatedEndpointInterface $endpoint, ContainerInterface $container): Ok
    {
        $scopes = $container->get('oauth_scopes');
        if (!$endpoint instanceof YourInternalScopeInterface) {
            throw new \LogicException('Endpoint is invalid');
        }
        // This is a method in YourInternalScopeInterface
        $neededScopes = $endpoint->getRequiredScopes();
        foreach ($neededScopes as $scope) {
            if (!in_array($scope, $scopes)) {
                throw new AuthException(sprintf('Missing scope %s', $scope));
            }
        }
        return new Ok();
    }
}

错误处理

强烈建议不要处理在端点的 execute() 方法中抛出的大多数异常。相反,更愿意编写通过抛出异常而失败声明的服务,以及期望成功情况的端点。这不是一个绝对规则,但有助于避免深层嵌套的 try/catch 块和其他错误处理复杂性。

API 框架负责捕获在端点的 execute() 方法期间抛出的所有异常,并将它们提供给专门的异常处理器。

强烈建议通过容器创建并提供默认错误处理器 Firehed\API\Errors\HandlerInterface(见上表)。所有未处理的异常都将发送到该处理器,包括请求(以便根据 Accept 头等格式化响应)。

所有实现 Firehed\API\Interfaces\HandlesOwnErrorsInterface(在 v4.0.0 之前的 EndpointInterface 的一部分)的端点都将使用抛出的异常调用其 handleException() 方法。此处理器将在默认错误处理器之前被调用。此方法 可以 选择忽略某些异常类(通过重新抛出它们),但必须在选择处理异常时返回一个 PSR ResponseInterface

最后,默认配置了一个全局回退处理器,该处理器将记录异常并返回一个通用的 500 错误。

最佳实践

源代码控制

当然要使用源代码控制。

以下模式应添加到源代码控制的忽略文件中,以排除生成的文件

  • __*__.*

构建自动化

强烈推荐(对于任何现代 PHP 应用程序)使用自动化构建。

此框架依赖于编译来提高性能。在部署之前必须运行编译过程,并在自动化构建期间运行。

vendor/bin/api compile:all.

Docker

在 Docker 中运行没有特殊要求,除了在上述构建自动化部分中提到的。

这意味着您应该在安装 Composer 依赖项之后的 Dockerfile 中的任何位置都包含以下行

RUN vendor/bin/api compile:all

应该 还将所有源代码控制忽略文件添加到 .dockerignore 中。

兼容性

此框架试图严格遵守语义版本规则的规则。总之,这意味着给定一个名为 X.Y.Z 的版本

  • 只有当 X 增加时,才会引入破坏性更改
  • 新功能将在以下任一情况下引入:当 Y 增加时,或者当 X 增加且 Y 重置为 0
  • 错误修复可能在任何版本增量中引入

术语“破坏性变更”应理解为

  • 向方法添加额外的必需参数
  • 向接口添加额外的方法定义
  • 紧化方法或函数参数的类型提示
  • 放宽方法或函数的返回类型
  • 删除任何公共方法(除非在标记为内部使用的类上)
  • 额外的系统要求(PHP版本、扩展等)
  • 重大的、非可选的行为变更
  • 对文档化构建步骤的必需修改(例如 vendor/bin/api compile:all

破坏性变更不包括

  • 删除依赖项(如果您隐式依赖于该框架的依赖项,您应显式将其添加到自己的 composer.json
  • 删除明确标记为内部使用的类或方法
  • 在编译过程中生成的任何文件的格式或内容变更,包括完全添加或删除文件

尽可能,过时功能将通过 trigger_error(string, E_USER_DEPRECATED) 标记为过时(除发布说明之外)。请注意,根据您的PHP设置,这可能会导致抛出 ErrorException。由于这是一个可配置的行为,它不被视为BC破坏。

此外,整个 Firehed\API 命名空间应被视为保留用于PSR-11容器自动检测的目的。也就是说,如果您在容器中使用以 Firehed\API 开头的键,您应该预期该键可能会被检索并使用,而无需明确选择该行为。