realtydev/laravel-swagger

自动为Laravel项目生成Swagger文档。

dev-main 2022-05-11 02:36 UTC

This package is auto-updated.

Last update: 2024-09-11 07:44:58 UTC


README

Laravel Swagger会扫描Laravel项目的端点,并自动为您生成Swagger 2.0文档。

Build Status Latest Stable Version License

关于

Laravel Swagger基于Laravel推荐的最佳实践。它将解析您的路由并为每个路由生成一个路径对象。如果您的控制器动作中注入了表单请求类作为请求验证,它还将为具有这些请求的每个请求生成参数。对于参数,它将考虑请求是GET/HEAD/DELETE还是POST/PUT/PATCH请求,并尽可能猜测应生成哪种参数对象类型。它还会生成包含在路由中的路径参数。最后,此包还将扫描您的动作方法中的任何文档,并将其作为摘要和描述添加到该路径中,同时添加任何适当的注释,如@deprecated。

请注意,此库倾向于明确性。即使有默认值,它也会选择包含键。例如,它选择说路由有一个已弃用的值为false,而不是将其省略。我相信这通过不省略重要信息使阅读文档变得更容易。如果用户选择省略默认值,文件可以很容易地进行清理。

默认情况下,生成的文档将可通过/docs路由使用Swagger UI视图访问。

安装

您可以通过在项目根目录中运行composer require realtydev/laravel-swagger轻松安装此包。

如果您正在运行Laravel < 5.5的版本,请确保将realtydev\LaravelSwagger\SwaggerServiceProvider::class添加到config/app.php中的providers数组中。

这将注册可用的artisan命令。

您还可以通过在项目根目录中运行php artisan vendor:publish --provider "realtydev\LaravelSwagger\SwaggerServiceProvider"来覆盖应用程序提供的默认配置,并更改新创建的config/laravel-swagger.php文件中的配置。

配置

在文件config/laravel-swagger.php中,您可以定义您的API版本,只需复制默认设置并根据需要更新即可。例如

[
    // ...

    'versions' => [
        [
            'appVersion' => '1.0.0',
            'host' => 'v1.myexample.com',
            'basePath' => '/v1',
            'schemes' => [
                'https',
            ],
            'consumes' => [
                'application/json',
            ],
            'produces' => [
                'application/json',
            ],
            'ignoredMethods' => [
                'head',
            ],
            'ignoredRoutes' => [
                'laravel-swagger.docs',
                'laravel-swagger.asset'
            ],
            'authFlow' => 'accessCode',
            'security_definition_type' => 'oauth2',
            'file_format' => 'json',
            'errors_definitions' => [
                'UnprocessableEntity' => [
                    'http_code' => 422,
                    'exception' => ValidationException::class,
                    'handler' => ValidationErrorDefinitionHandler::class
                ],
                'Forbidden' => [
                    'http_code' => 403,
                    'exception' => AuthorizationException::class,
                    'handler' => DefaultErrorDefinitionHandler::class
                ],
                'NotFound' => [
                    'http_code' => 404,
                    'exception' => ModelNotFoundException::class,
                    'handler' => DefaultErrorDefinitionHandler::class
                ],
                'Unauthenticated' => [
                    'http_code' => 401,
                    'exception' => AuthenticationException::class,
                    'handler' => DefaultErrorDefinitionHandler::class
                ],
            ],
        ],
        [
            'appVersion' => '2.0.0',
            'host' => 'v2.myexample.com',
            'basePath' => '/v2',
            'schemes' => [
                'https',
            ],
            'consumes' => [
                'application/json',
            ],
            'produces' => [
                'application/json',
            ],
            'ignoredMethods' => [
                'head',
            ],
            'ignoredRoutes' => [
                'laravel-swagger.docs',
                'laravel-swagger.asset'
            ],
            'authFlow' => 'accessCode',
            'security_definition_type' => 'jwt',
            'file_format' => 'json',
            'errors_definitions' => [
                'UnprocessableEntity' => [
                    'http_code' => 422,
                    'exception' => ValidationException::class,
                    'handler' => ValidationErrorDefinitionHandler::class
                ],
                'Forbidden' => [
                    'http_code' => 403,
                    'exception' => AuthorizationException::class,
                    'handler' => DefaultErrorDefinitionHandler::class
                ],
                'NotFound' => [
                    'http_code' => 404,
                    'exception' => ModelNotFoundException::class,
                    'handler' => DefaultErrorDefinitionHandler::class
                ],
                'Unauthenticated' => [
                    'http_code' => 401,
                    'exception' => AuthenticationException::class,
                    'handler' => DefaultErrorDefinitionHandler::class
                ],
            ],
        ],
    ],
];

