0

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

Trong phần 7 tôi đã đi đến Restful resource controller, kế tiếp trong phần 8 này tôi sẽ đề cập đến những nội dung dưới đây :

  • Navigation menu
  • Implicit controller
  • Đăng kí user và authen
  • Forgot password

Navigation menu

Đến phần này tôi đã viết khá nhiều về Article nhưng chưa có đề cập đến phần Navigation nên phần này tôi muốn thử tạo một cái. Tuy đây là chức năng phần lớn sẽ là dùng boostrap3 chứ không phải chức năng của Laravel cung cấp.

ROUTING

Đầu tiên thì bằng route tôi sẽ sửa routes.php để hiển thị được Article list. Rất đơn giản !

// app/Http/routes.php
 //...
Route::get('/', 'ArticlesController@index');  // root sẽ là danh sách bài viết
//...

VIEW

Tiếp là đi tạo Partial của navigation menu. Tôi tạo ra file mới là resouces/views/navbar.blade.php. Nội dung sẽ đi copy sample của Bootstrap3, phần Navbar và chỉ sửa phần link mà thôi.

{{-- resouces/views/navbar.blade.php --}}

<nav class="navbar navbar-default">
    <div class="container">
        <div class="navbar-header">
            <!-- Phần này sẽ hiển thị nút khi mà coi bằng smart phone hoặc tablet -->
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>

            <!-- Hiển thị brand -->
            <a class="navbar-brand" href="/">My Blog</a>
        </div>

        <!-- Menu -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <!-- Menu căn trái -->
            <ul class="nav navbar-nav">
                <li>{!! link_to_route('articles.index', 'Blog') !!}</li>
                <li><a href="/contact">Contact</a></li>
                <li><a href="/about">About</a></li>
            </ul>

            <!-- Menu căn phải -->
            <ul class="nav navbar-nav navbar-right">
                <li><a href="#">Login</a></li>
                <li><a href="#">Register</a></li>

                <!-- Dropdown menu -->
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">User Name <span class="caret"></span></a>
                    <ul class="dropdown-menu" role="menu">
                        <li><a href="#">Profile</a></li>
                        <li class="divider"></li>
                        <li><a href="#">Logout</a></li>
                    </ul>
                </li>
            </ul>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>

Để ví dụ thì tôi chỉ gắn link cho mấy menu trái là ”My Blog”, “Blog”, “Contact”, “About” đến địa chỉ cụ thể còn mấy cái hiển thị bên phải gồm ”Login”, “Register”, “User Name”, “Profile”, “Logout” thì sẽ ko gắn link, chỉ là dummy menu cho đẹp.

Sửalayout.blade.php để nó hiển thị cả navigation menu là công việc hoàn tất trong vài nốt nhạc 😄

{{-- resouces/views/layout.blade.php --}}

...
<body>
    {{-- Sử dụng đến Partial của navigation menu --}}
    @include('navbar')

    <div class="container">
        @if (Session::has('flash_message'))
            <div class="alert alert-success">{{ Session::get('flash_message') }}</div>
        @endif

        @yield('content')
    </div>
...

Implicit Controllers

Đây là khái niệm chỉ việc tất cả phương thức trong Controller có thể được định nghĩa một cách đơn giản bằng 1 dòng setting route.

ROUTING

Đầu tiên hãy tạo SampleController, chỉ định lựa chọn --plain để có một Controller trống.

$ php artisan make:controller SampleController --plain

Tiếp thep là chỉnh sửa routes.php như dưới đây :

<?php // app/Http/routes.php
...
Route::controller('sample', 'SampleController');  // 追加

Tôi đã định nghĩa SampleController như là một Implicit Controller. Đối số thứ nhất của Route::controller() sẽ là base URI, còn thứ 2 sẽ là Controller name. Nếu mà access vào “/sample/XXXX” thì phương thức XXXX của SampleController sẽ được gọi.

Nếu bạn thử xác nhận route bằng artisan sẽ có kết quả :

$ php artisan route:list
+--------+--------------------------------+--------------------------+------------------+---------------------------------------------------+------------+
| Domain | Method                         | URI                      | Name             | Action                                            | Middleware |
+--------+--------------------------------+--------------------------+------------------+---------------------------------------------------+------------+
|        | GET|HEAD|POST|PUT|PATCH|DELETE | sample/{_missing}          |                  | App\Http\Controllers\SampleController@missingMethod |            |
+--------+--------------------------------+--------------------------+------------------+---------------------------------------------------+------------+

