+1

Tìm hiểu Laravel từ số 0 (P7)

Tiếp nối phần 6 đã nói về Form validation, Formrequest, Error message, Scope và View Partial, trong phần 7 này tôi sẽ tiếp tục đề cập một số vấn đề sau :

  • Helper
  • Flash message
  • Named route
  • Restful resource controller

Helper

Lần này tôi sẽ thêm vào chức năng xoá article và thông qua đó cũng sẽ có nói luôn về Helper. Cái đầu tiên là cần thêm vào những route sau với phương thức delete trog routes.php

Route::delete('articles/{id}', 'ArticlesController@destroy');

Kế đến sẽ đi thêm vào Controller phương thức destroy với nội dung nhận vào tham số $id vừa khai báo trong route.

// app/Http/Controllers/ArticlesController.php
    public function destroy($id) {
        $article = Article::findOrFail($id);
        $article->delete();
        return redirect('articles');
    }

Để gọi được phương thức xoá ta cần đặt nút xoá trên View, tôi ở đây sẽ dùng phương thức DELETE của Form.

// resoruces/views/articles/show.blade.php 
@extends('layout')
@section('content')
    // Giản lược code ở đây
    {!! Form::open(['method' => 'DELETE', 'url' => ['articles', $article->id]]) !!}
        {!! Form::submit('Delete', ['class' => 'btn btn-danger']) !!}
    {!! Form::close() !!}
@stop

Đến đây tôi muốn đề cập đến khái niệm HELPER, mục đích dùng nó là sẽ refactor chia lại phần tạo Form mà dùng phương thức DELETE ở trên. Tôi sẽ thêm file helper.php với nội dung chưa một hàm là delete_form sinh ra thẻ Form rồi trả về.

<?php
// app/Http/helper.php
function delete_form($urlParams, $label = 'Delete')
{
    $form = Form::open(['method' => 'DELETE', 'url' => $urlParams]);
    $form .= Form::submit($label, ['class' => 'btn btn-danger']);
    $form .= Form::close();
 
    return $form;
}

Để có hiệu lực tôi sẽ cần chỉnh file composer.json để Laravel thực hiện Autoload helper.php.

{
    ...
    "autoload": {
        "classmap": [
            "database"
        ],
        "psr-4": {
            "App": "app/"
        },
        "files": ["app/Http/helper.php"]    <-- thêm đoạn này
    },
    ...
}

Nhưng sau đó phải thực hiện command dưới đây thì việc autoload mới thực sự được thiết lập :

composer dump-autoload

Lại tiếp tục chỉnh show.blade.php. Thay vì đoạn code tạo Form được nhét vào Blade thì ta chỉ cần 1 dòng ngắn ngọn là code đã sáng sủa hơn rất nhiều. Ngoài ra thì bằng việc đưa ra Helper, bất kì chỗ nào cần Delete thì bạn đều đã có thể tái sử dụng được rồi. Cool !

// resoruces/views/articles/show.blade.php
@extends('layout')
@section('content')
    // Giản lược code ở đây
     {!! delete_form(['articles', $article->id]) !!} 
@stop

Flash message

Xong phần xoá, bạn hãy thử chạy xem. Kết quả bạn sẽ được chuyển về trang list nhưng chẳng hề có thông báo nào được hiển thị ra, như thế đối với code normal thì bạn thấy thiếu thiếu mất messageđể cho user biết xử lý là có thành công hay không. Giờ tiếp theo tôi sẽ đi đến dùng Flash message để làm việc này.

Đầu tiên sẽ phải sửa phương thức destroy của ArticlesController.php một chút :

// app/Http/Controllers/ArticlesController.php 
    ...
    public function destroy($id) {
        $article = Article::findOrFail($id);
 
        $article->delete();
        \Session::flash('flash_message', 'Deleted article successfully.');  // dòng thêm vào 
        return redirect('articles');
    }
    ...

Ở đây tôi dùng đến facade là \Session để access vào session và bằng phương thức flash tôi thêm vào nội dung báo ra màn hình. Khái niệm Flash mang ý nghĩa nó chỉ có hiệu lực một thời điểm nhất thời và chỉ có hiệu lực với request tiếp sau.

