Laravel的代码生成器-用于OAS输入,用于JSON-API输出

为API创建代码生成器的能力,以便免于不断创建相同的控制器,模型,路由器,中间件,迁移,框架,过滤器,验证等的需求,从而节省了下一代。 手动(即使在熟悉且方便的框架中),对我来说似乎很有趣。

我研究了OpenAPI规范的典型性和精妙之处,我喜欢它的线性和能够描述1-3级深度的任何实体的结构和类型的能力。 从那时起,我就已经熟悉Laravel(Yii2以前曾使用它,CI,但它们不那么受欢迎)以及json-api数据输出格式-整个体系结构与连接图一起落在了我的脑海。



让我们继续示例。

假设我们在OAS中描述了以下实体:

ArticleAttributes: description: Article attributes description type: object properties: title: required: true type: string minLength: 16 maxLength: 256 facets: index: idx_title: index description: required: true type: string minLength: 32 maxLength: 1024 facets: spell_check: true spell_language: en url: required: false type: string minLength: 16 maxLength: 255 facets: index: idx_url: unique show_in_top: description: Show at the top of main page required: false type: boolean status: description: The state of an article enum: ["draft", "published", "postponed", "archived"] facets: state_machine: initial: ['draft'] draft: ['published'] published: ['archived', 'postponed'] postponed: ['published', 'archived'] archived: [] topic_id: description: ManyToOne Topic relationship required: true type: integer minimum: 1 maximum: 6 facets: index: idx_fk_topic_id: foreign references: id on: topic onDelete: cascade onUpdate: cascade rate: type: number minimum: 3 maximum: 9 format: double date_posted: type: date-only time_to_live: type: time-only deleted_at: type: datetime 

如果我们运行命令

 php artisan api:generate oas/openapi.yaml --migrations 

然后我们得到以下生成的对象:

1)实体控制器

 <?php namespace Modules\V1\Http\Controllers; class ArticleController extends DefaultController { } 

他已经知道GET / POST / PATCH / DELETE,为此他将通过模型进入表格,还将为其生成迁移。 DefaultController始终可供开发人员使用,因此可以为所有控制器实现功能。

