14/08/2019, 21:28

Tập 16: Request Laravel

Rất vui được gặp lại tất cả các bạn trong series "Hành trình chinh phục Laravel framework" của mình. Trong tập ngày hôm nay, chúng ta sẽ cùng nhau tìm hiểu về "Request", nói tìm hiểu thì không hợp lý cho lắm vì chắc ai cũng biết nó là gì rồi. Ở bài này, mình sẽ hướng dẫn các bạn ...

Rất vui được gặp lại tất cả các bạn trong series "Hành trình chinh phục Laravel framework" của mình. Trong tập ngày hôm nay, chúng ta sẽ cùng nhau tìm hiểu về "Request", nói tìm hiểu thì không hợp lý cho lắm vì chắc ai cũng biết nó là gì rồi. Ở bài này, mình sẽ hướng dẫn các bạn cách sử dụng, khai thác một HTTP request trong Laravel.

Để có được object request hiện tại thông qua denpendency injection, bạn cần type-hint IlluminateHttpRequest class trên controller method. Object request sẽ tự động inject bởi service container.

<?php

namespace AppHttpControllers;

use IlluminateHttpRequest;

class UserController extends Controller
{
    public function store(Request $request)
    {
        //
    }
}

1. Dependency injection & Route parameters

Như đã đề cập ở các tập trước, nếu trong method controller có nhận các dữ liệu từ tham số URI, thì nên khai báo chúng sau các dependency class. Chẳng hạn bạn có route sau:

Route::put('user/{id}', '[email protected]');

Bạn có thể inject IlluminateHttpRequest class và lấy dữ liệu tham số id theo cách sau:

<?php

namespace AppHttpControllers;

use IlluminateHttpRequest;

class UserController extends Controller
{
    public function update(Request $request, $id)
    {
        //
    }
}

Như bạn thấy, ta sẽ ưu tiên inject dependency class trước các tham số trong route.

2. Truy cập request thông qua Route Closure (Accessing the request via route closure)

Nếu việc xử lý logic quá ngắn gọn, bạn có thể inject IluminateHttpRequest tại file route như thế này:

use IlluminateHttpRequest;

Route::get('/', function (Request $request) {
    //
});

IlluminateHttpRequest cung cấp cho chúng ta rất nhiều method để kiểm tra HTTP request hiện tại. Dưới đây là một số các method quan trọng, hay sử dụng.

1. Lấy request path (Retrieving the request path)

Với method path sẽ trả về path của request hiện tại. Nếu request hiện tại có url là http://domain.com/foo/bar thì method path sẽ trả về foo/bar.

use IlluminateHttpRequest;

Route::get('/foo/bar', function (Request $request) {
    return $request->path();
});

Các bạn có thể dùng đoạn code trên để test.

Method is cho phép bạn xác thực rằng request path hiện tại khớp với pattern mà bạn đưa ra. Bạn có thể sử dụng ký tự * để làm đại diện cho các thành phần phía sau.

use IlluminateHttpRequest;

Route::get('/admin/post', function (Request $request) {
    if ($request->is('admin/*')) {
        return 'Request path matches with "admin/*" pattern';
    }
});