Đương nhiên cần đến View thì dòng thông báo đó mới có thể ra đến màn hình cho user, tôi sẽ đi sửa Layout. Đơn giản chỉ cần thêm vào code check xem trong Session có key nào tên là flash_messgae thì sẽ hiển thị nó ra. Dùng thêm class của Boostrap sẽ làm cho message có sắc hơn 😃

// resources/views/layout.blade.php
...
<body>
    <div class="container">
        {{-- Hiển thị Flash message --}}
        @if (Session::has('flash_message'))
            <div class="alert alert-success">{{ Session::get('flash_message') }}</div>
        @endif
        // Giản lược code
    </div>
 ...
</body>
</html>

Named route

Cái tên đã nói lên ý nghĩa của nó - route đã được đặt tên. Từ trước tới giờ khi mà chỉ định URL ở trên Controller hay View thì tôi viết cả path vào source code. Nhưng mà nếu làm như này suốt sẽ không ổn bởi vì một ngày đẹp trời path đó thay đổi bạn sẽ phải thay đổi cho rất nhiều nơi dùng nó. Do đó một cách đơn giản hiệu quả là đặt tên cho nó, nếu có sửa thì cũng chỉ cần thay đổi nội dung của path là xong.

Cần phải sửa file routes.php giống như sau :

// app/Http/routes.php 
...
// Đây là cách viết Route cũ 
//Route::get('articles', 'ArticlesController@index');
//Route::get('articles/create', 'ArticlesController@create');
//Route::get('articles/{id}', 'ArticlesController@show');
//Route::post('articles', 'ArticlesController@store');
//Route::get('articles/{id}/edit', 'ArticlesController@edit');
//Route::patch('articles/{id}', 'ArticlesController@update');
//Route::delete('articles/{id}', 'ArticlesController@destroy');
 
// Còn đây tôi đã chuyển sang đặt tên cho các Route
Route::get('articles', ['as' => 'articles.index', 'uses' => 'ArticlesController@index']);
Route::get('articles/create', ['as' => 'articles.create', 'uses' => 'ArticlesController@create']);
Route::get('articles/{id}', ['as' => 'articles.show', 'uses' => 'ArticlesController@show']);
Route::post('articles', ['as' => 'articles.store', 'uses' => 'ArticlesController@store']);
Route::get('articles/{id}/edit', ['as' => 'articles.edit', 'uses' => 'ArticlesController@edit']);
Route::patch('articles/{id}', ['as' => 'articles.update', 'uses' => 'ArticlesController@update']);
Route::delete('articles/{id}', ['as' => 'articles.destroy', 'uses' => 'ArticlesController@destroy']);

Bạn thấy code ở đây dài hơn nhưng thực sự nó rất tiện ích. Bằng việc dùng mảng rồi chỉ định khoá as là bạn sẽ đặt tên cho Route của mình, còn uses sẽ là phần chỉ ra action (gồm Controller và phương thức cần gọi). Cách sử dụng như dưới :

// app/Http/Controllers/ArticlesController.php
    ...
    public function store(ArticleRequest $request) {
        Article::create($request->all());
        Session::flash('flash_message', 'Add article successfully.');
 
        //cũ : return redirect('articles');
        return redirect()->route('articles.index');
    }
    ...
    public function update($id, ArticleRequest $request) {
        $article = Article::findOrFail($id);
 
        $article->update($request->all());
        Session::flash('flash_message', 'Update article successfully.');
 
        // cũ : return redirect(url('articles', [$article->id]));
        return redirect()->route('articles.show', [$article->id]);
    }
// resources/views/articles/create.blade.php 
@extends('layout')
@section('content')
// Giản lược code
    {{-- Form::open(['url' => 'articles']) --}}
    {!! Form::open(['route' => 'articles.store']) !!}
        @include('articles.form', ['published_at' => date('Y-m-d'), 'submitButton' => 'Add Article'])
    {!! Form::close() !!}
@stop

Restful Resource Controller

Cho đến bài này thì tất cả những cái cơ bản CRUD tức tạo mới - Create, đọc ra - Read, cập nhật - Update và xoá - Delete đã được thực hiện. Thực tế là tôi đã định nghĩa Route hay Controller dựa vào REST khi mà thực hiện CRUD. Phần cuối bài này tôi sẽ nói về quan hệ của Route hay Controller trong Laravel với REST.

Vậy REST là gì ?