2)文章实体模型

 <?php namespace Modules\V2\Entities; use Illuminate\Database\Eloquent\SoftDeletes; use rjapi\extension\BaseModel; class Article extends BaseModel { use SoftDeletes; // >>>props>>> protected $dates = ['deleted_at']; protected $primaryKey = 'id'; protected $table = 'article'; public $timestamps = false; public $incrementing = false; // <<<props<<< // >>>methods>>> public function tag() { return $this->belongsToMany(Tag::class, 'tag_article'); } public function topic() { return $this->belongsTo(Topic::class); } // <<<methods<<< } 


如您所见,注释出现在这里// >>>道具>>>和// >>>方法>>>-需要用它们才能将代码空间与用户代码空间分开。 还有一个标签/主题关系-belognsToMany / belongsTo,它们分别将Article实体与标签/主题联​​系起来,提供了通过单个GET请求在关系json-api中访问它们或通过更新文章来更改它们的机会。

3)实体迁移,并具有回滚支持(反射/原子性):

 <?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateArticleTable extends Migration { public function up() { Schema::create('article', function(Blueprint $table) { $table->bigIncrements('id'); $table->string('title', 256); $table->index('title', 'idx_title'); $table->string('description', 1024); $table->string('url', 255); $table->unique('url', 'idx_url'); // Show at the top of main page $table->unsignedTinyInteger('show_in_top'); $table->enum('status', ["draft","published","postponed","archived"]); // ManyToOne Topic relationship $table->unsignedInteger('topic_id'); $table->foreign('topic_id', 'idx_fk_topic_id')->references('id')->on('topic')->onDelete('cascade')->onUpdate('cascade'); $table->timestamps(); }); } public function down() { Schema::dropIfExists('article'); } } 

迁移生成器支持所有类型的索引(包括组合索引)。

4)滚动请求的路由器:

 // >>>routes>>> // Article routes Route::group(['prefix' => 'v2', 'namespace' => 'Modules\\V2\\Http\\Controllers'], function() { // bulk routes Route::post('/article/bulk', 'ArticleController@createBulk'); Route::patch('/article/bulk', 'ArticleController@updateBulk'); Route::delete('/article/bulk', 'ArticleController@deleteBulk'); // basic routes Route::get('/article', 'ArticleController@index'); Route::get('/article/{id}', 'ArticleController@view'); Route::post('/article', 'ArticleController@create'); Route::patch('/article/{id}', 'ArticleController@update'); Route::delete('/article/{id}', 'ArticleController@delete'); // relation routes Route::get('/article/{id}/relationships/{relations}', 'ArticleController@relations'); Route::post('/article/{id}/relationships/{relations}', 'ArticleController@createRelations'); Route::patch('/article/{id}/relationships/{relations}', 'ArticleController@updateRelations'); Route::delete('/article/{id}/relationships/{relations}', 'ArticleController@deleteRelations'); }); // <<<routes<<< 

不仅为基本查询创建了路由,还为与其他实体的关系创建了路由,这些查询将通过1个查询和扩展作为批量操作进行拉取,以便能够批量创建/更新/删除数据。

5)FormRequest用于预处理/验证请求:

 <?php namespace Modules\V1\Http\Requests; use rjapi\extension\BaseFormRequest; class ArticleFormRequest extends BaseFormRequest { // >>>props>>> public $id = null; // Attributes public $title = null; public $description = null; public $url = null; public $show_in_top = null; public $status = null; public $topic_id = null; public $rate = null; public $date_posted = null; public $time_to_live = null; public $deleted_at = null; // <<<props<<< // >>>methods>>> public function authorize(): bool { return true; } public function rules(): array { return [ 'title' => 'required|string|min:16|max:256|', 'description' => 'required|string|min:32|max:1024|', 'url' => 'string|min:16|max:255|', // Show at the top of main page 'show_in_top' => 'boolean', // The state of an article 'status' => 'in:draft,published,postponed,archived|', // ManyToOne Topic relationship 'topic_id' => 'required|integer|min:1|max:6|', 'rate' => '|min:3|max:9|', 'date_posted' => '', 'time_to_live' => '', 'deleted_at' => '', ]; } public function relations(): array { return [ 'tag', 'topic', ]; } // <<<methods<<< } 

这里的一切都很简单-生成用于验证属性和关系的规则,以将主实体与Relations方法中的实体链接起来。

最后,最好的部分是查询示例:

 http://example.com/v1/article?include=tag&page=2&limit=10&sort=asc 

显示文章,按关系固定所有标签,在第2页上进行分页,限制为10,然后按年龄排序。

如果我们不需要显示文章的所有字段:

 http://example.com/v1/article/1?include=tag&data=["title", "description"] 

按多个字段排序:

 http://example.com/v1/article/1?include=tag&order_by={"title":"asc", "created_at":"desc"} 

过滤(或属于WHERE子句的任何内容):

 http://example.com/v1/article?include=tag&filter=[["updated_at", ">", "2018-01-03 12:13:13"], ["updated_at", "<", "2018-09-03 12:13:15"]] 

创建实体的示例(在本例中为文章):

 POST http://laravel.loc/v1/article { "data": { "type":"article", "attributes": { "title":"Foo bar Foo bar Foo bar Foo bar", "description":"description description description description description", "fake_attr": "attr", "url":"title title bla bla bla", "show_in_top":1 } } } 

答案是:

 { "data": { "type": "article", "id": "1", "attributes": { "title": "Foo bar Foo bar Foo bar Foo bar", "description": "description description description description description", "url": "title title bla bla bla", "show_in_top": 1 }, "links": { "self": "laravel.loc/article/1" } } } 

看到链接->自我中的链接? 你可以立即
 GET http://laravel.loc/article/1 
或保存以供将来参考。

 GET http://laravel.loc/v1/article?include=tag&filter=[["updated_at", ">", "2017-01-03 12:13:13"], ["updated_at", "<", "2019-01-03 12:13:15"]] { "data": [ { "type": "article", "id": "1", "attributes": { "title": "Foo bar Foo bar Foo bar Foo bar 1", "description": "The quick brovn fox jumped ower the lazy dogg", "url": "http://example.com/articles_feed 1", "show_in_top": 0, "status": "draft", "topic_id": 1, "rate": 5, "date_posted": "2018-02-12", "time_to_live": "10:11:12" }, "links": { "self": "laravel.loc/article/1" }, "relationships": { "tag": { "links": { "self": "laravel.loc/article/1/relationships/tag", "related": "laravel.loc/article/1/tag" }, "data": [ { "type": "tag", "id": "1" } ] } } } ], "included": [ { "type": "tag", "id": "1", "attributes": { "title": "Tag 1" }, "links": { "self": "laravel.loc/tag/1" } } ] } 

他返回了一个对象列表,其中每个对象的类型,其ID,整个属性集,然后是其自身的链接,通过include =标记在url中请求的关系根据规范,包括关系没有任何限制,例如include =标记,主题,城市及其所有内容都将包含在关系块中,而它们的对象将存储在include中

如果我们想获得1条文章及其所有关系/关系:

 GET http://laravel.loc/v1/article/1?include=tag&data=["title", "description"] { "data": { "type": "article", "id": "1", "attributes": { "title": "Foo bar Foo bar Foo bar Foo bar 123456", "description": "description description description description description 123456", }, "links": { "self": "laravel.loc/article/1" }, "relationships": { "tag": { "links": { "self": "laravel.loc/article/1/relationships/tag", "related": "laravel.loc/article/1/tag" }, "data": [ { "type": "tag", "id": "3" }, { "type": "tag", "id": "1" }, { "type": "tag", "id": "2" } ] } } }, "included": [ { "type": "tag", "id": "3", "attributes": { "title": "Tag 4" }, "links": { "self": "laravel.loc/tag/3" } }, { "type": "tag", "id": "1", "attributes": { "title": "Tag 2" }, "links": { "self": "laravel.loc/tag/1" } }, { "type": "tag", "id": "2", "attributes": { "title": "Tag 3" }, "links": { "self": "laravel.loc/tag/2" } } ] } 


这是一个向现有实体添加关系的示例-一个请求:

 PATCH http://laravel.loc/v1/article/1/relationships/tag { "data": { "type":"article", "id":"1", "relationships": { "tag": { "data": [{ "type": "tag", "id": "2" },{ "type": "tag", "id": "3" }] } } } } 

答案是:

 { "data": { "type": "article", "id": "1", "attributes": { "title": "Foo bar Foo bar Foo bar Foo bar 1", "description": "The quick brovn fox jumped ower the lazy dogg", "url": "http://example.com/articles_feed 1", "show_in_top": 0, "status": "draft", "topic_id": 1, "rate": 5, "date_posted": "2018-02-12", "time_to_live": "10:11:12" }, "links": { "self": "laravel.loc/article/1" }, "relationships": { "tag": { "links": { "self": "laravel.loc/article/1/relationships/tag", "related": "laravel.loc/article/1/tag" }, "data": [ { "type": "tag", "id": "2" }, { "type": "tag", "id": "3" } ] } } }, "included": [ { "type": "tag", "id": "2", "attributes": { "title": "Tag 2" }, "links": { "self": "laravel.loc/tag/2" } }, { "type": "tag", "id": "3", "attributes": { "title": "Tag 3" }, "links": { "self": "laravel.loc/tag/3" } } ] } 

您可以将其他选项转移到控制台生成器:

 php artisan api:generate oas/openapi.yaml --migrations --regenerate --merge=last 

因此,您告诉生成器-创建带有迁移的代码(您已经看到了)并重新生成代码,以保存的历史记录中的最新更改存储它,而不会影响代码的自定义部分,而只影响那些自动生成的部分(即,并在代码中用特殊注释突出显示特殊块)。 可以指定后退步骤,例如:-- merge = 9 (将生成后退9步),代码生成日期为过去--merge =“ 2017-07-29 11:35:32”

该库的一位用户建议为查询生成功能测试-通过添加--tests选项,您可以运行测试以确保您的API正常运行而不会出错。

此外,您可以使用许多选项(它们均通过配置器灵活配置,配置器位于生成的模块中,例如: /Modules/V2/Config/config.php ):

 <?php return [ 'name' => 'V2', 'query_params'=> [ //    - 'limit' => 15, 'sort' => 'desc', 'access_token' => 'db7329d5a3f381875ea6ce7e28fe1ea536d0acaf', ], 'trees'=> [ //      'menu' => true, ], 'jwt'=> [ // jwt  'enabled' => true, 'table' => 'user', 'activate' => 30, 'expires' => 3600, ], 'state_machine'=> [ // finite state machine 'article'=> [ 'status'=> [ 'enabled' => true, 'states'=> [ 'initial' => ['draft'], 'draft' => ['published'], 'published' => ['archived', 'postponed'], 'postponed' => ['published', 'archived'], 'archived' => [''], ], ], ], ], 'spell_check'=> [ //       'article'=> [ 'description'=> [ 'enabled' => true, 'language' => 'en', ], ], ], 'bit_mask'=> [ //     permissions ( true/false /  ) 'user'=> [ 'permissions'=> [ 'enabled' => true, 'flags'=> [ 'publisher' => 1, 'editor' => 2, 'manager' => 4, 'photo_reporter' => 8, 'admin' => 16, ], ], ], ], 'cache'=> [ //    'tag'=> [ 'enabled' => false, //    tag  'stampede_xfetch' => true, 'stampede_beta' => 1.1, 'ttl' => 3600, ], 'article'=> [ 'enabled' => true, //    article  'stampede_xfetch' => true, 'stampede_beta' => 1.5, 'ttl' => 300, ], ], ]; 

自然,如有必要,所有配置都可以点开/关。 有关代码生成器其他功能的更多信息,请参见下面的链接。 始终欢迎捐款。

感谢您的关注,并取得了成功。

文章资源:

Source: https://habr.com/ru/post/zh-CN427741/


All Articles