Như bạn thấy thì chỉ có một route được định nghĩa. Phần {_missing} là wildcard, dù có gì được truyền vào thì missingMethod() cũng sẽ được gọi. Còn missingMethod() được định nghĩa trong Illuminate\Routing\Controller, là một super class của SampleController.

CONTROLLER

Tôi tiến hành sửa SampleController như bên dưới :

<?php // app/Http/Controllers/SampleController.php
...
class SampleController extends Controller
{
    public function getIndex() {}

    public function getCreate() {}

    public function postStore() {}

    public function getShow($id) {}

    public function getEdit($id) {}

    public function patchUpdate($id) {}

    public function deleteDestroy($id) {}

    public function noneRoutedMethod() {} // ko được route
}

Những phương thức mà muốn routing thì cần phải bắt đầu tên phương thức bằng tên phương thức của HTTP gồm get, post, put, patch, delete. Hãy thử lại

$ php artisan route:list
+--------+--------------------------------+-----------------------------------------------------+------------------+---------------------------------------------------+------------+
| Domain | Method                         | URI                                                 | Name             | Action                                            | Middleware |
+--------+--------------------------------+-----------------------------------------------------+------------------+---------------------------------------------------+------------+
|        | GET|HEAD                       | sample/index/{one?}/{two?}/{three?}/{four?}/{five?}   |                  | App\Http\Controllers\SampleController@getIndex      |            |
|        | GET|HEAD                       | sample                                                |                  | App\Http\Controllers\SampleController@getIndex      |            |
|        | GET|HEAD                       | sample/create/{one?}/{two?}/{three?}/{four?}/{five?}  |                  | App\Http\Controllers\SampleController@getCreate     |            |
|        | POST                           | sample/store/{one?}/{two?}/{three?}/{four?}/{five?}   |                  | App\Http\Controllers\SampleController@postStore     |            |
|        | GET|HEAD                       | sample/show/{one?}/{two?}/{three?}/{four?}/{five?}    |                  | App\Http\Controllers\SampleController@getShow       |            |
|        | GET|HEAD                       | sample/edit/{one?}/{two?}/{three?}/{four?}/{five?}    |                  | App\Http\Controllers\SampleController@getEdit       |            |
|        | PATCH                          | sample/update/{one?}/{two?}/{three?}/{four?}/{five?}  |                  | App\Http\Controllers\SampleController@patchUpdate   |            |
|        | DELETE                         | sample/destroy/{one?}/{two?}/{three?}/{four?}/{five?} |                  | App\Http\Controllers\SampleController@deleteDestroy |            |
|        | GET|HEAD|POST|PUT|PATCH|DELETE | sample/{_missing}                                     |                  | App\Http\Controllers\SampleController@missingMethod |            |
+--------+--------------------------------+-----------------------------------------------------+------------------+---------------------------------------------------+------------+

Những phương thức trong SampleController đã được chức năng Implicit Controller tự động routing. Phần tham số có từ 1〜5 có thể nhận giá trị một cách tuỳ ý. Riêng noneRoutedMethod() do không bắt đầu bằng tên phương thức của HTTP nên có thể hiểu nó sẽ không được routing.

Register User và Authen

Từ bản Laravel 5.1 được tạo ra thì chức năng đăng kí user, chứng thực và quên mật khẩu đã được chuẩn bị sẵn Controller có thể sử dụng được ngay. Nhưng mà để dùng mấy chức năng này thì cần tiến hành thiết lập routing và tạo View ... Trong phần này tôi chỉ nói về đăng kí user và chứng thực login.

Migration

Khi mà tạo ra project Laravel thì có 2 files migration dùng cho user và quên mật khẩu được tạo ra. Nếu mà migration được chạy một lần rồi thì ắt hẳn đã có DB tương ứng rồi.

ROUTING

Thêm route sau vào routes.php

<?php // app/Http/routes.php
...
// Authentication routes...
Route::get('auth/login', 'Auth\AuthController@getLogin');
Route::post('auth/login', 'Auth\AuthController@postLogin');
Route::get('auth/logout', 'Auth\AuthController@getLogout');

// Registration routes...
Route::get('auth/register', 'Auth\AuthController@getRegister');
Route::post('auth/register', 'Auth\AuthController@postRegister');

// Route::controller('auth', 'Auth\AuthController');  // ①

Tổng cộng tôi đã thêm 5 route. Và đương nhiện bạn có thể dùng implicit controller ở trên thay thế cho 5 cái đó. Hãy thử xác nhận :