使用方法

要使定义生成工作,您需要迁移数据库表。在生成文档之前,请确保执行migrate命令。

php artisan migrate

生成Swagger文档很简单,只需在项目根目录中运行php artisan laravel-swagger:generate即可。

默认情况下,该命令将为在config/laravel-swagger.php中定义的所有版本生成文档。

您可以通过传递参数--api-version=来过滤到特定版本。例如

php artisan laravel-swagger:generate --api-version=2.0.0 # must match the version defined in the configs

默认情况下,laravel-swagger以json格式生成文档,如果您想以YAML格式生成,可以使用--format标志覆盖格式。如果选择这样做,请确保已安装yaml扩展。

支持的格式选项有

  • json
  • yaml

生成文档后,访问路由/docs以查看API文档。默认情况下将显示最新版本的API,但您可以在屏幕上选择版本或通过路径参数传递版本。例如:/docs/2.0.0

示例

假设您有一个路由 /api/users/{id},它映射到 UserController@show

您的示例控制器可能看起来像这样

/**
 * Return all the details of a user
 *
 * Returns the user's first name, last name and address
 * Please see the documentation [here](https://example.com/users) for more information
 *
 * @deprecated
 */
class UserController extends Controller
{
    public function show(UserShowRequest $request, $id)
    {
        return User::find($id);
    }
}

FormRequest 类可能看起来像这样

class UserShowRequest extends FormRequest
{
    public function rules()
    {
        return [
            'fields' => 'array',
            'show_relationships' => 'boolean|required'
        ];
    }
}

运行 php artisan laravel-swagger:generate 将在公共路径上生成一个包含以下定义的文件

{
    "swagger": "2.0",
    "info": {
        "title": "Laravel",
        "description": "Test",
        "version": "1.0.1"
    },
    "host": "http:\/\/localhost",
    "basePath": "\/",
    "paths": {
        "\/api\/user\/{id}": {
            "get": {
                "summary": "Return all the details of a user",
                "description": "Returns the user's first name, last name and address. Please see the documentation [here](https://example.com/users) for more information",
                "deprecated": true
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                },
                "parameters": [
                    {
                        "in": "path",
                        "name": "id",
                        "type": "integer",
                        "required": true,
                        "description": ""
                    },
                    {
                        "in": "query",
                        "name": "fields",
                        "type": "array",
                        "required": false,
                        "description": ""
                    },
                    {
                        "in": "query",
                        "name": "show_relationships",
                        "type": "boolean",
                        "required": true,
                        "description": ""
                    }
                ]
            },
            ...
        }
    }
}

定义

您可以在方法或控制器/类的文档中使用完整模型路径来定义注解 @model(适用于所有方法)。这将生成对模型在响应中的引用,例如

// Model definition on method:
class OrderController
{
    /**
     * @param int $id
     * @model App\Models\Order
     */
    public function show(int $id)
    {
        // ...
    }
}

// Model definition on Controller:
/**
 * Class ProductController
 * @model App\Models\Product
 */
class ProductController
{
    /**
     * @param int $id
     */
    public function show(int $id)
    {
        // ...
    }
}

模型

模型定义字段将从 Schema::getColumnListing($model->getTable()) 函数返回的 columns 表中获取。

如果您想使用 $appends 属性的字段,请在您的模型类中使用 trait 'realtydev\LaravelSwagger\Traits\HasAppends'。例如

use realtydev\LaravelSwagger\Traits\HasAppends;

class MyModel extends Model
{
    use HasAppends;

    // ...
}

如果模型有一个关联的 factory,并且您在配置中启用了 generateExampleData 选项,则将为定义的每个字段生成 example 数据。

警告:我们使用数据库事务来生成此数据。尽管数据不会保存到数据库中,但建议您在开发环境中使用此功能,以避免任何意外的副作用。

将过滤列以删除具有 $hidden 属性的字段。

如果您启用了 parseModelRelationships 选项,并且如果关系方法包含 Relationship 返回类型提示,则将模型关系添加到定义中。

警告:为了获取关系的关联模型,我们必须调用该方法。确保调用这些方法时没有副作用,并且它们只返回关系。

例如

