Tìm hiểu Laravel từ số 0 (P6)
Bài đăng này đã không được cập nhật trong 7 năm
Ở phần trước tôi đã đi đến nội dung tạo Form. Trong bài này tôi sẽ đi tiếp đến các phần nội dung sau :
- Form validation
- Formrequest
- Error message
- Scope
- View Partial
Form validation
Controller
Tôi sẽ đi bước đầu tiên với controller để validate dữ liệu được nhập vào Form đã tạo. Đó là phải chỉnh sửa phương thức store()
như sau :
<?php namespace AppHttpControllers;
// app/Http/Controllers/ArticlesController.php
use App\Article;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request; // ①'
class ArticlesController extends Controller {
// ...
public function store(Request $request) { // ①
$rules = [ // ②
'title' => 'required|min:3',
'body' => 'required',
'published_at' => 'required|date',
];
$this->validate($request, $rules); // ③
Article::create($request->all());
return redirect('articles');
}
}
Phần ① tôi đã thay đổi cách lấy request. Lần trước tôi sử dụng facade \Request
để access vào request nhưng ở đây đã đổi thành lấy ra instance của lớp Illuminate\Http\Request
từ đối số của phương thức store()
. Đây là cách hoạt động rất hay của Laravel controller, khi mà ta chỉ cần ghi vào tên lớp vào đối số của phương thức thì tự động instance của lớp sẽ được sinh ra.
Phần ② là chỗ thiết lập những rule validate dữ liệu. Chúng ta có thể lập được rất nhiều rule nhưng trong bài này tôi chỉ đưa vào vài ví dụ như đoạn code trên. Chi tiết hơn bạn có thể xem tại : http://laravel.com/docs/5.1/validation#available-validation-rules
Phần ③ tôi đang thực hiện phương thức validate()
của Controller. Ở đây nếu như có lỗi thì Laravel có cơ chế tự động giúp ta được redirect trở về màn hình trước đó.
View
Xong phần xử lý bên Controller thì việc còn lại sẽ là hiển thị những lỗi bắt được lên View. Nếu như có nhiều lỗi thì ta sẽ nhận được mảng chứa chúng nên tôi sẽ dùng foreach
để duyệt rồi hiển thị chúng ra màn hình.
// resources/views/articles/create.blade.php
@extends('layout')
@section('content')
<h1>Write a New Article</h1>
<hr/>
{{-- Tôi thêm phần hiển thị lỗi --}}
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
{!! Form::open() !!}
...
{!
Việc thiết lập đã hoàn tất, bạn có thể truy cập vào http://localhost:8000/articles/create
nhưng không nhập gì cả mà ấn nút Add Article, nếu error message hiển thị là xong.
Formrequest
Bước trên tôi đã thiết lập xong validate Form, giờ tôi sẽ dùng 1 chức năng là Formrequest để refactor phần đó.
Tạo Formrequest
Tôi sử dụng artisan
để tạo ra FormRequest dùng cho Article.
php artisan make:request ArticleRequest
Và file app/Http/Requests/ArticleRequest.php
sẽ được tạo ra với nội dung như sau :
<?php // app/Http/Requests/ArticleRequest.php
namespace App\Http\Requests;
use App\Http\Requests\Request;
class ArticleRequest extends Request {
public function authorize()
{
return false;
}
public function rules()
{
return [
//
];
}
}
Sửa ARTICLEREQUEST.PHP
Tôi tiến hành sửa nội dung thành :
<?php namespace App\Http\Requests;
use App\Http\Requests\Request;
class ArticleRequest extends Request {
public function authorize()
{
return true; // ①
}
public function rules()
{
return [ // ②
'title' => 'required|min:3',
'body' => 'required',
'published_at' => 'required|date',
];
}
}
Phần ① tôi sẽ thiết lập quyền hạn đối với request bằng phương thức authorize()
. Ví dụ nhưng user đang login hiện tại mà ko có quyền thì sẽ trả về false
. Nhưng bây giờ thì ai cũng có thể tạo được dữ liệu Article nên sẽ trả về true
.
Phần ② trả về validation rule bằng phương thức rules()
.
Controller
Tiếp theo tôi sẽ đi sửa để trên phương thức store()
của ArticlesController sẽ sử dụng ArticleRequest.
<?php namespace AppHttpControllers;
// app/Http/Controllers/ArticlesController.php
use App\Article;
use App\Http\Controllers\Controller;
use App\Http\Requests\ArticleRequest; // ①'
class ArticlesController extends Controller {
// ...
public function store(ArticleRequest $request) { // ①
// đoạn validate ở đây giờ ko cần nữa
Article::create($request->all());
return redirect('articles');
}
}
Phần ① như bạn thấy là tôi đã đổi từ Illuminate\Http\Request
sang dùng App\Http\Requests\ArticleRequest
. Chỉ cần thiết lập như vậy là bạn đã có thể không cần đến validate đã thiết lập ở bước đầu tiên nữa nhưng kết quả vẫn sẽ tương tự. Controller lúc này đã trở lên sạch sẽ
hơn rồi
Đổi error message sang ngôn ngữ khác
Mặc định ngôn ngữ của message lỗi là tiếng Anh nên nếu thấy cần thiết, ví dụ như khách hàng của tôi là người nhật thì việc chuyển đổi sang tiếng Nhật là hoàn toàn cần làm. Tôi sẽ chuyển config qua ngôn ngữ Nhật bằng cách thiết lập locale
thành ja
như sau :
// config/app.php
'locale' => 'ja',
Sau đó là cần thiết lập file lỗi cho tiếng Nhật tương ứng. Những file này được lưu tại folder lang
:
resources
└── lang
├── en
│ └── validation.php
└── ja
└── validation.php
Bạn có thể copy bên thư mục en
sang ja
sau đó thì chuyển nhưng message có nội dung tiếng Anh sang tiếng Nhật.
Scope
Trước khi đi vào chức năng Scope
của Eloquent
chúng ta hãy nhìn lại xem phần danh sách hiển thị ra có vấn đề gì không. Tôi nghĩ là có 2. Thứ nhất là những bài mới được tạo ra lại bị chìm xuống dưới. Thứ hai là những bài mà có ngày published_at
là ngày của tương lai cũng được hiển thị ra. Vậy hãy sửa lại chức năng này cho hợp lý hơn.
Sắp xếp
Tôi sẽ sửa lại ArticlesController.php
như sau vì dùng all()
thì nó sẽ chỉ đơn thuần lấy ra tất cả các bài viết nên tôi đổi sang dùng lastest()->get()
để lấy ra những cái mới nhất rồi sắp xếp theo published_at
giảm dần :
<?php namespace App\Http\Controllers;
// app/Http/Controllers/ArticlesController.php
// ...
class ArticlesController extends Controller {
public function index() {
// $articles = Article::all(); Trước tôi chỉ dùng code này
$articles = Article::latest('published_at')->get(); // Thay đổi thành như này
/* Ngoài ra có thể dùng orderBy()->get() cũng cho kết quả tương tự
// $articles = Article::orderBy('published_at', 'desc')->get();
*/
return view('articles.index', compact('articles'));
}
}
Tiếp theo tôi sẽ sửa để lấy ra chỉ những bài viết có ngày publish trước thời điểm hiện tại dùng where()
:
$articles = Article::latest('published_at')->where('published_at', '<=', Carbon::now())->get();
Scope
Nếu như trang của bạn chỉ cần dùng điều kiện mà chỉ lấy bài viết có published_at
trước thời điểm hiện tại thì như vậy ok. Nhưng trong trường hợp điều kiện này được sử dụng lặp lại nhiều lần thì việc khai báo ở nhiều chỗ sẽ bị dư thừa và giảm sự mạch lạc của code. Lúc này khái niệm scope
sẽ hữu ích cho bạn. Đây là chức năng mà Eloquent của Laravel cung cấp. Nó cho phép định nghĩa phạm vi - scope
để sử dụng nhiều lần được.
Tôi sẽ thêm phương thức scope()
vào Model, và để định nghĩa bạn cần gắn cho nó tiền tố scope
như sau :
<?php namespace App;
// app/Article.php
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
class Article extends Model {
// ...
// Tôi định nghĩa published scope để dùng nhiều chỗ
public function scopePublished($query) {
$query->where('published_at', '<=', Carbon::now());
}
// ...
}
Sau đó sẽ thay đổi index()
trong Controller từ dùng where
sang dùng `scope.
public function index() {
$articles = Article::latest('published_at')->published()->get();
return view('articles.index', compact('articles'));
}
Code đoạn này đã trở lên ngắn gọn dễ đọc hơn và ta cũng có thể dùng lại scope
published này ở những chỗ khác. Và tương tự bạn hoàn toàn có thể tạo ra được nhiều scope
khác nhau phục vụ cho từng nhu cầu riêng rẽ.
View Partial
Trong Laravel tồn tại một khái niệm là View Partial
, bạn có thể hiểu nó cho phép bạn common hoá Form để dùng chung được ở các View khác nhau. Ví dụ như trong phạm vị loạt bài này, tôi đã tạo ra list và Form tạo mới, nhưng còn thiếu phần chỉnh sửa. Mà phần này sẽ gần như tương tự lúc tạo mới nên bạn có thể dùng View Partial
.
Routing
Tôi sẽ thêm hai route mới vào là edit
và update
. Bạn chú ý rằng update
không phải phương thức get
mà là path
:
// app/Http/routes.php
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'); // new
Route::patch('articles/{id}', 'ArticlesController@update'); // new
Controller
Do đã thêm route nên ta cần thêm vào hai phương thức tương ứng trong Controller là edit
và update
:
// app/Http/Controllers/ArticlesController.php
public function edit($id) {
$article = Article::findOrFail($id);
return view('articles.edit', compact('article'));
}
public function update($id, ArticleRequest $request) {
$article = Article::findOrFail($id);
$article->update($request->all());
return redirect(url('articles', [$article->id]));
}
}
Về phương thức edit()
thì hoàn toàn không có kiến thức gì mới lạ nhưng phần update()
thì nó đang nhận vào tham số $id
và $request
. Tham số $request
này đang dùng lớp ArticleRequest
kế thừa từ FormRequest
mà ở trên đã làm. Bằng việc sử dụng FormRequest
thì khi mà có lỗi lúc check dữ liệu nhập vào Form thì sẽ tự động redirect về màn hình trước đó. Trong phương thức update()
này sẽ lấy ra thông tin của bài viết đối với $id
chỉ định, rồi cập nhật bài viết với nội dung nhập vào Form và redirect về trang hiển thị của nó.
View
Tôi sẽ đi tạo ra edit.blade.php
bằng cách copy và chỉnh sửa từ phần tạo mới create.blade.php
:
// resources/views/articles/edit.blade.php
@extends('layout')
@section('content')
<h1>Edit: {{ $article->title }}</h1>
<hr/>
@if ($errors->any())
<ul class="alert alert-danger">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
@endif
{!! Form::model($article, ['method' => 'PATCH', 'url' => ['articles', $article->id]]) !!}
<div class="form-group">
{!! Form::label('title', 'Title:') !!}
{!! Form::text('title', null, ['class' => 'form-control']) !!}
</div>
<div class="form-group">
{!! Form::label('body', 'Body:') !!}
{!! Form::textarea('body', null, ['class' => 'form-control']) !!}
</div>
<div class="form-group">
{!! Form::label('published_at', 'Publish On:') !!}
{!! Form::input('date', 'published_at', $article->published_at->format('Y-m-d'), ['class' => 'form-control']) !!}
</div>
<div class="form-group">
{!! Form::submit('Edit Article', ['class' => 'btn btn-primary form-control']) !!}
</div>
{!! Form::close() !!}
@stop
Bạn có thể thấy có 4 điểm được sửa ở đây gồm :
- Hiển thị tiêu đề bài viết trên phần tiêu đề của trang
- Thay đổi từ
Form::open
thànhForm::model
. Cái này dùng để mapping giá trị của$article
với Form. Rồi chỉ định METHOD làPATH
vàid
của$article
đếnurl
. - Thay đổi giá trị khởi tạo của mục nhập
published_at
từdate('Y-m-d')
thành giá trị của$article->published_at
. Vì giá trị này đang chỉ định Date Mutator ở lớp Article nên sẽ có giá trị instance của lớp Carbon được trả về. Sau đó tiến hành biến đối nó thành chuỗi sử dụng phương thứcformat()
làm giá trị khởi tạo lúc edit. - Thay đổi label của nút
submit
thành Edit Article
Và nếu bạn dùng trình duyệt để view source thì nó sẽ có dạng :
...
<form method="POST" action="http://localhost:8000/articles/1" accept-charset="UTF-8">
<input name="_method" type="hidden" value="PATCH">
...
View Partial
Việc tiếp theo là sử dụng VIEW PARTIAL để refactor code do phần tạo mới và sửa bài viết có những code trùng lặp nhau. Đầu tiên là phải partial hoá hiển thị lỗi - tạo form_error_blade.php
:
// resources/views/errors/form_errors.blade.php
@if ($errors->any())
<ul class="alert alert-danger">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
@endif
Để sử dụng file trên thì cần sửa lại file edit.blade.php
bằng cách dùng @include
để tham chiếu đến nó. Và do file đó được tạo dưới thư mục resources/views/errors
nên cần chỉ định errors.form_errors
- đây chính là khái niệm View Partial, tức chia View ra từng phần :
// resources/views/articles/edit.blade.php
@extends('layout')
@section('content')
<h1>Edit: {!! $article->title !!}</h1>
<hr/>
@include('errors.form_errors')
{!! Form::model($article, ['method' => 'PATCH', 'url' => ['articles', $article->id]]) !!}
...
...
{!! Form::close() !!}
@stop
Và cũng cần phải Partial hoá cả Form rồi trả về giá trị đến Partial. Tôi sẽ tạo file form.blade.php
:
// resources/views/articles/form.blade.php
<div class="form-group">
{!! Form::label('title', 'Title:') !!}
{!! Form::text('title', null, ['class' => 'form-control']) !!}
</div>
<div class="form-group">
{!! Form::label('body', 'Body:') !!}
{!! Form::textarea('body', null, ['class' => 'form-control']) !!}
</div>
<div class="form-group">
{!! Form::label('published_at', 'Publish On:') !!}
{!! Form::input('date', 'published_at', $published_at, ['class' => 'form-control']) !!}
</div>
<div class="form-group">
{!! Form::submit($submitButton, ['class' => 'btn btn-primary form-control']) !!}
</div>
Bạn thấy rằng gía trị khởi tạo ban đầu của published_at
sẽ được trả về bởi biến $published_at
. Và label hiển thị của nút submit
cũng sẽ được trả về bằng biến $submitButton
. Để sử dụng nó tôi cũng cần sửa thêm file edit.blade.php
:
// resources/views/articles/edit.blade.php
@extends('layout')
@section('content')
<h1>Edit: {!! $article->title !!}</h1>
<hr/>
@include('errors.form_errors')
{!! Form::model($article, ['method' => 'PATCH', 'url' => ['articles', $article->id]]) !!}
@include('articles.form', ['published_at' => $article->published_at->format('Y-m-d'), 'submitButton' => 'Edit Article'])
{!! Form::close() !!}
@stop
Tương tự như ở trên tôi cũng dùng @include
để tham chiếu đến form.blade.php
. Và đưa giá trị của published_at
và submitButton
. Cuối cùng tôi chỉ cần thêm vào nút Edit là hoàn thiện :
// resources/views/articles/show.blade.php
@extends('layout')
@section('content')
<h1>{{ $article->title }}</h1>
<hr/>
<article>
<div class="body">{{ $article->body }}</div>
</article>
<br/>
{!! link_to(action('ArticlesController@edit', [$article->id]), 'Edit', ['class' => 'btn btn-primary']) !!}
@stop
Hy vọng bài này đã cung cấp được thêm kiến thức mới cho những newbie với Laravel như tôi.
Thanks.
All rights reserved