php artisan route:list
+--------+----------+--------------------------+------------------+-------------------------------------------------------+------------+
| Domain | Method   | URI                      | Name             | Action                                                | Middleware |
+--------+----------+--------------------------+------------------+-------------------------------------------------------+------------+
|        | GET|HEAD | auth/login               |                  | App\Http\Controllers\Auth\AuthController@getLogin     | guest      |
|        | POST     | auth/login               |                  | App\Http\Controllers\Auth\AuthController@postLogin    | guest      |
|        | GET|HEAD | auth/logout              |                  | App\Http\Controllers\Auth\AuthController@getLogout    |            |
|        | GET|HEAD | auth/register            |                  | App\Http\Controllers\Auth\AuthController@getRegister  | guest      |
|        | POST     | auth/register            |                  | App\Http\Controllers\Auth\AuthController@postRegister | guest      |
+--------+----------+--------------------------+------------------+-------------------------------------------------------+------------+

Navigation

Chỉnh sửa navigation menu một chút :

{{-- resouces/views/navbar.blade.php --}}
<nav class="navbar navbar-default">
    <div class="container">
        <div class="navbar-header">
            <!-- Hiển thị dạng nút khi trên smart phone hay tablet -->
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
                ...
            </button>

            <!-- hiển thị brand -->
            <a class="navbar-brand" href="/">My Blog</a>
        </div>

        <!-- Menu -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">

            <!-- Menu trái -->
            <ul class="nav navbar-nav">
                <li>{!! link_to_route('articles.index', 'Blog') !!}</li>
                <li><a href="/contact">Contact</a></li>
                <li><a href="/about">About</a></li>
            </ul>

            <!-- Menu phải -->
            <ul class="nav navbar-nav navbar-right">

                @if (Auth::guest())
                    {{-- Khi mà chưa login --}}

                    <li><a href="/auth/login">Login</a></li>
                    <li><a href="/auth/register">Register</a></li>
                @else
                    {{-- Khi mà đang login --}}

                    <!-- Drop down menu -->
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
                            {{ Auth::user()->name }}
                            <span class="caret"></span>
                        </a>
                        <ul class="dropdown-menu" role="menu">
                            <li><a href="/auth/logout">Logout</a></li>
                        </ul>
                    </li>
                @endif
            </ul>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>

Ở đây tôi dùng Auth facade để access vào thông tin của user. Dựa vào Auth::guest() để phán đoán user có đang login hay không nhằm thay đổi menu. Còn dfung Auth::user()->name thì sẽ lấy được tên user login là gì. Dựa vào định nghĩa route tôi đã chỉ định link path của Login, Register, Logout.

VIEW

Kế đến tôi sẽ cần phải tạo ra View của 2 chức năng đăng kí user và chứng thực.

./resources
└── views
    └── auth
         ├── login.blade.php
         └── register.blade.php

login.blade.php

{{-- resources/views/auth/login.blade.php --}}

@extends('layout')

@section('content')
<div class="container-fluid">
  <div class="row">
    <div class="col-md-8 col-md-offset-2">
      <div class="panel panel-default">
        <div class="panel-heading">Login</div>
        <div class="panel-body">
          @if (count($errors) > 0)
            <div class="alert alert-danger">
              <strong>Whoops!</strong> There were some problems with your input.<br><br>
              <ul>
                @foreach ($errors->all() as $error)
                  <li>{{ $error }}</li>
                @endforeach
              </ul>
            </div>
          @endif

          <form class="form-horizontal" role="form" method="POST" action="/auth/login">
            {{-- xử lý với CSRF--}}
            <input type="hidden" name="_token" value="{{ csrf_token() }}">

            <div class="form-group">
              <label class="col-md-4 control-label">E-Mail Address</label>
              <div class="col-md-6">
                <input type="email" class="form-control" name="email" value="{{ old('email') }}">
              </div>
            </div>

            <div class="form-group">
              <label class="col-md-4 control-label">Password</label>
              <div class="col-md-6">
                <input type="password" class="form-control" name="password">
              </div>
            </div>

            <div class="form-group">
              <div class="col-md-6 col-md-offset-4">
                <div class="checkbox">
                  <label>
                    <input type="checkbox" name="remember"> Remember Me
                  </label>
                </div>
              </div>
            </div>

            <div class="form-group">
              <div class="col-md-6 col-md-offset-4">
                <button type="submit" class="btn btn-primary" style="margin-right: 15px;">
                  Login
                </button>

                <a href="/password/email">Forgot Your Password?</a>
              </div>
            </div>
          </form>
        </div><!-- .panel-body -->
      </div><!-- .panel -->
    </div><!-- .col -->
  </div><!-- .row -->
