red-explosion / vermillion
Laravel应用的API版本管理工具
Requires
- php: ^8.1
- illuminate/contracts: ^9.0|^10.0
- illuminate/routing: ^9.0|^10.0
- illuminate/support: ^9.0|^10.0
Requires (Dev)
- laravel/pint: ^1.10
- nunomaduro/larastan: ^2.0
- orchestra/testbench: ^7.0|^8.0
- phpstan/phpstan: ^1.9
- red-explosion/pint-config: ^1.1
This package is not auto-updated.
Last update: 2024-09-20 04:52:44 UTC
README
为Laravel应用程序提供API版本管理工具️
您将获得什么
- 一个你可以使用的
ApiVersion
服务,用于检测请求的API版本并执行版本之间的比较。 - 你可以拥有任意多的API版本(10个版本?1,000个?这里没有评判。)而不会爆炸你的应用的路由集合。保持路由器匹配版本化路由的时间恒定,让你的开发者保持理智:不再有路由名污染——它是
user.list
,而不是users.list.v1
、users.list.v3
、users.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
您也可以自己构建。请参阅“高级用法”下的“使用自定义版本化方案”。
指定支持的版本
您可以在配置文件中独立配置min
、latest
和max
版本
- 最低版本(例如
'min' => '1'
)- 您的API当前支持的最早版本。任何请求版本小于min
版本的路由将自动返回404。 - 最新版本(例如
'latest' => '2'
)- 您的API支持的最新稳定版本。在没有明确指定或无法推断版本(例如在不带版本的路由上下文中生成URL,在异步任务中等)时,将使用此版本。 - 最大版本(例如
'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, ];
使用自定义版本格式
-
您可以通过扩展
RedExplosion\Vermillion\ApiVersion
抽象实现自己的版本控制格式,例如SemVer。您的新ApiVersion
类型所需提供的只是您版本字符串的整数表示。库使用此值来计算版本之间的序号,这正是使一切正常工作的所需。 -
您需要实现一个
RedExplosion\Vermillion\VersionNormalizer
类,该类负责将版本字符串转换为您的自定义ApiVersion
子类的实例。它必须支持您的自定义版本字符串作为输入,以及自定义的ApiVersion
子类。如果它提供的输入无法转换为有效ApiVersion
,则必须抛出BadVersionFormatException
。 -
将您的正常化器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