基本功能
建立文章的第一步是使用者發請求,然後返回建立文章的頁面。
路由:處理使用者「建立文章」的請求
/routes/web.php
Route::get(`/posts/create`,`PostsController@create`);
控制器: 返回文章編輯檢視
/app/Http/Controllers/PostsController.php
public function create()
{
return view(`posts.create`);
}
檢視: 使用 Bootstrap 元件來建立文章編輯頁面
/resources/views/posts/create.blade.php
@extends(`layouts.master`)
@section(`content`)
<div class="col-sm-8 blog-main">
<h1>建立文章</h1>
<hr>
<form action="{{ action(`PostsController@store`) }}" method="post">
<div class="form-group">
<label for="title">標題</label>
<input type="text" id="title" name="title" class="form-control">
</div>
<div class="form-group">
<label for="body">內容</label>
<textarea class="form-control" id="body" name="body" rows="10"></textarea>
</div>
<button type="submit" class="btn btn-primary">提交</button>
</form>
</div>
@endsection
action
方法根據控制器來生成對應的路由,也可以用之前學過的 url
方法:
<form action="{{ url(`posts`) }}" method="post">
生成的 url 如下:
<form action="http://localhost:8000/posts" method="post">
使用者提交之後,需要在路由中處理使用者提交的資料的請求:
/routes/web.php
Route::post(`/posts`,`PostsController@store`);
最後是儲存文章實現,我們使用 request()
方法獲取請求欄位,儲存完之後跳轉到部落格首頁:
use AppPost;
public function store()
{
$post = new Post();
$post->title = request(`title`);
$post->body = request(`body`);
$post->save();
return redirect(`posts`);
}
現在,訪問 posts/create
,建立文章後點選提交,檢視下效果。實際上,會報錯:
TokenMismatchException in VerifyCsrfToken.php line 68
新增 CSRF 保護
雖然我們完成了基本功能,但是提交請求的時候還是會報錯,其實這是防止 CSRF 攻擊。
舉一個簡單的例子,你登入一個投票網站,通過傳送該請求向編號為 25 的人投票:
http://example.com/vote/25
CSRF 如何進行攻擊呢,顧名思義,CSRF 是 Cross-site request forgery 的縮寫,即跨站請求偽造,因此需要具備兩個條件:
-
跨站。首先,我登入了該投票網站,網站儲存了我的登入資訊,然後我又登入了另外一個網站;
-
偽造請求。在另外一個網站的介面中,可能包含了類似
<img src="http://example.com/vote/30" />
這樣的 HTML 程式碼。由於投票網站無法區分你在哪裡傳送的請求,因此,就等於你向 30 號選手進行了投票;
解決方式也很簡單:
-
登入 A 網站的時候,生成一條 token
-
提交請求的時候,該 token 也跟著提交
-
兩者進行驗證即可
第一步,Laravel 已經幫我們實現了:
/vendor/laravel/framework/src/Illuminate/Session/Store.php
public function start()
{
$this->loadSession();
if (! $this->has(`_token`)) {
$this->regenerateToken();
}
return $this->started = true;
}
第二步,Laravel 也幫我們封裝好了,直接使用 csrf_field()
函式即可,我們在文章編輯的表單中加入即可:
/resources/views/posts/create.blade.php
<h1>建立文章</h1>
<hr>
<form action="{{ url(`posts`) }}" method="post">
{{ csrf_field() }}
<div class="form-group">
<label for="title">標題</label>
<input type="text" id="title" name="title" class="form-control">
</div>
可以看看該函式長什麼樣:
function csrf_field()
{
return new HtmlString(`<input type="hidden" name="_token" value="`.csrf_token().`">`);
}
因此,我們也可以寫成:
<input type="hidden" name="_token" value="{{ csrf_token() }}">
最後一步,Laravel 通過中介軟體來進行自動檢驗:
public function handle($request, Closure $next)
{
if (
$this->isReading($request) ||
$this->runningUnitTests() ||
$this->inExceptArray($request) ||
$this->tokensMatch($request)
) {
return $this->addCookieToResponse($request, $next($request));
}
throw new TokenMismatchException;
}
簡單解讀下該中介軟體的處理流程:
-
判斷請求型別,如果是
GET
、HEAD
、OPTIONS
等不會更改資源的請求就通過; -
如果處於測試環境下就通過;
-
$except
陣列內新增的 url 預設通過; -
tokens 匹配也通過;
通過之後,就會新增名為 XSRF-TOKEN
的cookie;如果沒通過,就丟擲異常,也就是我們上一節顯示的錯誤資訊了。
批量建立文章
剛才我們採用是 save()
方法來儲存文章,實際上,也可以使用 create()
方法,該方法允許一次性插入多條資料,因此必須指定允許批量插入的欄位:
/app/Post.php
class Post extends Model
{
protected $fillable = [
`title`,
`body`,
];
}
store()
方法可以寫成:
/app/Http/Controllers/PostsController.php
public function store(Request $request)
{
Post::create([
`title` => request(`title`),
`body` => request(`body`)
]);
return redirect("posts");
}
或者傳入陣列給 request()
:
/app/Http/Controllers/PostsController.php
public function store(Request $request)
{
Post::create(request([`title`,`body`]));
return redirect("posts");
}
新增欄位驗證
接下來進一步完善建立文章的功能,即欄位驗證。可以直接使用 validate
方法:
/app/Http/Controllers/PostsController.php
public function store(Request $request)
{
$this->validate(request(), [
`title` => `required|unique:posts|max:255`,
`body` => `required|min:5`,
]);
Post::create(request([`title`, `body`]));
return redirect("posts");
}
我們為 title
新增了非空、唯一性以及最大字元的驗證規則,對 body
欄位新增了非空和最小字元的規則。
假如違反了規則,錯誤資訊 $errors
會自動被儲存在快閃記憶體的 Session 中,即只對下一次請求生效。並且,我們不需要將其返回給檢視,Laravel 幫我們做了處理,我們所有的檢視都可以獲取到 $errors
變數,可以令其顯示出來:
/resources/views/layouts/master.blade.php
@include(`layouts.errors`);
@include(`layouts.footer`)
具體錯誤訊息:
/resources/views/layouts/errors.blade.php
@if (count($errors))
<div class="form-group">
<div class="alert alert-warning" role="alert">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
</div>
@endif