</div><!-- .container-fluid -->
@endsection

register.blade.php

{{-- resources/views/auth/register.blade.php --}}

@extends('layout')

@section('content')
<div class="container-fluid">
  <div class="row">
    <div class="col-md-8 col-md-offset-2">
      <div class="panel panel-default">
        <div class="panel-heading">Register</div>
        <div class="panel-body">
          @if (count($errors) > 0)
            <div class="alert alert-danger">
              <strong>Whoops!</strong> There were some problems with your input.<br><br>
              <ul>
                @foreach ($errors->all() as $error)
                  <li>{{ $error }}</li>
                @endforeach
              </ul>
            </div>
          @endif

          <form class="form-horizontal" role="form" method="POST" action="/auth/register">
            {{-- Xử lý CSRF--}}
            <input type="hidden" name="_token" value="{{ csrf_token() }}">

            <div class="form-group">
              <label class="col-md-4 control-label">Name</label>
              <div class="col-md-6">
                <input type="text" class="form-control" name="name" value="{{ old('name') }}">
              </div>
            </div>

            <div class="form-group">
              <label class="col-md-4 control-label">E-Mail Address</label>
              <div class="col-md-6">
                <input type="email" class="form-control" name="email" value="{{ old('email') }}">
              </div>
            </div>

            <div class="form-group">
              <label class="col-md-4 control-label">Password</label>
              <div class="col-md-6">
                <input type="password" class="form-control" name="password">
              </div>
            </div>

            <div class="form-group">
              <label class="col-md-4 control-label">Confirm Password</label>
              <div class="col-md-6">
                <input type="password" class="form-control" name="password_confirmation">
              </div>
            </div>

            <div class="form-group">
              <div class="col-md-6 col-md-offset-4">
                <button type="submit" class="btn btn-primary">
                  Register
                </button>
              </div>
            </div>
          </form>
        </div><!-- .panel-body -->
      </div><!-- .panel -->
    </div><!-- .col -->
  </div><!-- .row -->
</div><!-- .container-fluid -->
@endsection

Lần này thì cả view 2 phần đều sử dụng đến Form. Dù trước đây tồi từng sử dụng Helper của package laravelcollective/html nhưng phần này tôi sẽ dùng thẻ form. Trường hợp này sẽ cần tự nhúng cái token coi như là đối sách với CSRF. Ngoài ra thì trog trường hợp có lỗi nhập và form sẽ được load lại nhưg sẽ cần hiển thị lại giá trị trước đó bằng cách sử dụng phương thức old().

CONTROLLER

Chỉnh sửa file AuthController.php để chỉ định nơi đến sau khi đăng kí với login. Nhưng nếu ko chỉ định thì mặc định sẽ bay về root /home.

// app/Http/Controllers/Auth/AuthController.php

class AuthController extends Controller {
    use AuthenticatesAndRegistersUsers;

    protected $redirectTo = '/articles'; // Thêm vào. nơi sẽ đến sau tạo và login

    public function __construct()
    {
        $this->middleware('guest', ['except' => 'getLogout']);
    }

    ...
}

Đến đây là hoàn tất công việc và bạn có thể thử đăng kí user. Trên navigation click vào Register sẽ đến màn đăng kí user. Khi đã nhập đầy đủ thôg tin user sẽ tiến hành đăng kí. List bài viết sẽ được hiển thị và tên user sẽ hiển thị ở phần trên bên phải

Thực hiện logout ra và click vào navigation menu phải sẽ thấy Login, Register. Hãy thử login với user vừa tạo ở trên.

Forgot Password

Như vậy đã xong phần login với đăng kí ở trên, giờ tôi sẽ làm nốt chức năng forgot password luôn cho đồng bộ.

Mail test

Vì khi quên pass thì sẽ có chức năng gửi mail nên cần chuẩn bị sẵn môi trường có thể gửi mail được. Đối với môi trường local thì có thể dùng MailCatcher rất tiện.

ROUTING

Hãy thêm vào routes.php các route sau :

<?php // app/Http/routes.php
...

// Password reset link request routes...
Route::get('password/email', 'Auth\PasswordController@getEmail');
Route::post('password/email', 'Auth\PasswordController@postEmail');

// Password reset routes...
Route::get('password/reset/{token}', 'Auth\PasswordController@getReset');
Route::post('password/reset', 'Auth\PasswordController@postReset');