Tôi cũng không tự tin lắm để giải thích chi tiết được nhưng theo tôi hiểu nó là một khái niệm thiết kế của API chỉ rõ thao tác và đối tượng thao tác đến ứng với URI và phương thức Request của HTTP. Và người ta gọi thứ mà được thiết kế dựa trên khái niệm REST là RESTful.

Trong Laravel nhìn chung nếu mà hiểu với pattern dưới chắc sẽ không có vấn đề gì.

Trường hợp đối tượng thao tác đến là bài viết (Article) :

METHOD URI ACTION Nội dung thao tác
GET /articles ArticlesController@index list bài viết
GET /articles/create ArticlesController@create Tạo mới
POST /articles ArticlesController@store Lưu mới
GET /articles/{article} ArticlesController@show Hiển thị
GET /articles/{article}/edit ArticlesController@edit Chỉnh sửa
PUT/PATCH /articles/{article} ArticlesController@update Cập nhật
DELETE /articles/{article} ArticlesController@destroy Xoá

RESTFUL CONTROLLER

Trong Laravel mặc định nếu mà tạo Controller thì phương thức sẽ được tạo. Những phương thức này sẽ theo pattern bên trên để được đặt tên. Nếu như tạo raPostsController.php

php artisan make:controller PostsController

thì mặc định bên trong nó sẽ có đủ các phương thức nên gọi là Restful Controller.

<?php // app/Http/Controllers/PostsController.php
 
namespace AppHttpControllers;
 
use AppHttpRequests;
use AppHttpControllersController;
 
use IlluminateHttpRequest;
 
class posts extends Controller {
 
    public function index()
    {
        //
    }
 
    public function create()
    {
        //
    }
 
    public function store()
    {
        //
    }
 
    public function show($id)
    {
        //
    }
 
    public function edit($id)
    {
        //
    }
 
    public function update($id)
    {
        //
    }
 
    public function destroy($id)
    {
        //
    }
}

RESTFUL ROUTES

Và tương tự thế, để định nghĩa được tất cả Route đối với PostsController ta chỉ cần 1 dòng code sử dụng Route::resource - gọi là Restful Routes :

// app/Http/routes.php 
Route::resource('posts', 'PostsController');

Để xác nhận điều đó hãy chạy command sau :

php artisan route:list
Domain Method URI Name Action Middleware
GET/HEAD posts posts.index App\Http\Controllers\PostsController@index
GET/HEAD posts/create posts.create App\Http\Controllers\PostsController@create
POST posts posts.store App\Http\Controllers\PostsController@store
GET/HEAD posts/{posts} posts.show App\Http\Controllers\PostsController@show
GET/HEAD posts/{posts}/edit posts.edit App\Http\Controllers\PostsController@edit
PUT posts/{posts} posts.update App\Http\Controllers\PostsController@update
PATCH posts/{posts} App\Http\Controllers\PostsController@update
DELETE posts/{posts} posts.destroy App\Http\Controllers\PostsController@destroy

Bạn để ý sẽ thấy cột Name có định nghĩa cả giá trị cho Named Route luôn. Cool ! Không chỉ thế, mà tuỳ vào từng trường hợp bạn hoàn toàn customize Route như bên dưới :

Route::resource('posts', 'PostsController', ['only' =>['index', 'show']]);
Route::resource('posts', 'PostsController', ['except' => ['create', 'store', 'update', 'destroy']]);

Đối với trường hợp Article thì thay vì phải định nghĩa tất cả Routes như đã làm tới 7 dòng code :

// app/Http/routes.php
 
//Route::get('articles', ['as' => 'articles.index', 'uses' => 'ArticlesController@index']);
//Route::get('articles/create', ['as' => 'articles.create', 'uses' => 'ArticlesController@create']);
//Route::get('articles/{id}', ['as' => 'articles.show', 'uses' => 'ArticlesController@show']);
//Route::post('articles', ['as' => 'articles.store', 'uses' => 'ArticlesController@store']);
//Route::get('articles/{id}/edit', ['as' => 'articles.edit', 'uses' => 'ArticlesController@edit']);
//Route::patch('articles/{id}', ['as' => 'articles.update', 'uses' => 'ArticlesController@update']);
//Route::delete('articles/{id}', ['as' => 'articles.destroy', 'uses' => 'ArticlesController@destroy']);

Thì chỉ cần 1 dòng là đủ !

Route::resource('articles', 'ArticlesController');

Bạn hãy chạy thử php artisan route:list xem có đúng là như vậy không !?


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí