square/vermillion

Laravel应用的API版本管理工具

v1.1.0 2023-07-08 07:52 UTC

This package is auto-updated.

Last update: 2024-09-08 10:17:49 UTC


README

PHP

用于Laravel应用程序的API版本管理工具️

您将获得

  • 一个你可以用来检测请求的API版本并进行版本比较的ApiVersion服务。
  • 你可以拥有任何数量的API版本(10个版本?1,000个?这里没有评判。)而不必让你的应用路由集合爆炸。保持路由器匹配版本化路由的时间恒定,让你的开发者保持理智:没有更多的路由名污染--它是user.list,而不是users.list.v1users.list.v3users.list.v21等。
  • 一种可扩展的方式来版本化你的API响应。通过反向迁移将现有的JsonResource支持多个API版本。
  • 一个声明式实用API,你可以用它来做任何事情。只需指定适用于一系列API版本的逻辑变化,并在需要时程序化地解决正确的变体。
  • 一个可扩展的版本化策略系统,可以精确地定义客户如何请求API版本。内置了对URI前缀(例如/v2/...)或HTTP头(例如X-Api-Version: ...)的支持,你还可以轻松地构建自己的。
  • 一个可扩展的版本化格式系统,可以精确地定义你理解哪些API版本。内置了对数值版本(例如v2)和日期版本(例如2022-11-01)的支持,而且自己创建也非常简单。

安装

composer install square/vermillion

配置

首先,确保在您的应用中注册了Square\Vermillion\VersioningServiceProvider。有时它是自动的(通过包发现),有时则不是,所以请检查您的应用配置。

运行此命令以生成版本化配置的副本

php artisan vendor:publish

选择API版本格式

版本有多种表示方法,例如SemVer。内置了对两种格式的支持,您可以选择其中一种

  • major - 代表"仅主版本",例如2
  • date - 版本为日期格式,例如2020-02-24

您也可以自己创建。请参阅高级用法下的使用自定义版本格式

选择API版本策略

您可以选择客户端指定API版本的方式

  • url_prefix - API版本将指定在URL中,例如/api/v2/hello
  • header - API版本将指定在请求头中,例如X-Api-Version: 2020-02-01

您也可以自己创建。请参阅高级用法下的使用自定义版本策略

指定支持的版本

您可以在配置文件中独立配置minlatestmax版本

  1. 最低版本(例如'min' => '1')- 您的API当前支持的最低版本。任何请求版本小于min版本版本化路由都将自动404。
  2. 最新版本(例如'latest' => '2')- 您的API支持的最新稳定版本。当未显式指定或无法推断时(例如在非版本化路由上下文中生成URL,在异步作业中等)将使用此版本。
  3. 最大版本(例如 'max' => '3')- 您的API所支持的最大版本。任何被认为是alpha、beta或RC的版本都更适合于大于 latest 的版本。这是客户端可以请求的最大API版本,即任何请求的带有比 max 版本更大的版本号的版本路由将自动返回404。

基本用法

如何定义版本化路由

# routes/api.php

/*
 * Desired URL format: /api/v3/hello-world, etc.
 */
Route::prefix('/api')->group(function ($router) {

   // Start a group of versioned routes.
    $router->versioned()->group(function ($router) {

        // Define the base route. The specified controller-action will be used from min version to right before next...

        Route::get('/users', [UserController::class, 'list'])
          ->name('users.list')
          ->apiVersion('2', [UserController::class, 'listWithImplicitDefaults']) // Another controller used for v2+
          ->apiVersion('5', [UsersController::class, 'listViaCursorPagination']); // ...and another controller for v5+.

        // This other endpoint is only available v3+
        Route::post('/insights', $router->versioning()->unsupported()) // Default to "not supported" response (404)
          ->name('insights')
          ->apiVersion('3', [StatsController::class, 'insights']); // ...then goes to working controller for v3+
    });
});

路由URL生成

当使用 url_prefix 作为版本化方案时,URL生成器将自动配置为使用当前活动的版本。

# During an /api/v3/insights request
route('users.list');  #=> /api/v3/users

如果没有活动版本,例如在未版本化的路由中执行的代码,将使用最新版本(在 config('versioning.latest') 中定义)。

如果您想为另一个API版本生成路由,请指定 apiVersion 选项

# Generate URL with specific version:
route('users.list', ['apiVersion' => '4']); #=> /api/v4/users

ApiVersion 服务

您可以将 Square\Vermillion\ApiVersion 作为依赖项,并将获得当前活动(或最新)的API版本对象的引用

class InfoController
{
    public function list(Request $request, ApiVersion $apiVersion)
    {
        /*
         * Decide business logic based on API version...
         */
        if ($apiVersion->gte('4')) {
            // ...
        }

        if ($apiVersion->lte('6')) {
            // ...
        }

        if ($apiVersion->eq('7')) {
            // ...
        }

    }
}

