alirah / laravel-rest

此包用于简化构建Laravel RESTful API。

v1.0.0 2022-02-22 23:35 UTC

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状态码返回$data
  • accepted($data):以202状态码返回$data
  • badRequest($data):以400状态码返回$data
  • unauthorized($data):以401状态码返回$data
  • forbidden($data):以403状态码返回$data
  • notFound($data):以404状态码返回$data
  • error($data):以500状态码返回$data
  • custom($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)。有关更多信息,请参阅许可文件