// Route::controller('password', 'Auth\PasswordController'); // ①

Note : Bạn có thể dùng Implicit Controller.

VIEW

Cần tạo ra View dưới cho chức năng quên mật khẩu này :

resources/
└── views
    ├── auth
    │   ├── password.blade.php // phần gửi mail
    │   └── reset.blade.php // phần quên mật khẩu
    └── emails
         └── password.blade.php // mail quên mật khẩu

```PHP
// auth/password.blade.php
{{-- resources/views/auth/password.blade.php --}}

@extends('layout')

@section('content')
<div class="container-fluid">
  <div class="row">
    <div class="col-md-8 col-md-offset-2">
      <div class="panel panel-default">
        <div class="panel-heading">Reset Password</div>
        <div class="panel-body">
          @if (session('status'))
            <div class="alert alert-success">
              {{ session('status') }}
            </div>
          @endif

          @if (count($errors) > 0)
            <div class="alert alert-danger">
              <strong>Whoops!</strong> There were some problems with your input.<br><br>
              <ul>
                @foreach ($errors->all() as $error)
                  <li>{{ $error }}</li>
                @endforeach
              </ul>
            </div>
          @endif

          <form class="form-horizontal" role="form" method="POST" action="/password/email">
            <input type="hidden" name="_token" value="{{ csrf_token() }}">

            <div class="form-group">
              <label class="col-md-4 control-label">E-Mail Address</label>
              <div class="col-md-6">
                <input type="email" class="form-control" name="email" value="{{ old('email') }}">
              </div>
            </div>

            <div class="form-group">
              <div class="col-md-6 col-md-offset-4">
                <button type="submit" class="btn btn-primary">
                  Send Password Reset Link
                </button>
              </div>
            </div>
          </form>
        </div><!-- .panel-body -->
      </div><!-- .panel -->
    </div><!-- .col -->
  </div><!-- .row -->
</div><!-- .container-fluid -->
@endsection
// auth/reset.blade.php
{{-- resources/views/auth/reset.blade.php --}}

@extends('layout')

@section('content')
<div class="container-fluid">
  <div class="row">
    <div class="col-md-8 col-md-offset-2">
      <div class="panel panel-default">
        <div class="panel-heading">Reset Password</div>
        <div class="panel-body">
          @if (count($errors) > 0)
            <div class="alert alert-danger">
              <strong>Whoops!</strong> There were some problems with your input.<br><br>
              <ul>
                @foreach ($errors->all() as $error)
                  <li>{{ $error }}</li>
                @endforeach
              </ul>
            </div>
          @endif

          <form class="form-horizontal" role="form" method="POST" action="/password/reset">
            <input type="hidden" name="_token" value="{{ csrf_token() }}">
            <input type="hidden" name="token" value="{{ $token }}">

            <div class="form-group">
              <label class="col-md-4 control-label">E-Mail Address</label>
              <div class="col-md-6">
                <input type="email" class="form-control" name="email" value="{{ old('email') }}">
              </div>
            </div>

            <div class="form-group">
              <label class="col-md-4 control-label">Password</label>
              <div class="col-md-6">
                <input type="password" class="form-control" name="password">
              </div>
            </div>

            <div class="form-group">
              <label class="col-md-4 control-label">Confirm Password</label>
              <div class="col-md-6">
                <input type="password" class="form-control" name="password_confirmation">
              </div>
            </div>

            <div class="form-group">
              <div class="col-md-6 col-md-offset-4">
                <button type="submit" class="btn btn-primary">
                  Reset Password
                </button>
              </div>
            </div>
          </form>
        </div><!-- panel-body -->
      </div><!-- .panel -->
    </div><!-- .col -->
  </div><!-- .row -->
</div><!-- .container-fluid -->
@endsection
/// emails/password.blade.php
{{-- resources/views/emails/password.blade.php --}}

Click here to reset your password: {{ url('password/reset/'.$token) }}

CONTROLLER

Tôi tiến hành sửa file PasswordController.php để redirect đến trang sau khi quên mật khẩu. Nếu ko chỉ định thì sẽ bay về /home.

// app/Http/Controllers/Auth/PasswordController.php

class PasswordController extends Controller {
    use ResetsPasswords;

    protected $redirectTo = '/articles';    // sẽ bay đến đây sau khi xong quên mật khẩu 
    ...

Bạn hãy thử xem chức năng quên mật khẩu đã chạy được chưa :


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í