Stori.
Laravel API - Phần 2
5 min read

Laravel API - Phần 2

APILaravelPHP

Xử lý Posts

1. Tạo Model

  • Sử dụng command make model để tạo tất cả các thành phần liên quan (model, migration, factory, seeder, policy, controller, ... )
  • php artisan make:model Post --all
  • Cập nhật migration file trong thư mục  ”database/migrations”
  • //database\migrations\xxxx_xx_xx_xxxxxx_create_posts_table.php
    public function up()
        {
            Schema::create('posts', function (Blueprint $table) {
                $table->uuid('id')->primary();
                $table->uuid('user_id')->constrained();
                $table->string('title');
                $table->string('thumbnail')->nullable();
                $table->string('slug')->unique();
                $table->text('content');
                $table->text('summary');
                $table->boolean('published')->default(false);
                $table->timestamp('published_at')->nullable();
                $table->timestamps();
            });
        }
  • Cập nhật Model fillable property trong “app/models/Post.php”
  • //app\Models\Post.php
    protected $fillable = [
            'user_id',
            'title',
            'thumbnail',
            'slug',
            'content',
            'summary',
            'published',
            'published_at',
        ];
    
        public function user()
        {
            return $this->belongsTo(User::class);
        }

    2. Thêm quan hệ trong model User

    //app\Models\User.php
    public function posts()
        {
            return $this->hasMany(Post::class);
        }

    3. Thay đổi thư mục

  • Di chuyển 2 file “StorePostRequest.php” và “UpdatePostRequest.php” vào trong thư mục “app\Http\Requests\Post”, điều chỉnh namespace của 2 file này. Điều này để cấu trúc thư mục khoa học hơn, Khi đó các file Request của Post sẽ tìm thấy trong “app\Http\Requests\Post”
  • //app\Http\Requests\Post\StorePostRequest.php (UpdatePostRequest.php)
    namespace App\Http\Requests\Post;
  • Cập nhật namspace trong PostController
  • //app\Http\Controllers\PostController.php
    use App\Http\Requests\Post\StorePostRequest;
    use App\Http\Requests\Post\UpdatePostRequest;

    4. Factory

  • Truy cập file “PostFactory.php”
  • Thiết lập dữ liệu ảo bằng faker
  • Faker formatters: https://github.com/fzaninotto/Faker

    //database\factories\PostFactory.php
    public function definition()
        {
            return [
                'user_id' => User::factory(),
                'title' => $this->faker->sentence,
                'thumbnail' => $this->faker->imageUrl(640, 480),
                'slug' => $this->faker->slug,
                'content' => $this->faker->paragraphs(3, true),
                'summary' => $this->faker->paragraph,
                'published' => $this->faker->boolean,
                'published_at' => $this->faker->dateTimeThisMonth(),
            ];
        }

    5. Policy

  • Truy cập file “PostPolicy.php”
  • Cập nhật logic trong PostPolicy
  • //app\Policies\PostPolicy.php
    class PostPolicy
    {
        use HandlesAuthorization;
    
        /**
         * Determine whether the user can view any models.
         *
         * @param  \App\Models\User  $user
         * @return \Illuminate\Auth\Access\Response|bool
         */
        public function viewAny(User $user)
        {
            return true;
        }
    
        /**
         * Determine whether the user can view the model.
         *
         * @param  \App\Models\User  $user
         * @param  \App\Models\API\V1\Post  $post
         * @return \Illuminate\Auth\Access\Response|bool
         */
        public function view(User $user, Post $post)
        {
            return true;
        }
    
        /**
         * Determine whether the user can create models.
         *
         * @param  \App\Models\User  $user
         * @return \Illuminate\Auth\Access\Response|bool
         */
        public function create(User $user)
        {
            return true;
        }
    
        /**
         * Determine whether the user can update the model.
         *
         * @param  \App\Models\User  $user
         * @param  \App\Models\API\V1\Post  $post
         * @return \Illuminate\Auth\Access\Response|bool
         */
        public function update(User $user, Post $post)
        {
            return $user->id === $post->user_id;
        }
    
        /**
         * Determine whether the user can delete the model.
         *
         * @param  \App\Models\User  $user
         * @param  \App\Models\API\V1\Post  $post
         * @return \Illuminate\Auth\Access\Response|bool
         */
        public function delete(User $user, Post $post)
        {
            return $user->id === $post->user_id;
        }
    
        /**
         * Determine whether the user can restore the model.
         *
         * @param  \App\Models\User  $user
         * @param  \App\Models\API\V1\Post  $post
         * @return \Illuminate\Auth\Access\Response|bool
         */
        public function restore(User $user, Post $post)
        {
            return false;
        }
    
        /**
         * Determine whether the user can permanently delete the model.
         *
         * @param  \App\Models\User  $user
         * @param  \App\Models\API\V1\Post  $post
         * @return \Illuminate\Auth\Access\Response|bool
         */
        public function forceDelete(User $user, Post $post)
        {
            return false;
        }
    }
  • Khai báo PostPolicy trong AuthServiceProvider.php
  • //app\Providers\AuthServiceProvider.php
    protected $policies = [
            // 'App\Models\Model' => 'App\Policies\ModelPolicy',
            Post::class => PostPolicy::class,
        ];

    6. Resquest

  • Truy cập lại 2 file “StorePostRequest.php” và “UpdatePostRequest.php”
  • Config rule và thêm hàm failedValidation() để trả về lỗi khi request không hợp lệ
  • //app\Http\Requests\Post\StorePostRequest.php
    class StorePostRequest extends FormRequest
    {
        use ApiResponse;
        /**
         * Determine if the user is authorized to make this request.
         *
         * @return bool
         */
        public function authorize()
        {
            return true;
        }
    
        /**
         * Get the validation rules that apply to the request.
         *
         * @return array<string, mixed>
         */
        public function rules()
        {
            return [
                'title' => 'required|string|max:255',
                'user_id' => 'required',
                'slug' => 'required',
                'summary' => 'required',
                'content' => 'required|string',
                'thumbnail' => 'required|url',
                'published' => 'required|boolean',
                'published_at' => 'required',
            ];
        }
    
        public function failedValidation(Validator $validator)
        {
            throw new HttpResponseException(
                $this->validationErrorResponse(
                    'Validation failed',
                    $validator->errors()
                )
            );
        }
    }
    //app\Http\Requests\Post\UpdatePostRequest.php
    class UpdatePostRequest extends FormRequest
    {
        use ApiResponse;
    
        /**
         * Determine if the user is authorized to make this request.
         *
         * @return bool
         */
        public function authorize()
        {
            return true;
        }
    
        /**
         * Get the validation rules that apply to the request.
         *
         * @return array<string, mixed>
         */
        public function rules()
        {
            return [
                'title' => 'sometimes|required|string|max:255',
                'content' => 'sometimes|required|string',
                'thumbnail' => 'sometimes|required|url',
                'published' => 'sometimes|required|boolean',
            ];
        }
    
        public function failedValidation(Validator $validator)
        {
            throw new HttpResponseException(
                $this->validationErrorResponse(
                    'Validation failed',
                    $validator->errors()
                )
            );
        }
    }

    7. Service

    Tất cả các function xử lý logic nên được viết trong file service, và được gọi lại trong controller.

  • Tạo file “PostService.php” trong “app\Services”
  • Viết function logic trong PostService
  • <?php
    namespace App\Services;
    
    class PostService
    {
        public function getData()
        {
            // Logic
        }
    }
  • Khai báo PostService trong “AppServiceProvider.php”
  • //app\Providers\AppServiceProvider.php
    /**
         * Register any application services.
         *
         * @return void
         */
        public function register()
        {
            ...
    
            $this->app->bind(PostService::class, function ($app) {
                return new PostService();
            });
        }

    8. Resource & Collection

  • Tạo 2 file PostResource và PostCollection
  • php artisan make:resource PostCollection
    php artisan make:resource PostResource
  • Tùy chỉnh các giá trị trả về khi có response
  • //app\Http\Resources\PostResource.php
    class PostResource extends JsonResource
    {
        /**
         * Transform the resource into an array.
         *
         * @param  \Illuminate\Http\Request  $request
         * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
         */
        public function toArray($request)
        {
            return [
                'id' => $this->id,
                'title' => $this->title,
                'user_id' => $this->user_id,
                'content' => $this->content,
                'summary' => $this->summary,
                'thumbnail' => $this->thumbnail,
                'slug' => $this->slug,
                'published' => $this->published,
                'published_at' => $this->published_at,
                'created_at' => $this->created_at,
                'updated_at' => $this->updated_at,
            ];
        }
    }
    //app\Http\Resources\PostCollection.php
    class PostCollection extends ResourceCollection
    {
        /**
         * Transform the resource collection into an array.
         *
         * @param  \Illuminate\Http\Request  $request
         * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
         */
        public function toArray($request)
        {
            return [
                'posts' => PostResource::collection($this->collection),
                'meta' => [
                    'total' => $this->total(),
                    'per_page' => $this->perPage(),
                    'current_page' => $this->currentPage(),
                    'last_page' => $this->lastPage(),
                    'from' => $this->firstItem(),
                    'to' => $this->lastItem(),
                ],
            ];
        }
    }

    9. Controller

  • Truy cập file “PostController.php”
  • Xóa 2 hàm create(), edit() - 2 hàm này không cần dùng đến trong api
  • Code logic trong các hàm còn lại
  • //app\Http\Controllers\PostController.php
    <?php
    
    namespace App\Http\Controllers;
    
    use Illuminate\Support\Facades\Auth;
    use Illuminate\Http\Request;
    
    use App\Traits\ApiResponse;
    
    use App\Models\Post;
    
    use App\Http\Requests\Post\StorePostRequest;
    use App\Http\Requests\Post\UpdatePostRequest;
    
    use App\Http\Resources\PostCollection;
    use App\Http\Resources\PostResource;
    
    use App\Services\PostService;
    
    class PostController extends Controller
    {
        use ApiResponse;
    
        private $postService;
    
        public function __construct(PostService $postService)
        {
            $this->postService = $postService;
        }
        
        /**
         * Display a listing of the resource.
         *
         * @return \Illuminate\Http\Response
         */
        public function index(Request $request)
        {
            $posts = Post::with('user')->paginate($request->get('per_page', 15));
            return $this->successResponse(
                new PostCollection($posts),
                'Posts retrieved successfully'
            );
        }
    
        /**
         * Store a newly created resource in storage.
         *
         * @param  \App\Http\Requests\StorePostRequest  $request
         * @return \Illuminate\Http\Response
         */
        public function store(StorePostRequest $request)
        {
            $validated = $request->validated();
            $post = new Post($validated);
            $post->save();
    
            return $this->createdResponse(
                new PostResource($post),
                'Post created successfully'
            );
        }
    
        /**
         * Display the specified resource.
         *
         * @param  $id
         * @return \Illuminate\Http\Response
         */
        public function show($id)
        {
            $post = Post::with('user')->find($id);
    
            if (!$post) {
                return $this->notFoundResponse('Post not found');
            }
    
            if (!Auth::user()->can('view', $post)) {
                return $this->unauthorizedResponse('You do not own this post.');
            }
    
            return $this->successResponse(
                new PostResource($post),
                'Post retrieved successfully'
            );
        }
    
    
        /**
         * Update the specified resource in storage.
         *
         * @param  \App\Http\Requests\UpdatePostRequest  $request
         * @param  $id
         * @return \Illuminate\Http\Response
         */
        public function update(UpdatePostRequest $request, $id)
        {
            $post = Post::find($id);
            if (!$post) {
                return $this->notFoundResponse('Post not found');
            }
    
            if (!Auth::user()->can('update', $post)) {
                return $this->unauthorizedResponse('You do not own this post.');
            }
    
            $validated = $request->validated();
            $post->fill($validated);
            $post->save();
    
            return $this->successResponse(
                new PostResource($post),
                'Post updated successfully'
            );
        }
    
        /**
         * Remove the specified resource from storage.
         *
         * @param  $id
         * @return \Illuminate\Http\Response
         */
        public function destroy($id)
        {
            $post = Post::find($id);
            if (!$post) {
                return $this->notFoundResponse('Post not found');
            }
            $post->delete();
            return $this->deletedResponse('Post deleted successfully');
        }
    }

    10. Seeder

    Chạy seeder để tạo mẫu các dữ liệu đã được định nghĩa trong factory

  • Truy cập file DatabaseSeeder.php
  • Thêm hasPost(’số lượng post’) cùng User::factory(’số lượng user’)
  • //database\seeders\DatabaseSeeder.php
    User::factory(5)->superAdmin()->hasPosts(25)->create();
    User::factory(50)->admin()->hasPosts(25)->create();
    User::factory(50)->hasPosts(25)->create();
  • Chạy lại database
  • php artisan migrate:fresh --seed

    11. Route

  • Truy cập “api.php” trong routes
  • Thêm các route (Lưu ý, cân nhắc đặt route phù hợp với middleware)
  • //routes\api.php
    
    //Private - auth
    Route::get('/posts', [PostController::class, 'index']);
    Route::get('/posts/{id}', [PostController::class, 'show']);
    
    //Public - web
    Route::post('/posts', [PostController::class, 'store']);
    Route::put('/posts/{id}', [PostController::class, 'update']);
    Route::delete('/posts/{id}', [PostController::class, 'destroy']);

    12. Postman

  • Truy vấn dữ liệu
  • Note image

    Share this article

    Share:
    Read Next Article