alirah / laravel-rest
此包用于简化构建Laravel RESTful API。
Requires
- php: >=7.1
- illuminate/support: >=5.8
This package is auto-updated.
Last update: 2024-09-23 05:36:25 UTC
README
这是一个用于简化构建RESTful API的Laravel包。本包支持Laravel 5.8+
。
使用本包,您只需一个命令即可构建RESTful API所需的所有内容。例如:控制器、资源、请求、模型、迁移、种子、工厂、路由、测试和Swagger。此外,您还可以删除它们。
内容列表
安装
通过Composer
$ composer require alirah/laravel-rest
配置
如果您使用的是Laravel 5.5
或更高版本,则无需添加提供者和别名。(跳到b)
a. 在您的config/app.php
文件中添加以下两行。
// In your providers array. 'providers' => [ ... Alirah\LaravelRest\Provider\LaravelRestServiceProvider::class, ], // In your aliases array. 'aliases' => [ ... 'Rest' => Alirah\LaravelRest\Facade\Rest::class, ],
b. 然后运行以下命令以发布配置目录中的config/laravel-rest.php
文件:
$ php artisan vendor:publish --provider="Alirah\LaravelRest\Provider\LaravelRestServiceProvider" --tag="config"
运行命令后,您可以设置所需的配置。
// to use swagger you have to install darkaonline/l5-swagger 'swagger' => false, 'swagger_route_prefix' => 'api', 'model' => true, 'migration' => true, 'factory_seeder' => true, 'test' => true, 'route' => true, // file in the routes' folder // if you have another folder in routes use this pattern: v1/api.php 'route_path' => 'api.php'
Swagger
要使用Swagger,您需要按照以下步骤操作
a. 运行此命令
$ composer require darkaonline/l5-swagger
b. 接下来,从服务提供者发布配置/views
$ php artisan vendor:publish --provider "L5Swagger\L5SwaggerServiceProvider"
c. 将以下代码复制并粘贴到App\Http\Controller\Controller类中控制器类的顶部
/** * @OA\Info ( * title="Laravel Rest Swagger", * version="1.0.0", * ) * * @OA\Get( * path="/", * description="Home page", * @OA\Response(response="200", description="Home Page") * ) */
最后,您的控制器必须像这样
<?php namespace App\Http\Controllers; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Routing\Controller as BaseController; /** * @OA\Info ( * title="Laravel Rest Swagger", * version="1.0.0", * ) * * @OA\Get( * path="/", * description="Home page", * @OA\Response(response="200", description="Home Page") * ) */ class Controller extends BaseController { use AuthorizesRequests, DispatchesJobs, ValidatesRequests; }
d. 在您的config\laravel-rest.php中将swagger字段设置为true
'swagger' => true, 'swagger_route_prefix' => 'api',
e. 要生成您的Swagger,请运行以下命令
$ php artisan l5-swagger:generate
默认情况下,Swagger路由为'api/documentation',但您可以在config/l5-swagger.php中更改它。要查看完整文档,请检查https://github.com/DarkaOnLine/L5-Swagger。
测试
为了获得更好的结果,我们建议在测试中使用sqlite数据库。要使用sqlite,请按照以下步骤操作
a. 首先,在根目录下的phpunit.xml中取消注释DB_CONNECTION行
<php> <env name="APP_ENV" value="testing"/> <env name="BCRYPT_ROUNDS" value="4"/> <env name="CACHE_DRIVER" value="array"/> <env name="DB_CONNECTION" value="sqlite"/> <!-- <env name="DB_DATABASE" value=":memory:"/>--> <env name="MAIL_MAILER" value="array"/> <env name="QUEUE_CONNECTION" value="sync"/> <env name="SESSION_DRIVER" value="array"/> <env name="TELESCOPE_ENABLED" value="false"/> </php>
b. 更改config/database.php中的sqlite数据库路径
'connections' => [ 'sqlite' => [ 'driver' => 'sqlite', 'url' => env('DATABASE_URL'), 'database' => database_path('database.sqlite'), 'prefix' => '', 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), ], ... ]
c. 在数据库文件夹中创建一个名为database.sqlite的文件
d. 要运行您的测试,请运行以下命令
$ php artisan test //or $ .\vendor\bin\phpunit
如何使用
创建
要创建REST资源,您应该运行以下命令
$ php artisan rest:make ModelName
此命令创建以下文件
- app\Models\ModelName.php
- app\Controllers\ModelName\ModelNameController.php
- app\Request\ModelName\StoreRequest.php
- app\Request\ModelName\UpdateRequest.php
- app\Resource\ModelName\ModelNameResource.php
- database/migrations/...model_names.php
- database/factories/ModelNameFactory.php
- database/seeders/ModelNameSeeder.php
- tests/Feature/ModelName/ModelNameTest.php
- 并在./routes/api.php的末尾添加以下行
... Route::apiResource('modelNames', \App\Http\Controllers\ModelName\ModelNameController::class);
- 您可以在config中更改api.php文件。
- 要覆盖具有相同名称的文件,需要在cmd中具有您的权限。
- 您可以使用-F或--force标志强制执行。
删除
要删除REST资源,您应该运行以下命令
$ php artisan rest:delete ModelName
此命令删除以下文件
- app\Models\ModelName.php
- app\Controllers\ModelName\ModelNameController.php
- app\Request\ModelName\StoreRequest.php
- app\Request\ModelName\UpdateRequest.php
- app\Resource\ModelName\ModelNameResource.php
- database/migrations/...model_names.php
- database/factories/ModelNameFactory.php
- database/seeders/ModelNameSeeder.php
- tests/Feature/ModelName/ModelNameTest.php
- 并从./routes/api.php的末尾删除以下行
... Route::apiResource('modelNames', \App\Http\Controllers\ModelName\ModelNameController::class);
- 您可以在config中更改api.php文件。要删除所有文件,需要在cmd中具有您的权限。
- 您可以使用-F或--force标志强制执行。
版本化
对于版本化,您可以在配置中为Swagger路由添加前缀
..., 'swagger_route_prefix' => 'api', ...
版本化创建
要创建版本资源,您可以运行
$ php artisan rest:make V1\ModelName
此命令创建以下文件
- app\Models\ModelName.php
- app\Controllers\V1\ModelName\ModelNameController.php
- app\Request\V1\ModelName\StoreRequest.php
- app\Request\V1\ModelName\UpdateRequest.php
- app\Resource\V1\ModelName\ModelNameResource.php
- database/migrations/...model_names.php
- database/factories/ModelNameFactory.php
- database/seeders/ModelNameSeeder.php
- tests/Feature/V1/ModelName/ModelNameTest.php
版本化删除
要删除版本资源,您可以运行
$ php artisan rest:delete V1\ModelName
此命令删除以下文件
- app\Models\ModelName.php
- app\Controllers\V1\ModelName\ModelNameController.php
- app\Request\V1\ModelName\StoreRequest.php
- app\Request\V1\ModelName\UpdateRequest.php
- app\Resource\V1\ModelName\ModelNameResource.php
- database/migrations/...model_names.php
- database/factories/ModelNameFactory.php
- database/seeders/ModelNameSeeder.php
- tests/Feature/V1/ModelName/ModelNameTest.php
Rest Facade
Rest门面用于在控制器中返回JsonResource。
/** * @return JsonResponse */ public function index(): JsonResponse { $users = User::paginate(20); return Rest::ok(UserResource::collection($users)); }
Rest门面的可用方法:
ok($data)
:以200状态码返回$dataaccepted($data)
:以202状态码返回$databadRequest($data)
:以400状态码返回$dataunauthorized($data)
:以401状态码返回$dataforbidden($data)
:以403状态码返回$datanotFound($data)
:以404状态码返回$dataerror($data)
:以500状态码返回$datacustom($data, $statusCode)
:以$statusCode状态码返回$data
$data应该是Laravel资源或数组
示例
当启用swagger并运行php artisan rest:make Blog
时,这是结果
- 填写所有必填(TODO)字段后,您可以通过运行
php artisan test
进行测试,并通过运行php artisan l5-swagger:generate
生成swagger - 如果您未更改config/l5-swagger.php中的默认路由,您可以在“api/documentation”中访问Swagger输出
app\Models\Blog.php
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Blog extends Model { use HasFactory; protected $guarded = [ 'id' ]; }
app\Http\Controllers\Blog\BlogController.php
<?php namespace App\Http\Controllers\Blog; use Alirah\LaravelRest\Facade\Rest; use App\Http\Controllers\Controller; use App\Http\Request\Blog\StoreRequest; use App\Http\Request\Blog\UpdateRequest; use Illuminate\Http\JsonResponse; use App\Models\Blog; use App\Http\Resource\Blog\BlogResource; class BlogController extends Controller { /** * @OA\Get( * path="/api/blogs", * operationId="getBlogsList", * tags={"Blogs"}, * summary="Get list of blogs", * description="Returns list of blogs", * @OA\Response( * response=200, * description="Successful operation", * @OA\JsonContent * ), * @OA\Response( * response=401, * description="Unauthenticated", * @OA\JsonContent * ), * @OA\Response( * response=403, * description="Forbidden", * @OA\JsonContent * ) * ) */ public function index(): JsonResponse { $blogs = Blog::paginate(20); // TODO handle query return Rest::ok(BlogResource::collection($blogs)); } /** * @OA\Post( * path="/api/blogs", * operationId="storeBlog", * tags={"Blogs"}, * summary="Store new blog", * description="Returns blog data", * @OA\RequestBody( * required=true, * ), * @OA\Response( * response=202, * description="Successful operation", * @OA\JsonContent * ), * @OA\Response( * response=400, * description="Bad Request", * @OA\JsonContent * ), * @OA\Response( * response=401, * description="Unauthenticated", * @OA\JsonContent * ), * @OA\Response( * response=403, * description="Forbidden", * @OA\JsonContent * ) * ) */ public function store(StoreRequest $request): JsonResponse { $item = Blog::create($request->validated()); return Rest::accepted(new BlogResource($item)); } /** * @OA\Get( * path="/api/blogs/{id}", * operationId="getBlogById", * tags={"Blogs"}, * summary="Get blog information", * description="Returns blog data", * @OA\Parameter( * name="id", * description="Blog id", * required=true, * in="path", * @OA\Schema( * type="integer" * ) * ), * @OA\Response( * response=200, * description="Successful operation", * @OA\JsonContent * ), * @OA\Response( * response=400, * description="Bad Request", * @OA\JsonContent * ), * @OA\Response( * response=401, * description="Unauthenticated", * @OA\JsonContent * ), * @OA\Response( * response=403, * description="Forbidden", * @OA\JsonContent * ), * @OA\Response( * response=404, * description="Blog Not Found", * @OA\JsonContent * ) * ) */ public function show(Blog $blog): JsonResponse { // you can load relationships by using // $blog->load('relation-1', 'relation-2'); return Rest::ok(new BlogResource($blog)); } /** * @OA\Put( * path="/api/blogs/{id}", * operationId="updateBlog", * tags={"Blogs"}, * summary="Update existing blog", * description="Returns updated blog data", * @OA\Parameter( * name="id", * description="Blog id", * required=true, * in="path", * @OA\Schema( * type="integer" * ), * ), * @OA\RequestBody( * required=true, * ), * @OA\Response( * response=202, * description="Successful operation", * @OA\JsonContent * ), * @OA\Response( * response=400, * description="Bad Request", * @OA\JsonContent * ), * @OA\Response( * response=401, * description="Unauthenticated", * @OA\JsonContent * ), * @OA\Response( * response=403, * description="Forbidden", * @OA\JsonContent * ), * @OA\Response( * response=404, * description="Blog Not Found", * @OA\JsonContent * ) * ) */ public function update(UpdateRequest $request, Blog $blog): JsonResponse { // TODO handle updated fields $blog->update($request->only('')); return Rest::accepted(new BlogResource($blog)); } /** * @OA\Delete( * path="/api/blogs/{id}", * operationId="deleteBlog", * tags={"Blogs"}, * summary="Delete existing blog", * description="Deletes a record and returns no content", * @OA\Parameter( * name="id", * description="Blog id", * required=true, * in="path", * @OA\Schema( * type="integer" * ) * ), * @OA\Response( * response=202, * description="Successful operation", * @OA\JsonContent * ), * @OA\Response( * response=401, * description="Unauthenticated", * @OA\JsonContent * ), * @OA\Response( * response=403, * description="Forbidden", * @OA\JsonContent * ), * @OA\Response( * response=404, * description="Blog Not Found", * @OA\JsonContent * ) * ) */ public function destroy(Blog $blog): JsonResponse { $blog->delete(); return Rest::accepted([ 'message' => 'blog deleted successfully' ]); } }
app\Http\Request\Blog\StoreRequest.php
<?php namespace App\Http\Request\Blog; use Illuminate\Foundation\Http\FormRequest; class StoreRequest extends FormRequest { /** * Determine if the user is authorized to make this request. * * @return bool */ public function authorize(): bool { return true; } protected function prepareForValidation() { // } /** * Get the validation rules that apply to the request. * * @return array */ public function rules(): array { // TODO validation return [ // ]; } }
app\Http\Request\Blog\UpdateRequest.php
<?php namespace App\Http\Request\Blog; use Illuminate\Foundation\Http\FormRequest; class UpdateRequest extends FormRequest { /** * Determine if the user is authorized to make this request. * * @return bool */ public function authorize(): bool { return true; } protected function prepareForValidation() { // } /** * Get the validation rules that apply to the request. * * @return array */ public function rules(): array { // TODO validation return [ // ]; } }
app\Http\Resource\Blog\BlogResource.php
<?php namespace App\Http\Resource\Blog; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; use JsonSerializable; class BlogResource extends JsonResource { /** * Transform the resource into an array. * * @param Request $request * @return array|Arrayable|JsonSerializable */ public function toArray($request): array|JsonSerializable|Arrayable { // TODO return Blog fields return [ 'id' => $this->id, // your fields 'createdAt' => $this->created_at ]; } }
database\migrations\...create_blogs_table.php
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { /** * Run the migrations. * * @return void */ public function up() { if (!Schema::hasTable('blogs')) { Schema::create('blogs', function (Blueprint $table) { $table->id(); // TODO table fields $table->timestamps(); }); } } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('blogs'); } };
database\factories\BlogFactory.php
<?php namespace Database\Factories; use Illuminate\Database\Eloquent\Factories\Factory; /** * @extends Factory */ class BlogFactory extends Factory { /** * Define the model's default state. * * @return array */ public function definition(): array { // TODO factory fields return [ // for example: 'title' => $this->faker->sentence(1), ]; } }
database\seeders\BlogSeeder.php
<?php namespace Database\Seeders; use App\Models\Blog; use Illuminate\Database\Seeder; class BlogSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { // TODO handle factory // TODO you should add it to DatabaseSeeder // for example: // public function run() // { // $this->call([ // BlogSeeder::class // ]); // } Blog::factory(10)->create(); } }
tests\Feature\Blog\BlogTests.php
<?php namespace Tests\Feature\Blog; use App\Models\Blog; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; use Symfony\Component\HttpFoundation\Response; use Tests\TestCase; class BlogTest extends TestCase { use RefreshDatabase; protected array $dataStruct; protected array $testData; protected Blog $blog; public function setUp(): void { parent::setUp(); $this->dataStruct = [ 'id', // TODO Enter fields that return from BlogResource // e.g title 'createdAt' ]; $this->testData = [ // TODO Enter test data for store and update methods // e.g 'title' => 'title' ]; Blog::factory(10)->create(); $this->blog = Blog::inRandomOrder()->first(); } public function test_index() { $response = $this->json('get', '/api/blogs'); $response->assertStatus(Response::HTTP_OK) ->assertJsonStructure([ 'data' => [ $this->dataStruct ] ]); } public function test_store() { $response = $this->json('post', '/api/blogs', $this->testData); $response->assertStatus(Response::HTTP_ACCEPTED) ->assertJsonStructure([ 'data' => $this->dataStruct ])->assertJson([ 'data' => $this->testData ]); } public function test_show() { $response = $this->json('get', "/api/blogs/{$this->blog->id}"); $response->assertStatus(Response::HTTP_OK) ->assertJsonStructure([ 'data' => $this->dataStruct ]); } public function test_update() { $response = $this->json('put', "/api/blogs/{$this->blog->id}", $this->testData); $response->assertStatus(Response::HTTP_ACCEPTED) ->assertJsonStructure([ 'data' => $this->dataStruct ])->assertJson([ 'data' => $this->testData ]); } public function test_delete() { $response = $this->json('delete', "/api/blogs/{$this->blog->id}"); $response->assertStatus(Response::HTTP_ACCEPTED); } }
routes/api.php
<?php use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; . .. ... Route::apiResource('blogs', \App\Http\Controllers\Blog\BlogController::class);
变更日志
有关最近更改的更多信息,请参阅变更日志。
安全性
如果您发现任何安全相关的问题,请通过电子邮件alirahgoshy@gmail.com报告,而不是使用问题跟踪器。
致谢
许可证
MIT许可(MIT)。有关更多信息,请参阅许可文件。