red-explosion/vermillion

Laravel应用的API版本管理工具

v0.1.0 2023-05-04 00:34 UTC

This package is not auto-updated.

Last update: 2024-09-20 04:52:44 UTC


README

PHP

为Laravel应用程序提供API版本管理工具️

您将获得什么

  • 一个你可以使用的ApiVersion服务,用于检测请求的API版本并执行版本之间的比较。
  • 你可以拥有任意多的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 red-explosion/vermillion

配置

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

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

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('vermillion.latest')中定义)。

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

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

ApiVersion服务

您可以将RedExplosion\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')) {
            // ...
        }

    }
}

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

API响应版本化

您可以使用RedExplosion\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 RedExplosion\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版本,您需要编写自己的方案,通过实现RedExplosion\Vermillion\VersioningScheme合同。

versioning配置中指定您自定义版本控制方案的FQCN。

<?php

// config/vermillion.php

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

使用自定义版本格式

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

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

  3. 将您的正常化器FQCN指定为vermillion.normalizer配置值,例如。

<?php

// config/vermillion.php

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

使用VersionedSet进行版本控制

通过Strategy pattern(策略模式)建模条件逻辑是一种强大的方式,特别是如果有许多可能的分支。当支持许多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/red-explosion/vermillion/phpstan/extension.neon