// Tables structure:
Schema::create('products', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->string('name');
    $table->decimal('price');
    $table->boolean('active');
    $table->timestamp('finished_at');
    $table->timestamps();
});
Schema::create('orders', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->decimal('value');
    $table->unsignedBigInteger('product_id');
    $table->foreign('product_id')
        ->references('id')
        ->on('products');
    $table->timestamps();
});

// Models
use Illuminate\Database\Eloquent\Relations;
use realtydev\LaravelSwagger\Traits\HasAppends;

class Order extends Model
{
    use HasAppends;

    protected $fillable = [
        'value',
    ];

    protected $casts = [
        'value' => 'float',
        'formatted_value' => 'string',
    ];

    protected $appends = [
        'formatted_value',
    ];

    public function getFormattedValueAttribute()
    {
        return '$ ' . $this->value;
    }

    public function product() : Relations\BelongsTo
    {
        return $this->belongsTo(Product::class);
    }
}

class Product extends Model
{
    protected $fillable = [
        'name',
        'price',
        'active',
    ];

    protected $hidden = [
        'active',
    ];

    protected $casts = [
        'name' => 'string',
        'price' => 'float',
        'active' => 'boolean',
    ];

    protected $dates = [
        'finished_at',
    ];

    public function orders() : Relations\HasMany
    {
        return $this->hasMany(Order::class);
    }
}

上面的结构将返回以下定义

{
  "Order": {
    "type": "object",
    "properties": {
      "id": {
        "type": "integer",
        "example": "1"
      },
      "value": {
        "type": "number",
        "format": "float",
        "example": "16.54"
      },
      "product_id": {
        "type": "string"
      },
      "customer_id": {
        "type": "string"
      },
      "created_at": {
        "type": "string",
        "format": "date-time"
      },
      "updated_at": {
        "type": "string",
        "format": "date-time"
      },
      "formatted_value": {
        "type": "string"
      },
      "product": {
        "$ref": "#/definitions/Product"
      }
    }
  },
  "Product": {
    "type": "object",
    "properties": {
      "id": {
        "type": "integer"
      },
      "name": {
        "type": "string"
      },
      "price": {
        "type": "number",
        "format": "float"
      },
      "finished_at": {
        "type": "string",
        "format": "date-time"
      },
      "created_at": {
        "type": "string",
        "format": "date-time"
      },
      "updated_at": {
        "type": "string",
        "format": "date-time"
      },
      "orders": {
        "type": "array",
        "items": {
          "$ref": "#/definitions/Order"
        }
      }
    }
  }
}

将使用属性 $casts 来定义属性 typeformat。如果没有定义任何类型转换,将使用默认类型 string

响应

响应将根据路由的 http 方法、@throws 注解和 auth 中间件来定义。例如

// Routes
Route::get('/customers', 'CustomerController@index')
    ->name('customers.index')
    ->middleware('auth:api');

Route::get('/customers', 'CustomerController@store')
    ->name('customers.store');

Route::put('/customers/{id}', 'CustomerController@update')
    ->name('customers.update');

// Controller
use App\Http\Requests\StoreCustomerRequest;
use App\Http\Requests\UpdateCustomerRequest;

/**
 * Class CustomerController
 * @model App\Customer
 */
class CustomerController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth:api');
    }

    public function index()
    {

    }

    public function store(StoreCustomerRequest $request)
    {

    }

    /**
     * @param int $id
     * @param UpdateCustomerRequest $request
     *
     * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
     * @throws \Illuminate\Auth\AuthenticationException
     * @throws \Illuminate\Auth\Access\AuthorizationException
     */
    public function update(int $id, UpdateCustomerRequest $request)
    {
        // ...
    }
}

上面的定义将生成以下响应

获取所有客户

{
  "responses": {
     "200": {
       "description": "OK",
       "schema": {
         "type": "array",
         "items": {
           "$ref": "#/definitions/Customer"
         }
       }
     },
     "401": {
       "description": "Unauthenticated"
     }
  }
}

存储客户

{
  "responses": {
     "201": {
       "description": "Created",
       "schema": {
         "$ref": "#/definitions/Customer"
       }
     },
     "422": {
       "description": "Validation errors"
     }
  }
}

更新客户

{
  "responses": {
    "204": {
      "description": "No Content"
    },
    "422": {
      "description": "Validation errors"
    },
    "404": {
      "description": "Model not found"
    },
    "401": {
      "description": "Unauthenticated"
    },
    "403": {
      "description": "Forbidden"
    }
  }
}