square / vermillion
Laravel应用的API版本管理工具
Requires
- php: ^8.0
- illuminate/contracts: ^8.0|^9.0|^10.0
- illuminate/routing: ^8.0|^9.0|^10.0
- illuminate/support: ^8.0|^9.0|^10.0
Requires (Dev)
- nunomaduro/larastan: ^2.0
- orchestra/testbench: ^7.15
- phpstan/phpstan: ^1.9
- phpunit/phpunit: ^9.0
README
用于Laravel应用程序的API版本管理工具️
您将获得
- 一个你可以用来检测请求的API版本并进行版本比较的
ApiVersion
服务。 - 你可以拥有任何数量的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 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
您也可以自己创建。请参阅高级用法下的使用自定义版本策略。
指定支持的版本
您可以在配置文件中独立配置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('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, ];
使用自定义版本格式
-
您可以通过扩展
Square\Vermillion\ApiVersion
抽象来实现自己的版本化格式,例如SemVer。对于您的新ApiVersion
类型,所需的是提供版本字符串的整数表示。库使用它来计算版本之间的序数,这是使一切正常工作的所有需要。 -
您需要实现一个
Square\Vermillion\VersionNormalizer
类,它负责将版本字符串转换为您的自定义ApiVersion
子类的实例。它必须支持您的自定义版本字符串作为输入,以及自定义的ApiVersion
子类。如果它提供了无法根据您自定义的版本格式规范转换为有效ApiVersion
的输入,则必须抛出BadVersionFormatException
。 -
将您的规范器的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