Pattern ở đây chính là admin/*, nếu ta truy cập request path admin/post thì chắc chắn sẽ trùng khớp.

2. Lấy request URL (Retrieving the request URL)

Để lấy đầy đủ URL của request hiện tại, bạn có thể sử dụng method url hoặc fullUrl. Tuy nhiên method url sẽ trả về URL không chứa query string, còn đối với fullUrl sẽ trả về URL kèm với query string.

Để có thể hiểu hơn về sự khác biệt này, các bạn đăng ký route fallback như sau:

use IlluminateHttpRequest;

Route::fallback(function (Request $request) {
    return dd([
        $request->url(),
        $request->fullUrl()
    ]);
});

Ở đây mình đăng ký route fallback để có thể test với bất kỳ đường dẫn nào. Mình truyền hai method url và fullUrl vào trong một mảng rồi sau đó dump ra trình duyệt để dễ dàng quan sát.

Các bạn nạp server và truy cập đường dẫn http://localhost:8000/post?id=1 chẳng hạn, đây là kết qua chúng ta thu nhận được:

3. Lấy request method (Retrieving the request method)

Cái này thì khá dễ hiểu, ta chỉ cần sử dụng phương thức method để lấy method HTTP hiện tại của request. Ngoài ra, ta có thể kiểm tra method HTTP của request hiện tại bằng phương thức isMethod với tham số truyền vào là method HTTP bạn muốn khớp với method HTTP của request hiện tại.

use IlluminateHttpRequest;

Route::get('/', function (Request $request) {
    echo 'Current method HTTP: ' . $request->method() . '<br>';
    
    if ($request->isMethod('get')) {
        echo 'This is GET method HTTP';
    }
});

Một kết quả mong đợi:

Mặc định thì Laravel đã kết nối hai middleware TrimStrings và ConvertEmptyStringsToNull trong ứng dụng. Các middleware này được liệt kê trong lớp AppHttpKernel, cụ thể tại $middleware. Các middleware này có chức năng sẽ tự động trim tất cả các trường chứa chuỗi trên request, hơn thế nữa là chuyển đổi bất kỳ trường trống nào về giá trị null. Với nó, bạn sẽ không cần phải quá lo lắng về việc chuẩn hóa input trong route hay controller nữa.

Nếu bạn muốn vô hiệu hóa hai middleware này, bạn có thể xóa nó khỏi $middleware tại class AppHttpKernel.

Đầu tiên các bạn tạo controller FormController để ta tiến hành lấy dữ liệu input trong đó. Trong FormController bạn định nghĩa hai method dưới:

public function show()
{
    return view('form');
}

public function post(Request $request)
{
    //
}

Trong method post mình đã inject IlluminateHttpRequest để có thể lấy dữ liệu input từ request hiện tại. Tạm thời ta sẽ để đây, lát ta sẽ tiến hành test sau.

Tiếp đến là đăng ký 2 route:

Route::get('/', '[email protected]');

Route::post('/post', '[email protected]');

Cuối cùng là tạo blade view form để build các form HTML cho việc gửi data từ input. Thế là việc chuẩn bị đã hoàn tất, giờ ta tiến hành test các method hay sử dụng để lấy input từ IlluminateHttpRequest.

1. Lấy tất cả dữ liệu input (Retriveing all input data)

Tại blade view form, các bạn tạo form như sau:

<form action="/post" method="POST">
    @csrf

    <p>Username</p>
    <div>
        <input type="text" name="username">
    </div>
    
    <p>Password</p>
    <div>
        <input type="password" name="password">
    </div>

    <br>

    <div>
        <button type="submit">Login</button>
    </div>
</form>

Tiếp theo tại method post của FormController, ta sẽ thử lấy dữ liệu của tất cả input được gửi từ request bằng cách:

public function post(Request $request)
{
    dd($request->all());
}

Chúng ta sử dụng method all để thực thi việc này. Đây là kết quả:

Chú ý: Bạn có thể sử dụng method input để thay thế cho all.

2. Lấy dữ liệu của một input (Retriveing an input data)

Vẫn giữ nguyên blade view form, tại method post của FormController ta dump lệnh sau:

public function post(Request $request)
{
    dd($request->input('username'));
}

Để có thể lấy dữ liệu của một input nào đó trong request hiện tại, ta chỉ cần sử dụng method input với tham số là tên của input cần lấy trong HTML. Kết quả thu được là:

Ngoài ra, bạn có thể gán giá trị mặc định cho một input nào đó nếu như nó không xác định trong request hiện tại. Chẳng hạn lấy form trên thì mình chẳng có cái input nào tên là remember cả. Nhưng mình muốn kể cả khi không nó không tồn tại thì vẫn có được giá trị là true. Để làm thế, mình chỉ cần thêm tham số thứ hai của method input là giá trị mặc định mà mình muốn gán cho nó.

public function post(Request $request)
{
    dd($request->input('remember', true));
}

Nếu bạn làm việc với các array input, sử dụng ký hiệu . để tham chiếu đến các phần tử của nó. Chẳng hạn giờ mình sẽ thay đổi blade view form như sau:

<form action="/post" method="POST">
    @csrf
    
    <div>
        Name: <input type="text" name="products[][name]">
        Price: <input type="text" name="products[0][price]">
    </div>
    <div>
        Name: <input type="text" name="products[][name]">
        Price: <input type="text" name="products[1][price]">
    </div>

    <br>

    <div>
        <button type="submit">Submit</button>
    </div>
</form>

Đây là form mô phỏng đăng các sản phẩm lên shop online. Như bạn thấy, các thông tin của sản phẩm đều chứa name là products dạng mảng.

Giờ ta thử dump data của input products này bằng cách:

public function store(Request $request)
{
    dd($request->input('products'));
}

Chúng ta sẽ nhận được một mảng dữ liệu trả về của các input sau khi nhập thử dữ liệu:

Nếu bạn muốn lấy thông tin của sản phẩm có index 0 thì bạn sử dụng cú pháp tham chiếu sau:

$request->input('products.0'); // Lấy toàn bộ thông tin sản phẩm có index "0"

$request->input('products.0.name'); // Lấy name của sản phẩ có index "0"

Nếu bạn chỉ muốn lấy name của tất cả sản phẩm trong products thì có thể là như sau:

$request->input('products.*.name');

3. Lấy dữ liệu input từ chuỗi truy vấn (Retriveing input data from query string)

Trong khi method input dùng để lấy dữ liệu từ input trong request hiện tại (bao gồm cả query string) thì method query chỉ lấy giá trị từ query string.

Các bạn thay đổi blade view form như sau:

<form action="/post?id=1" method="POST">
    @csrf
    
    <div>
        <button type="submit">Submit</button>
    </div>
</form>

Các bạn thấy mình đã truyền query string id=1 cho action. Để lấy query id, tại method post của FormController ta thực hiện cú pháp sau:

public function store(Request $request)
{
    dd($request->query('id'));
}

Và đây là kết quả:

4. Lấy dữ liệu input thông qua thuộc tính động (Retrieving input via dynamic properties)

Bạn có thể lấy các dữ liệu input thông qua các thuộc tính trên lớp khởi tạo IlluminateHttpRequest. Thay vì:

$request->input('name');

Bạn có thể sử dụng cú pháp:

$request->name;

Nếu như không tồn tại input có được tham chiếu thì nó sẽ lấy giá trị tham số route có tên trùng với tên thuộc tính, còn nếu không tồn tại cả hai thì nó sẽ trả về giá trị là null.

5. Lấy giá trị JSON input (Retrieving JSON input value)

Chẳng hạn chúng ta có blade view form như sau:

<form action="/post" method="POST">
    @csrf

    <div>
        <button type="submit">Submit</button>
    </div>
</form>

<script src="https://code.jquery.com/jquery.min.js"></script>
<script>
    $('form').submit(function (e) {
        e.preventDefault();

        $.ajax({
            url: '/post',
            type: 'POST',
            data: {
                _token: $('input[name=_token]').val(),
                user: {
                    name: 'Lê Chí Huy',
                    age: 18
                }
            }, success: function(res) {
                console.log(res);
            }
        });
    });
</script>

Như đoạn code trên thì sau khi click "Submit", chúng ta sẽ thực thi ajax đã gửi request có kèm data json user tới controller.

Tại controller FormController, để tham chiếu data json user này, ta thực hiện như sau:

public function post(Request $request)
{
    return $request->input('user.name');
}

Chúng ta tham chiếu đến các phần tử trong JSOn như là array input ở phần trên.

6. Lấy một phần dữ liệu input (Retrieving a portion of input data)

Ta có thể dùng phương thức only hoặc except để lấy một phần dữ liệu input tại request, các phương thức này vừa chấp nhận liệt kê tham số, cũng vừa chấp nhận mảng.

// Chỉ lấy input có name là "username" và "password"
$request->only(['username', 'password']);

$request->only('username', 'password');

// Lấy tất cả input ngoại trừ input có name là "credit_card"
$request->except(['credit_card']);

$request->except('credit_card');

7. Kiểm tra nếu có giá trị input (Checking if has input value)

Bạn có thể sử dụng method has để kiểm tra sự tồn tại của một input trên request. Nếu có tồn tại sẽ trả về true.

if ($request->has('name')) {
    //
}

Lưu ý: Trường hợp có tồn tại input trong HTML nhưng có giá trị null thì method has vẫn chấp nhận và trả về true.

Bạn có thể kiểm tra nhiều input cùng lúc như sau:

if ($request->has(['name', 'email'])) {
    //
}

Còn nếu bạn muốn kiểm tra input gửi đến có rỗng hay không thì dùng method filled.

if ($request->filled('name')) {
    //
}

Trong trường hợp này nếu input có giá trị null hoặc không tồn tại thì sẽ trả về false.

8. Old input

Laravel cho phép chúng ta giữ input data của request trong request kế tiếp. Tính năng này cực kỳ hữu ích cho việc điền lại các form khi submit gặp lỗi.

Trước khi tìm hiểu thì ta hãy phân tích thuật ngữ "flash session" đã. Thông thường các session sẽ được giới hạn bởi thời gian nhất định, hoặc kéo dài cho tới khi kết thúc phiên làm việc của trình duyệt... Nhưng đối với flash session, "tuổi thọ" của nó chỉ kéo dài trong một request. Tức là tại request nào đó, flash session sẽ được khởi tạo, tới request tiếp theo, nó được sử dụng làm việc gì đó rồi sau đó bị xóa bỏ.

a. Flash input đến session (Flash input to the session)

Method flash trong lớp IlluminateHttpRequest sẽ flash input data trong request hiện tại đến session để có thể sử dụng lại trong request kế tiếp. Nếu bạn muốn tất cả input đều flash thì có thể cho dòng code này trước khi bắt đầu xử lý logic trong controller.

$request->flash();

Bạn có thể sử dụng hai method flashOnly và flashExcept để có thể lọc các input mà bạn muốn flash session.

// Chỉ flash session hai input "username" và "email" trong request
$request->flashOnly(['username', 'email']);

// Flash session tất cả input, ngoại trừ input "password" trong request
$request->flashExcept('password');

b. Flash input rồi chuyển hướng (Flash input then redirecting)

Thông thường khi cho user điền một form nào đó quá dài, nhưng nếu xảy ra lỗi, bạn muốn quay trở lại và thông báo một lỗi nào đó mà không phải mất hết các dữ liệu của form. Laravel cung cấp một lệnh chuyển hướng kèm theo flash input để ta có thể giải quyết yêu cầu này.

return back()->withInput();

Method back sẽ giúp ta chuyển hướng đến url đã truy cập trước đó, còn phương thức withInput có chức năng tương tự như flash, sẽ flash session các input từ request. Chính vì thế bạn có thể thực hiện lọc các flash input trong method withInput.

return back()->withInput(
    $request->except('password')
);

Nếu bạn muốn redirect một đường dẫn bất kì thì có thể sử dụng method redirect để thay thế cho back. Method redirect sẽ nhận tham số là URI mà bạn muốn chuyến hướng đến.

return redirect('form')->withInput();

c. Lấy old input (Retrieving old input)

Đế lấy old input từ request trước, sử dụng method old có trong IlluminateHttpRequest. Phương thức này sẽ lấy flash session đã lưu trữ ở request trước với tham số nhận vào là tên input.

$request->old('username');

Ngoài ra, Laravel còn cung cấp cho ta global helper function old. Với nó, ta có thể lấy bất kỳ flash input nào ngay cả trong Blade template. Nếu flash input không tồn tại, nó sẽ trả về giá trị null.

<input type="text" name="username" value="{{ old('username') }}">

Nói nãy giờ mình nghĩ ta nên thực hành một chút. Chúng ta sẽ sử dụng các route và controller đã đăng ký ở trước. Tại blade view form, mình có HTML form như sau:

<form action="/post" method="POST">
    @csrf

    <p>Username</p>
    <div>
        <input type="text" name="username" value="{{ old('username') }}">
    </div>
    
    <p>Password</p>
    <div>
        <input type="password" name="password">
    </div>

    <br>

    <div>
        <button type="submit">Login</button>
    </div>
</form>

Mục đích mình muốn là sau khi nhấn nút "Login" thì sẽ quay trở lại form này và truyền flash input chỉ cho input "username". Chính vì thế mình chỉ khai báo old ở trong input "username" thôi.

Rồi bây giờ chúng ta qua code xử lý logic trong controller FormController tại hàm post.

public function post(Request $request)
{
    return back()->withInput(
        $request->only('username')
    );
}

Như bạn thấy, mình sử dụng method back để trở về lại trang form, tiếp theo là setup flash input với method withInput, cuối cùng là lọc flash input mà mình muốn. Các bạn thử điền username và password, sau đó nhấn nút "Login", và đây là kết quả:

Input data của "username" đã được giữ lại sau khi trở về trang form, nếu các bạn refresh trang form lần nữa, thì các flash input sẽ biến mất.

8. Cookie

Tất cả cookie được tạo bởi framework đã mã hóa và đăng ký bằng các mã chứng thực, chính vì vậy nó sẽ được coi là không hợp lệ nếu người dùng cố tình thay đổi.

a. Lấy cookie từ request (Retrieving cookie from request)

Để nhận một giá trị cookie từ request, bạn có thể sử dụng method cookie trong lớp khởi tạo IlluminateHttpRequest.

$value = $request->cookie('name');

Ngoài ra, bạn có thể sử dụng Cookie facade để thay thế.

use IlluminateSupportFacadesCookie;

$value = Cookie::get('name');

b. Đính kèm cookie đến response (Attaching cookie to response)

Bạn có thể đính kèm mộ cookie đến object IlluminateHttpResponsebằng cách sử dụng method cookie. Bạn nên truyền đầy đủ tham số tên, giá trị cookie và thời gian tồn tại của cookie (tính theo phút). Nếu bạn bỏ qua tham số thời gian, Laravel sẽ coi cookie tồn tại như một session.

return response('Hi')->cookie('name', 'Lê Chí Huy', $minutes);

Method cookie hoạt động tương tự hàm setcookie mặc định của PHP. Chính vì vậy nó cũng có thể nhận một số tham số tùy chọn khác.

return response('Hi')->cookie(
    'name', 'value', $minutes, $path, $domain, $secure, $httpOnly
);

Ngoài ra bạn có thể sử dụng method queue trong Cookie facade. Nói một chút về "queue", nó có nghĩ đen là "xếp hàng". Hiểu một cách đơn giản, thì set cookie "queue" sẽ thực hiện cuối cùng khi đã hoàn tất cả xử lý khác trong request hiện tại. Để hiểu hơn thì chúng ta sẽ đi đến thử nghiệm.

Tại blade view form, các bạn thay đổi nội dung như sau:

@inject('cookie', 'IlluminateSupportFacadesCookie')

<p>Hello, {{ $cookie->get('name') }}</p>

<form action="/post" method="POST">
    @csrf

    <div>
        <button type="submit">Set cookie</button>
    </div>
</form>

Mình đã inject Cookie facade để có thể lấy cookie tại blade template. Ngoài ra bên dưới còn có form đển thực thi set cookie thông qua method post trong FormController.

use IlluminateSupportFacadesCookie;

public function post(Request $request)
{
    Cookie::queue('name', 'Lê Chí Huy', 5);
    
    echo $request->cookie('name');

                                          
0