reindert-vetter/api-version-control

一个用于优雅地管理端点版本的 Laravel 扩展包

2.2.4 2022-08-11 08:35 UTC

README

一个用于优雅地管理端点版本的 Laravel 扩展包。

有关新闻,请关注我的 Twitter

如何安装

两种管理端点版本的方法

选项 1: 版本声明

你可能使用 if 语句来确定是否应该从特定版本执行代码。但如果你想要为两个端点运行此代码,一个是从版本 2 运行的,另一个是从版本 3 运行的,你该怎么办?此扩展包为此提供了一种干净的解决方案:版本声明

选项 2: 版本中间件

遗留代码可能会很快阻碍进度。因此,你因此创建了多个控制器来分离旧代码和新代码?如果有 10 个版本,你将如何做到这一点?那时,你是否也将为每个端点创建 10 个验证方案和响应类?此扩展包还提供了一种比 版本声明 更进一步的 SOLID 解决方案:版本中间件

你可以在一个项目中同时使用 版本中间件版本声明

优势

注意 版本中间件:如果你还没有使用自定义中间件,你可以从控制器中进行调试。有了 版本中间件,同事们现在必须理解,只有当端点处于旧版本时,中间件中的代码也会影响其他代码。

如何使用

版本

在 api_version_control.php 配置文件中,您将看到版本数组

    'releases' => [

        'orders.index' => [
            '<=1' => [
                PrepareParameterException::class,
            ],
        ],

        'orders.store|orders.update' => [
            '<=2' => [
                ThrowCustomException::class,
                ValidateZipCode::class,
            ],
            '<=1' => [
                PrepareParameterException::class,
            ],
        ],

        'default' => [
            '<=1' => [
                ThrowCustomException::class,
            ],
        ],

        'all' => [
            '<=1.0' => [
                RequireUserAgent::class,
            ],
        ],

    ],

路由匹配

您将路由名称放入 releases 数组的键中。键必须与当前路由名称匹配。使用 | 匹配多个路由名称。包将遍历路由名称。如果找到匹配项,则停止搜索。匹配项包含 版本规则。如果找不到路由名称匹配项,则使用 默认。这样,您可以更新您的其他端点。要匹配所有端点的版本,您可以使用 all 键。

您必须在您的路由器中指定路由名称。 示例:Route::get('orders', 'OrdersController@index')->name('orders.index');。当使用资源控制器时,名称会自动确定。有关更多信息,请参阅 Laravel 文档

版本规则

版本规则包含一个运算符和版本('<=2')的字符串。支持的运算符有:<<=>>===!=。所有匹配的 版本规则 中的类都将使用。位于 版本规则 中的类是 版本声明版本中间件

版本声明

一个 版本声明 文件看起来像这样

<?php

namespace App\VersionControl\Orders;

use ReindertVetter\ApiVersionControl\Concerns\VersionStatement;

class ValidateZipCode
{
    use VersionStatement;
}

如果文件包含特质 \ReindertVetter\ApiVersionControl\Concerns\VersionStatement,那么你可以在你的源代码中这样做

if (ValidateZipCode::permitted()) {
    (...)
}

版本中间件

在中间件中处理所有请求和响应,这与最新版本不同。您可以使用多个中间件调整请求以匹配最新版本。您还可以在版本中间件中调整响应的格式。

一个版本中间件文件(用于更改请求)可能如下所示

<?php

namespace App\Middleware\Version;

use Closure;
use Illuminate\Http\Request;

class PrepareParameterException
{
    /**
     * @param           $request
     * @param  \Closure $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        // Set the default parameter because it is required in a newer version.
        $request->query->set('sort', 'DESC');

        return $next($request);
    }
}

一个版本中间件文件(用于更改响应)可能如下所示

<?php

namespace App\Middleware\Version;

use Closure;
use Illuminate\Http\Request;

class ThrowHumanException
{
    /**
     * @param           $request
     * @param  \Closure $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        /** @var \Illuminate\Http\Response $response */
        $response = $next($request);

        // Catch the exception to return an exception in a different format.
        if ($response->exception) {
            $response->setContent(
                [
                    "errors" => [
                        [
                            "human" => $response->exception->getMessage(),
                        ],
                    ],
                ]
            );
        }

        return $response;
    }
}

请求和资源绑定

您可以将FormRequest或Resource绑定到处理其他版本。这样,您可以更容易地使用规则支持不同的参数,并且可以更容易地支持不同的资源。支持不同版本的控制器可能如下所示

    public function index(OrderIndexRequest $request, OrderResource $resource): ResourceCollection
    {
        $orders = Order::query()
            ->productIds($request->productIds())
            ->with($resource->withRelationships())
            ->paginate($request->limit());

        return $resource::collection($orders);
    }

请求变量$request可以是OrderIndexRequestV1或OrderIndexRequestV2,资源变量$resource可以是OrderResourceV1或OrderResourceV2。OrderIndexRequestV2必须扩展基类OrderIndexRequest。您可以为资源类执行相同的操作。当使用Bind中间件时,配置将如下所示

<?php

use ReindertVetter\ApiVersionControl\Middleware\Version\Bind;

return [

    'releases' => [

        'orders.index' => [
            '<=1' => [
                new Bind(OrderIndexRequest::class, OrderIndexRequestV1::class),
                new Bind(OrderIndexResource::class, OrderIndexResourceV1::class),
            ],
            '>=2' => [
                new Bind(OrderIndexRequest::class, OrderIndexRequestV2::class),
                new Bind(OrderIndexResource::class, OrderIndexResourceV2::class),
            ],
        ],

    ]
]

如果还不清楚,请在讨论中发布您的问题。

版本解析器

默认情况下,此包支持头部的版本和URI中的版本。但您也可以创建自己的版本解析器。在api_version_control.php配置文件中指定此内容。

安装

  1. 运行composer require reindert-vetter/api-version-control
  2. 在您的RouteServiceProvider中添加->middleware(['api', ApiVersionControl::class])

如果您使用的是URL版本解析器(这是默认设置),请确保URL中存在版本变量。例如

Route::middleware(['api', ApiVersionControl::class])
    ->prefix('api/{version}')
    ->where(['version' => 'v\d{1,3}'])
    ->group(base_path('routes/api.php'));

现在,只有带有版本的URL(例如/api/v2/products)可以访问路由。您也想让端点在没有版本的情况下工作吗?然后首先定义没有版本变量的路由

Route::middleware(['api', ApiVersionControl::class])
    ->prefix('api')
    ->as('no_version.')
    ->group(base_path('routes/api.php'));

Route::middleware(['api', ApiVersionControl::class])
    ->prefix('api/{version}')
    ->where(['version' => 'v\d{1,3}'])
    ->group(base_path('routes/api.php'));

您可以看到,我们使用no_version.(用于没有版本的router)前缀路由名称。您必须这样做以避免在缓存路由时出现错误Another route is already using that name。自行决定这是否适合您的应用程序。

  1. 在config/app.php中将\ReindertVetter\ApiVersionControl\ApiVersionControlServiceProvider::class添加到您的提供者中
  2. 运行php artisan vendor:publish --provider='ReindertVetter\ApiVersionControl\ApiVersionControlServiceProvider'以创建配置文件。
  3. 选择一个版本解析器或自己创建一个。
  4. 在需要时运行php artisan route:clearphp artisan route:cache

如果还不清楚,请在讨论中发布您的问题。