Tìm hiểu Laravel từ số 0 (P9)
Tiếp sau phần 8 thì phần 9 này tôi sẽ trình bày nốt về những nội dung cuối cùng trong chuỗi bài về Laravel cơ bản này. Bao gồm các nội dung sau : Middleware Relationships Route Model Binding ! Trong phần trước chúng ta đã có thể login vào nhưng vẫn chưa có cơ chế điều khiển để những ...
Tiếp sau phần 8 thì phần 9 này tôi sẽ trình bày nốt về những nội dung cuối cùng trong chuỗi bài về Laravel cơ bản này. Bao gồm các nội dung sau :
- Middleware
- Relationships
- Route Model Binding
!
Trong phần trước chúng ta đã có thể login vào nhưng vẫn chưa có cơ chế điều khiển để những ai chưa login hệ thống sẽ không tạo thêm, hay sửa xoá gì các bài viết được. Bắt đầu từ Laravel 5 thì việc filter này sẽ tiến hành trong Middleware. Nó được đặt ở thư mục như bên dưới, khi bạn tạo project Laravel thì sẽ có 3 files được tạo ra :
app/Http/Middleware/ ├── Authenticate.php ├── RedirectIfAuthenticated.php └── VerifyCsrfToken.php
Hãy vào xem nội dung của file Authenticate.php :
<?php // app/Http/Middleware/Authenticate.php namespace AppHttpMiddleware; use Closure; use IlluminateContractsAuthGuard; class Authenticate { protected $auth; public function __construct(Guard $auth) { $this->auth = $auth; } public function handle($request, Closure $next) { if ($this->auth->guest()) { // Khi mà chưa login if ($request->ajax()) { return response('Unauthorized.', 401); } else { return redirect()->guest('auth/login'); } } // Khi đã login return $next($request); } }
Điểm chính ở đây như bạn thấy đó chính là phương thức handle(), nó sẽ được gọi đến trước khi mà phương thức trong Controller được gọi từ Route. Bên trong nó chứa xử lý phán đoán xem user có tiếp tục được dùng hệ thống hay không? Nếu user mà chưa login thì sẽ bị chuyển hướng về trang login còn ngược lại thì để tiếp tục nó sẽ gọi callback là $next.
Vậy đăng kí Middleware như nào? Việc đó sẽ làm ở trong app/Html/Kernel.php :
<?php // app/Http/Kernel.php namespace AppHttp; use IlluminateFoundationHttpKernel as HttpKernel; class Kernel extends HttpKernel { protected $middleware = [ 'IlluminateFoundationHttpMiddlewareCheckForMaintenanceMode', 'IlluminateCookieMiddlewareEncryptCookies', 'IlluminateCookieMiddlewareAddQueuedCookiesToResponse', 'IlluminateSessionMiddlewareStartSession', 'IlluminateViewMiddlewareShareErrorsFromSession', 'AppHttpMiddlewareVerifyCsrfToken', ]; protected $routeMiddleware = [ 'auth' => 'AppHttpMiddlewareAuthenticate', 'auth.basic' => 'IlluminateAuthMiddlewareAuthenticateWithBasicAuth', 'guest' => 'AppHttpMiddlewareRedirectIfAuthenticated', ]; }
Điều mà bạn cần nhớ ở những dòng này sẽ có 2 cái sau :
- Trường hợp bạn muốn thực hiện Middleware đối với tất HTTP request của hệ thống thì cho vào $middleware
- Còn chỉ muốn thực hiện Middleware đối với Route đặc định nào đó thì đăng kí vào $$outeMiddleware.
Giờ hãy thử dùng Middleware trong Controller :
<?php // app/Http/Controllers/ArticlesControllers.php namespace AppHttpControllers; ... class ArticlesController extends Controller { public function __construct() { $this->middleware('auth', ['except' => ['index', 'show']]); } ... }
Như bạn thấy tôi đã thêm vào constructor với nội dung bên trong nó là sử dụng đến Middleware. Tôi truyền vào tham số là auth - chính là key khi mà code đăng kí Middleware ở trên thuộc tính $routeMiddleware của Kernel.php khi thực hiện phương thức middleware(). Bạn để ý là tôi có sử dụng cả lựa chọn optional là except có chỉ định mảng đối tượng không bị ảnh hưởng của Middleware đã chỉ định là index và show. Đến đây hãy thử xác nhận lại Ruote bằng artisan :
php artisan route:list +--------+----------+--------------------------+------------------+--------------------------------------------------------+------------+ | Domain | Method | URI | Name | Action | Middleware | +--------+----------+--------------------------+------------------+--------------------------------------------------------+------------+ | | GET|HEAD | articles | articles.index | AppHttpControllersArticlesController@index | | | | GET|HEAD | articles/create | articles.create | AppHttpControllersArticlesController@create | auth | | | POST | articles | articles.store | AppHttpControllersArticlesController@store | auth | | | GET|HEAD | articles/{articles} | articles.show | AppHttpControllersArticlesController@show | | | | GET|HEAD | articles/{articles}/edit | articles.edit | AppHttpControllersArticlesController@edit | auth | | | PUT | articles/{articles} | articles.update | AppHttpControllersArticlesController@update | auth | | | PATCH | articles/{articles} | | AppHttpControllersArticlesController@update | auth | | | DELETE | articles/{articles} | articles.destroy | AppHttpControllersArticlesController@destroy | auth | +--------+----------+--------------------------+------------------+--------------------------------------------------------+------------+
Như bạn thấy rằng cột Middleware ngoài cùng bên tay phải cho thấy Middleware auth đang được áp dụng cho những Route nào ngoại trừ 2 ông đã được thiết lập ngoài vùng phủ sóng index, show. Vậy là đã hoàn thành việc sử dụng Middleware, bạn có thể vào tạo hay sửa xoá bài viết sẽ thấy được Middleware sẽ check xem bạn đã login chưa, nếu chưa nó sẽ chuyển hướng bạn về lại trang login.
Tiếp đến do chúng ta đã thêm Middelware vào ArticlesController.php nên phía View cũng nên tiến hành điều khiển hiển thị của button theo trạng thái login :
{{-- resources/views/articles/index.blade.php --}} @extends('layout') @section('content') <h1>Articles</h1> <hr/> {{-- chỉ hiển thị nút Create ở list các bài viết khi user đang login --}} @if (Auth::check()) {!! link_to('articles/create', 'Create', ['class' => 'btn btn-primary']) !!} @endif @foreach($articles as $article) ... @endforeach @stop
{{-- resources/views/articles/show.blade.php --}} @extends('layout') @section('content') <h1>{{ $article->title }}</h1> <hr/> <article> <div class="body">{{ $article->body }}</div> </article> {{-- Và lúc show bài viết cũng tương tự với nút Edit và Delete --}} @if (Auth::check()) <br/> {!! link_to(route('articles.edit', [$article->id]), 'Edit', ['class' => 'btn btn-primary']) !!} <br/> <br/> {!! delete_form(['articles', $article->id]) !!} @endif @stop
Xong ! Còn việc tạo ra Middleware cũng rất đơn giản, bạn sẽ dùng lệnh artisan để làm việc đó :
php artisan make:middleware MyMiddleware // app/Http/Middleware/MyMiddleware.php sẽ được tạo
<?php namespace AppHttpMiddleware; use Closure; class MyMiddleware { public function handle($request, Closure $next) { // Những xử lý của bạn đặt ở đây return $next($request); } }
Kế đến tôi muốn chuyển sang đến relationship một - nhiều trên Model, để mà một User có được nhiều bài Article thì cần gắn quan hệ giữa User model với Article model.
// app/User.php class User extends Model implements AuthenticatableContract, CanResetPasswordContract { ... public function articles() { return $this->hasMany('AppArticle'); } }
Tôi tạo ra phương thức articles() với mục đích tạo quan hệ một User nhiều bài Article bằng cách dùng phương thức hasMany(). Lúc này thì ta đã có thể lấy ra nhiều Article có quan hệ với User như code bên dưới :
$articles = User::find(1)->articles();
// app/Article.php class Article extends Model { ... public function user() { return $this->belongsTo('AppUser'); } }
Ngược lại thì tôi tạo ra phương thức user() để mapping với AppUser bằng phương thức belongsTo(). Tương tự bạn có thể lấy được một User có mapping với Article như dưới :
$user = Article::find(1)->user();
Rồi tôi thêm vào khoá ngoại đến bảng Users vào bảng Articles bằng cách sửa trực tiếp Migration đã tạo ở lần trước, thực hiện rollback tất cả DB và tái xây dựng lại từ đầu.
<?php // database/migrations/YYYY_MM_DD_TTTTTT_create_articles_table.php use IlluminateDatabaseSchemaBlueprint; use IlluminateDatabaseMigrationsMigration; class CreateArticlesTable extends Migration { public function up() { Schema::create('articles', function(Blueprint $table) { $table->increments('id'); $table->integer('user_id')->unsigned(); // Thêm vào $table->string('title'); $table->text('body'); $table->timestamps(); // Thêm khoá ngoại user_id và có chỉ định onDelete(‘cascade’) để nếu dữ liệu Users có bị xoá thì tất cả bài viết của hắn cũng bị xoá theo $table->foreign('user_id') ->references('id') ->on('users') ->onDelete('cascade'); }); } public function down() { Schema::drop('articles'); } }
Nếu đã chắc chắn hãy chạy command dưới để rollback tất cả DB bằng artisan để thực hiện lại toàn bộ Migration.
php artisan migrate:refresh
Do đã có sự thay đổi trong bảng nên ta cũng cần đi sửa cả Seed nữa :
<?php use AppUser; use AppArticle; use CarbonCarbon; use FakerFactory as Faker; use IlluminateDatabaseSeeder; use IlluminateDatabaseEloquentModel; use IlluminateSupportFacadesDB; class DatabaseSeeder extends Seeder { public function run() { Model::unguard(); $this->call('UsersTableSeeder'); // Thêm vào $this->call('ArticlesTableSeeder'); Model::reguard(); } } // Thêm vào class UsersTableSeeder extends Seeder { public function run() { DB::table('users')->delete(); User::create([ 'name' => 'root', 'email' => 'root@sample.com', 'password' => bcrypt('password') ]); } } class ArticlesTableSeeder extends Seeder { public function run() { DB::table('articles')->delete(); $user = User::all()->first(); $faker = Faker::create('en_US'); for ($i = 0; $i &lt; 10; $i++) { // Article::create([ // 'title' => $faker->sentence(), // 'body' => $faker->paragraph(), // 'published_at' => Carbon::today(), // ]); // Thay đổi đoạn trên thành như dưới $article = new Article([ 'title' => $faker->sentence(), 'body' => $faker->paragraph(), 'published_at' => Carbon::now(), ]); $user->articles()->save($article); // Mapping với $user } } }
Tôi đã thêm vào dữ liệu của một user và thay đổi để mapping dữ liệu bài viết với user rồi mới thực hiện lưu. Hãy chạy lệnh artisan để thực hiện seed :
php artisan db:seed
Và khi mà lưu bài viết mới thì cũng cần sửa phương thức store() trong Controller để mapping nó với user đang login :
// app/Http/Controllers/ArticlesController.php class ArticlesController extends Controller { ... public function store(ArticleRequest $request) { // Article::create($request->all()); Auth::user()->articles()->create($request->all()); Session::flash('flash_message', 'Add article successfully.'); return redirect()->route('articles.index'); } ... }
Bạn hãy thực hiện login rồi dùng tinker để xác nhận dữ liệu :
$ php artisan tinker >>> >>> $user = AppUser::where("name", "who")->first(); >>> >>> $user->articles->count();