注意:您需要对 Square\Vermillion\ApiVersion 抽象进行类型提示,而不是 Square\Vermillion\Formats\* 命名空间中的任何具体实现!

版本化API响应

您可以使用 Square\Vermillion\Traits\JsonResource\WithReverseMigrations 特性来支持类似Stripe的数据版本化,通过“反向迁移”

class UserResource extends JsonResource
{
     use WithReverseMigrations;

     /**
      * This must return the API response body of the max version you support.
      */
     public function toLatestArray($request)
     {
        return [
            'display_name' => $this->resource->name,
            'full_name' => $this->resource->full_name,
            'age' => $this->resource->age,
            'friends' => $this->whenLoaded($this->resource->friends, fn() ...),
        ];
     }

     /**
      * Override this method to specify the "reverse-migrations" 
      * responsible for rolling back the latest API response body, 
      * one iteration at at ime.
      */
     protected static function reverseMigrations(VersionedSet $migrations)
     {
           $migrations
               ->for('5', self::addBackFirstAndLastName(...))
               ->for('3', self::friendsWasNotNullable(...))
     }

     protected static function addBackFirstAndLastName(array $data, UserResource $resource, $request)
     {
           unset($data['full_name']);
           return array_merge(
               $data,
               [
                   'first_name' => $resource->user->first_name,
                   'last_name' => $resource->user->last_name,
               ],
           );
     }

     protected static function friendsWasNotNullable(array $data, UserResource $resource, $request)
     {
        return array_merge(
              $data,
              [
                 'friends' => $resource->user->friends ?? [],
              ],
        );
     }
}

基于头的版本化及 MissingVersionException

默认情况下,使用 header 方案将要求请求包含头信息,当它是一个版本化路由时。这表现为抛出一个 MissingVersionException 异常,您需要适当地渲染它

// app/Exceptions/Handler.php

namespace App\Exceptions;

use Square\Vermillion\Exceptions\VersionMissingException;

class Handler extends ExceptionHandler
{
    ...
    
    public function register()
    {
        $this->renderable(function (VersionMissingException $e) {
            return response([
                // Whatever you see fit.
            ])->setStatusCode(400);
        });
    }
}

高级用法

使用自定义版本策略

如果您需要根据HTTP请求确定要使用的API版本,您需要编写自己的方案,通过实现 Square\Vermillion\VersioningScheme 合约。

versioning 配置中指定您的自定义版本化方案的FQCN

<?php

// config/versioning.php

return [
  'scheme' => App\Http\Versioning\MyCustomScheme::class,
];

使用自定义版本格式

  1. 您可以通过扩展 Square\Vermillion\ApiVersion 抽象来实现自己的版本化格式,例如SemVer。对于您的新 ApiVersion 类型,所需的是提供版本字符串的整数表示。库使用它来计算版本之间的序数,这是使一切正常工作的所有需要。

  2. 您需要实现一个 Square\Vermillion\VersionNormalizer 类,它负责将版本字符串转换为您的自定义 ApiVersion 子类的实例。它必须支持您的自定义版本字符串作为输入,以及自定义的 ApiVersion 子类。如果它提供了无法根据您自定义的版本格式规范转换为有效 ApiVersion 的输入,则必须抛出 BadVersionFormatException

  3. 将您的规范器的FQCN指定为 versioning.normalizer 配置值,例如。

<?php

// config/versioning.php

return [
  'normalizer' => App\Http\Versioning\MyCustomNormalizer::class,
];

使用 VersionedSet 版本化任何内容

通过 策略模式 来建模条件逻辑是一种强大的方法,特别是如果有许多可能的分支。这在支持许多API版本时通常是这种情况。专门设计 VersionedSet 工具类来使基于API版本的决策易于实现和管理

// Create a new VersionedSet
$set = app(VersioningManager::class)->versionedSet();

$set->for('1', new LegacyDoer()); // The original variation.
$set->for('3', new DifferentDoer()); // We made things a little different starting v3 but not in a very BC way.
$set->for('5', new BetterDoer()); // We made things a lot better starting v5, but it again requires breaking BC.

$doer = $set->resolve('4'); // returns `new DifferentDoer()`.

$doer->execute();

// Resolves variation according to default or active version:
$doer = $set->resolve(); // returns `new BetterDoer()`.

// Throws exception for unsupported versions e.g. below min or above max:
$doer = $set->resolve('100') // throws UnknownVersionException because we don't support this version.

PHPStan

将此行添加到您的 phpstan.neon 文件中,以填充关于 Route 方法等的静态分析空白。

includes:
  - ./vendor/square/vermillion/phpstan/extension.neon