เริ่มต้นสร้างเว็บ Laravel ใหม่อีกครั้ง โดยการสร้างในชื่อ routingApp
>composer create-project laravel/laravel routingApp
ปรับปรุงเส้นทางใหม่ จากเดิมที่คืนค่า view ให้แก้เป็นคืนข้อมูลอักษรอย่างเดียว
Code 1. routes/web.php
<?php
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
//return view('welcome');
return 'Hello World';
});
เมื่อดูผลการทำงาน เหมือนกับการเรียกใช้ คำสั่ง echo "Hello World";
เส้นทางมี URL กับ View
ตามปกติ เส้นทางจะมี จะระบุชื่อ เส้นทาง และ view ดังตัวอย่างที่ผ่านมา ใช้ “/” แทนเส้นทางเริ่มต้น แต่เนื่องจากไม่ได้ระบุชื่อ view จึงส่งคืนค่า อักษร ดังตัวอย่างที่ผ่านมา และนี้ถือเป็นเส้นทางแรกที่พบ จึงต้องเขียนเส้นทางไว้เป็นเส้นทางแรก ถ้าจะให้เขียนเส้นทางต่อไป ก็ต้องเขียนเส้นทางตามลำดับ
URI: Uniform Resource Identifier หมายถึงการเข้าทรัพยากรโดยระบุสิ่งที่ต้องการ เช่น /user/1 หรือ www.google.com/user/1
เส้นทางในลำดับที่สอง ต่อไปนี้ ต้องการให้ ไปยัง "http://localhost:8000/users" ซึ่งเป็น URI และจะต้องมีตัวแปรที่สอง เป็นชื่อ view เช่น ใช้ชื่อเหมือน URL นอกจากนี้ยังอาจมีตัวแปรที่สามได้เพื่อแทนข้อมูลที่ส่ง
Code 2. routes/web.php
<?php
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
//return view('welcome');
return 'Hello World';
});
Route::view('/users', 'users');
ดังนั้นเราจะต้องมีหน้า view ในชื่อไฟล์ users.blade.php ในหน้านี้ต้องการแสดงรายชื่อ ต่าง ๆ ในรูปตาราง ใช้รูปแบบตาม Bootstrap ตาม URL ใน <head>
Code 3. resources/views/users.blade.php
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Laravel</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
</head>
<body>
<div class="content">
<h2>Users</h2>
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">First</th>
<th scope="col">Last</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">1</th>
<td>Mark</td>
<td>Otto</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
เราจะทดสอบการทำงานตาม URL นี้ได้: http://localhost:8000/users
กรณีต้องการใช้ ไฟล์ bootstrap.min.css โดยที่เก็บไว้ในเว็บแอปฯ ของเราเอง ให้สร้าง โฟล์เดอร์เก็บไฟล์ไว้ที่ public โดยอาจสร้างเป็นโฟลเดอร์ css เช่นเดียวกับใช้งานไฟล์ jquery.js ก็ทำโฟลเดอร์ js
public/css/bootstrap.min.csspublic/js/jquery.min.js
ให้คอนโทรลเลอร์ส่ง View
นอกจากจะส่ง view ตามเส้นทาง URI แล้วยังสามารถส่ง view ที่มาจากคอนโทรลเลอร์ได้ด้วย จากตัวอย่างต่อไปนี้ ใช้คอนโทรลเลอร์ ในชื่อ UserController จึงต้องประกาศใช้ (use) คอนโทรลเลอร์นี้ก่อน
Code 4. routes/web.php
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\UserController;
Route::get('/', function () {
return 'Hello World';
});
Route::get('/users', [UserController::class, 'index']);
จากตัวอย่างนี้เป็นใช้คอนโทรลเลอร์เป็นตัวเลือก view โดยที่ UserController เรียกใช้ฟังก์ชัน index ซึ่งจะคืนค่า view ดังนั้นเราจะต้องมีคลาส UserController และมีฟังก์ชัน index( ) ที่จะคืนค่า view
Code 5. app/Http/Controllers/UserController.php
<?php
namespace App\Http\Controllers;
use App\Models\User;
class UserController extends Controller{
public function index() {
return view('users', [
'users' => User::all()
]);
}
}
จากตัวอย่างนี้ คลาสนี้ กำหนดให้มี namespace เป็น App\Http\Controllers ดังเราจะต้องสร้างโฟลเดอร์ Controllers เพิ่มขึ้นอีกภายโฟลเดอร์ App/Http และมีการใช้ User เป็นข้อมูลที่จะนำมาแสดงหน้าเว็บ ซึ่งจะสร้างเพิ่มอีกภายหลัง
สำหรับฟังก์ชัน index() ฟังก์ชันนี้ ทำหน้าที่คืนค่า view และข้อมูลเพิ่มเติม จากการเรียกคลาส User และฟังก์ชัน all() โดยฟังก์ชัน all() ต้องการให้คืนข้อมูลผู้ใช้ในรูปแบบอาร์เรย์
ให้ Model ส่งค่าไปกับคอนโทรเลอร์
เราจะใช้โฟลเดอร์ Models เพื่อเก็บกลุ่มข้อมูลที่เราสนใจ เราอาจสร้างโฟดเดอร์เพิ่มอีกก็ได้เพื่อรวมกลุ่มข้อมูลที่ใกล้เคียงกัน ซึ่งแล้วแต่ว่าเราจะออกแบบที่เหมาะสม
ในที่นี้เราใช้คลาส User.php เพื่อเป็นตัวแทนข้อมูลผู้ใช้ ข้อมูลเป็นการสมมุติ ในได้มาซึ่งข้อมูล อาจได้มาจากฐานข้อมูล แล้วค่อยเลือกมาใช้งานในการแสดงผลตามความต้องการใช้ในแต่ละงาน
Code 6. app/Models/User.php
<?php
namespace App\Models;
class User{
public static $users =array(
array("id" => 1, "fname" => "Tee", "lname" => "L."),
array("id" => 2, "fname" => "Mon", "lname" => "K."),
array("id" => 3, "fname" => "Pol", "lname" => "V."),
);
public static function all(){
return self::$users;
}
}
จากคลาส User นี้ สังเกตว่าสมาชิกแต่ละตัวเป็นค่า static เพื่อให้เรียกใช้งานผ่านคลาสโดยตรง ข้อมูลที่สมมุติขึ้น มีเพียงสามรายการ แต่ละรายการมี id, fname, lname
เมื่อแบบจำลองข้อมูลพร้อมรองรับการทำงานตามการเรียกของคอนโทรลเลอร์ ก็พร้อมส่งไปแสดงผลในรูปตารางที่เขียนไว้ที่ users.blade.php โดยเขียนส่งไปในรูปตัวแปร $users ตามที่ระบุตามคอนโทรลเลอร์ ในการแสดงผลใช้การวนซ้ำ @foreach … @endforeach ดังแก้ไขเฉพาะข้อมูลในส่วน <tbody> ดังนี้
Code 7. resources/views/users.blade.php
<tbody>
@foreach($users as $user)
<tr>
<th scope="row">{{$user['id']}}</th>
<td>{{$user['fname']}}</td>
<td>{{$user['lname']}}</td>
</tr>
@endforeach
</tbody>
เปลี่ยนเส้นทาง ด้วย redirect( )
ในบางครั้ง เราต้องการเปลี่ยนเส้นทาง ไปยังอีกทางหนึ่ง เช่น บน URI: “/” ซึ่งเป็นหน้าเริ่มต้น แต่ต้องการให้ไปยังหน้า users เราใช้คำสั่ง redirect( )
Code 8. routes/web.php
Route::get('/', function () {
return 'Hello World';
});
Route::redirect('/', '/users');
Route::get('/users', [UserController::class, 'index']);
จากตัวอย่างนี้ เมื่อผู้ใช้ไปยัง URL:http://localhost:8000/ จะถูกเปลี่ยนเส้นทางไปยัง URL:/users และพบต่อไปว่าเส้นทาง URL:/users จะเป็น view ที่คอนโทรลเลอร์ UserController เป็นผู้เลือกให้
ส่งข้อมูลไปกับเส้นทาง
ตามที่เคยได้กล่าวมาก่อนแล้วว่า การส่งข้อมูลไป จะเป็นตัวแปรที่สาม เช่น ส่งไปยัง URI: /user, ตัวแปรที่สอง เป็น view: user และข้อมูลเลือกเฉพาะรายแรก จะเขียนดังนี้
Code 9. routes/web.php
//เขียนเพิ่มไปกับก่อนหน้านี้ในบรรทัดถัดไป
Route::view("/user", "user",
['user'=>array('id'=>1, 'fname'=>'Pol', 'lname'=>'L.')]);
แต่การส่งแบบนี้ต้องการจะแจ้งอะไรบางอย่างไปยัง view
Code 10. resources/views/user.blade.php
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Laravel</title>
</head>
<body>
<div class="content">
<h2>Users</h2>
<p class="lead">
{{$user['id']}}:{{$user['fname']}} {{$user['lname']}}
</p>
</div>
</body>
</html>
เมื่อทดลองใส่ URL: http://localhost:8000/user ก็จะแสดงผลข้อมูล
แต่ดูจะมีประโยชน์ไม่มาก เพราะข้อมูลกำหนดแน่นอน ทางที่ดีกว่า คือควรส่งไปในรูปตัวแปร ซึ่งเปลี่ยนแปลงได้การใช้งาน URL
ส่งตัวแปรไปกับเส้นทาง
การส่งข้อมูลผ่านในรูปตัวแปร แล้วให้คอนโทรลเลอร์จัดการดูจะมีประโยชน์กว่า เพราะนำไปทำอะไรได้ เช่น ต้องการข้อมูลเฉพาะรายที่มี id=1 ตัวเลข 2 หรือ 3 เป็นตัวเลขที่เปลี่ยนแปลงได้ ดังนั้นแล้ว จึงควรเขียนส่งผ่านคอนโทรลเลอร์ดังนี้
Code 11. routes/web.php
//เขียนเพิ่มไปกับก่อนหน้านี้ในบรรทัดถัดไป
Route::get('/user/{id}', function($id){
return UserController::getById($id);
});
Route::get(
'/user/{id}',
[UserController::class, 'getById']);
จากตัวอย่างนี้มีฟังก์ชันใหม่ คือ getById($id) ที่มีตัวแปรเข้าหนึ่งตัว เราจำเป็นต้องสร้างฟังก์ชันนี้ด้วย ใช้เป็นค่าสเตติก เพราะเรียกใช้แบบสเตติก (::)
Code 12. app/Http/Controllers/UserController.php
public static function getById($id){
return view('user', [
'user'=> User::getById($id)
]);
}
การเรียก User::getById($id) ทำให้เห็นว่า คลาส User ต้องมีฟังก์ชันสเตติกชื่อ getById($id) ด้วย ดังนั้นแล้วเราจะต้องสร้างฟังก์ชันนี้เพื่อคืนค่าตามเลข id ที่ร้องขอมา
Code 13. app/Models/User.php
public static function getById($id){
foreach(self::$users as $user){
if($id == $user['id']){
return $user;
}
}
//เพิ่มค่าปริยาย กรณีหาไม่พบ เพื่อป้องกันความผิดพลาด
return array("id" => 0, "fname" => "NONE", "lname" => "NONE");
}
ใช้แบบ Array_filter
public static funciton getById($id){
$users = array_filter(self::$users, function($item) use($id) {
return $item['id']== $id;
});
if(count($users)>0) return $users[$id-1];
return array("id" => 0, "fname" => "NONE", "lname" => "NONE");
}
เมื่อทุกอย่างพร้อมแล้วก็สามารถทดสอบผลการทำงานโดยใส่เลขid ตามที่มีในระบบ เช่น ใส่ URI: user/1
URL:http://localhost:8000/user/1
เป็นตัวแทนของ การเลือกเฉพาะรายที่มี id=1 โดยเมื่อพิมพ์ URL นี้จะได้ตัวแปร ซึ่งเขียนภายใน {id} แล้วตัวแปรนี้นำไปใช้กับฟังก์ชัน function($id) ในชื่อตัวแปรว่า $id ชื่อตัวแปรนี้จะแทน ค่า {id} ซึ่งจะเขียนในชื่อตัวเป็นอย่างอื่นก็ได้ เช่น function($userId) เพราะใช้การนับตำแหน่ง กรณีนี้มีตัวแปรหนึ่งตัว จึงเป็นตำแหน่งที่หนึ่ง ถ้ามีหลายตัวแปรเช่น /user/{group}/{id} เขียนลักษณะนี้มีสองตัวแปร จึงเป็นเป็นตัวแปรในฟังก์ชัน เป็น function($g, $i) แทนตัวแปร {group} และ {id} ตามลำดับ
เราอาจสงสัยว่าทำไมต้องใช้แบบสเตติก ตลอดหรือไม่ ความจริงคือไม่จำเป็น เราสามารถสร้างออบเจ็กต์แล้วเรียกใช้งานฟังก์ชันจากคลาส โดยไม่จำเป็นต้องสร้างเป็นสเตติก แต่วิธีการส่งข้อมูลไปยังคอนโทรลเลอร์จะใช้อีกวิธีหนึ่ง และวิธีนี้ถือว่าละเว้นการส่งตัวแปรไปยังคอนโทรลเลอร์ได้ด้วย
Code 14. routes/web.php
//Controller base: object method
Route::get('/user/{id}', [UserController:: class, 'getById']);
จากตัวอย่างนี้ ให้สังเกตว่า มีตัวแปรใน URI แต่ไม่จำเป็นต้องใส่ตัวแปรในฟังก์ชัน getById เพราะวิธีการนี้จะถือว่าใช้ตัวแปรใน URI ได้เลย และที่สำคัญฟังก์ชันนี้ในคอนโทรลเลอร์ ไม่จำเป็นต้องใส่ค่าสเตติกอีกต่อไป (ทดสอบโดยให้คำว่า static ออกในฟังก์ชัน getById ของ UserController)
ส่งตัวแปรทางเลือกไปกับเส้นทาง
ในบางครั้งเราอาจตั้งค่าปริยายไว้ เช่น กำหนดค่า id=1 ไว้ หมายความว่าถ้าผู้ใช้งาน ไม่ใส่ค่า id ตัวแปรเส้นทางจะกำหนดให้เองคือมีค่า id=1 แต่อย่างไรก็ตาม การสร้างตัวแปรในเส้นทาง URL ต้องกำหนดเป็นเครื่องหมาย “?” หลังตัวแปร ดังตัวอย่างต่อไปไปนี้
Code 15. routes/web.php
Route::get('/user/{id?}', function($id=1){
return UserController::getById($id);
});
ถึงตอนนี้เราทดสอบค่า URL:http://localhost:8000/user ก็จะได้ผลเหมือนใส่ user/1 (หากว่า เส้นทาง /user ที่เคยสร้างไว้ดังใน Code 9 ต้องลบที่ทิ้งก่อน เพราะเมื่อไม่ใส่ ค่า id จะกลับไปยังเส้นทางเดิมตามเขียนที่ Code 9)
ถ้าจะส่งตัวแปรแบบ Code 14 ด้วยก็ทำได้
Code 16. routes/web.php
Route::get('/user/{id?}', [UserController::class, 'show'],['id'=>1]);
สร้างข้อกำหนดให้กับตัวแปร
เมื่อตัวแปรให้ผู้ใช้เลือกใส่แล้ว เรายังสามารถสร้างข้อกำหนดให้ผู้ใช้ใส่ตัวแปรตามข้อกำหนดที่เราสร้างขึ้นได้ วิธีการสร้างใช้รูปแบบ ประพจน์รีกูล่าร์ (Regular Expression)
จากตัวอย่างข้อมูลในโมเดล User ที่ได้สร้างจำลองไว้มีเพียงแค่สามรายการ คือ รายการที่มี id เท่ากับ 1, 2, และ 3 เท่านั้น ดังนั้นจึงกำหนดให้ใส่ข้อมูลได้เพียง 1, 2, หรือ 3 นอกเหนือจากนี้จะไม่ยอมรับ หมายความว่าจะไม่มี URL ที่ไม่ยอมรับนี้
Code 17. routes/web.php
Route::get('/user/{id?}', function($id=1){
return UserController::getById($id);
})->where('id','[1-3]');
จากตัวอย่างนี้ใช้ฟังก์ชัน where( ) โดยใส่สองตัวแปร ตัวแปรแรกแทน ตัวแปรของ URL และตัวแปรที่สองแทน ประพจน์รีกูล่าร์
สมมุติว่าเราใส่ URL:http://localhost:8000/user/4 ซึ่ง URL ที่ไม่ยอมรับ หน้าเว็บขี้นแสดงว่า "404 | Not found"
สร้าง Page Not Found
เมื่อเกิดหา URL ไม่พบในระบบเส้นทาง ก็จะเกิดหน้า Page Not Found เกิดขึ้น ซึ่งก็ดีแล้ว แต่บางครั้งเราอาจต้องการสร้างหน้านี้ขึ้นเอง ในรูปแบบของตนเอง
สมมุติให้ต้องการให้หน้า Page Not Fond มีหน้าตาแบบนี้
Code 18. resources/views/pageNotFound.blade.php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Laravel</title>
<style>
html, body {
background-color: #fff;color: #636b6f; height: 100vh; margin: 0; }
.full-height { height: 100vh; }
.flex-center {
align-items: center; display: flex;justify-content: center; }
.position-ref { position: relative; }
.content {text-align: center; }
.title { font-size: 84px; }
.m-b-md { margin-bottom: 30px; }
</style>
</head>
<body>
<div class="flex-center position-ref full-height">
<div class="content">
<div class="title m-b-md"> Laravel </div>
<p>Page Not Found</p>
</div>
</div>
</body>
</html>
แล้วกำหนดกำหนดเส้นทาง ไปยัง view ที่เป็น Page Not Found โดยใช้ฟังก์ชัน fallback( ) โดยต้องให้เส้นทางนี้อยู่เป็นเส้นทางสุดท้ายเสมอ
Code 19. routes/web.php
Route::fallback(function(){
return view('pageNotFound');
});
สร้างชื่อให้เส้นทาง
เส้นทางแต่ละเส้นทางก็มีชื่ออยู่แล้วตาม URI ที่ระบุ แต่บางที เส้นทางอาจจะใช้ชื่อยาวไป ทำให้การอ้างอิงทำได้ไม่สะดวก เช่น URL: user/profile แต่จะอ้างอิงเพียงแค่ URI: profile พอแค่นี้
วิธีการคือการสร้างชื่อเส้นทางด้วยฟังก์ชัน name() ตามหลังฟังก์ชัน get() เช่น
Code 20. routes/web.php
Route::get('/user/{id?}', function($id=1){
return UserController::getById($id);
})->name('profile');
ทันที่ตั้งชื่อเส้นใหม่ ก็สามารถนำไปอ้างอิงในที่อื่น ๆ ได้ เช่น เมื่อนำไปใช้งานในกรณีนี้
$url = route('profile',['id'=> 1]);
ถ้าต้องการใส่ข้อมูลเพิ่มเติมไปกับฟังก์ชัน get() ก็ใส่ดังนี้
$url = route('profile', ['id' => 1, 'photos' => 'yes']);
จะทำให้ URI: /user/1/profile?photos=yes แต่ดูเหมือนว่าตัวแปรต้องระบุอย่างนี้คงไม่ดีแน่ ซึ่งก็มีทางแก้ได้ แต่อย่างไรก็ตามต้องเขียนในรูปแบบนี้ ยกตัวอย่างเช่น ต้องการส่ง URL ตามชื่อที่สร้างใหม่ในชื่อ profile และมีตัวแปรตามที่ผู้ใช้คลิกเลือก ตามตาราง Users ดังที่เคยทำไว้ในไฟล์ users.blade.php
Code 21. resources/views/users.blade.php
@foreach($users as $user)
<tr>
<th scope="row">
@php
$url = route('profile', ['id'=>$user['id']]);
@endphp
<a href='{{$url}}'> {{$user['id']}} </a>
</th>
<td>{{$user['fname']}}</td>
<td>{{$user['lname']}}</td>
</tr>
@endforeach
ฟังก์ชันสร้างเส้นทางตามแบบ RESTful
การทำงานของ RESTful ต้องการคำขอบริการ HTTP ในรูปแบบต่าง ๆ ซึ่ง มีครบให้เลือกใช้แล้ว ดังเขียนเป็นฟังก์ชันของ Route คือ
Route::get($uri, $callback); // แทนการอ่าน
Route::post($uri, $callback); // แทนการสร้างใหม่
Route::put($uri, $callback); // แทนการสร้างแทนของเดิม
Route::patch($uri, $callback); // แทนการปรับปรุง
Route::delete($uri, $callback); // แทนการลบ
Route::options($uri, $callback); // แทนทางเลือกอื่น ที่เซิร์บเวอร์ให้ทำอะไร
$callback คือฟังก์ชันที่ทำงานกับ $uri หรือเมื่อมีการเรียก URI นี้ก็จะมีฟังก์ชัน $callback ทำงาน นอกจากนี้ในบางครั้งถ้าต้องการให้เส้นทางตอบสนองหลายคำขอ จะต้องใช้ฟังก์ชัน match( ) หรือแม้กระทั้งให้ตอบสนองทุกคำขอบริการใช้ฟังก์ชัน any( ) แทน ดังตัวอย่างต่อไปนี้
Route::match(['get', 'post'], '/', function () {
//
});
Route::any('/', function () {
//
});
ตาราง 1 เมธอดของคอนโทรลเลอร์สำหรับ RESTful : กรณีใช้กับ User
กริยาคำขอ | URL | เมธอดในคอนโทรลเลอร์ | ชื่อ | คำอธิบาย |
GET | users | index() | users.index | แสดงข้อมูล User ทั้งหมด |
GET | users/create | create() | users.create | แสดงฟอร์มสร้าง User |
POST | users | store() | users.store | สร้างข้อมูล User ขึ้นมาใหม่ |
GET | users/{id} | show() | users.show | อ่านข้อมูล User ตาม id |
GET | users/{id}/edit | edit() | users.edit | แทนการแก้ไขข้อมูลตาม id |
PUT/PATCH | users/{id} | update() | users.update | แทนการปรับปรุงข้อมูลตาม id |
DELETE | users/{id} | destroy() | users.destroy | แทนการลบ User ตาม id |
การใช้งาน REESful ตามปกติจะใช้งานผ่าน API ซึ่งสร้างจาก ResourceController ซึ่งจะสร้างฟังก์ชันมาตรฐานตามตาราง 1 แต่เพื่อเป็นทดสอบอย่างง่าย โดยจะใช้กับไฟล์ web.php โดยลบเส้นทางอื่นทั้งหมดก่อน หรือปิดเส้นทางอื่นไว้ก่อนก็ได้ ดังรายการสร้างทางดังนี้
Code 22. routes/web.php
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\UserController;
//RESTfull
Route::get('/users', [UserController::class, 'index']);
Route::get('/users/{id?}', [UserController::class, 'show']);
Route::post('/users', [UserController::class, 'store']);
สร้างสามเส้นทางอย่างง่าย โดยทั้งสามเส้นทางนี้ ทำงานกับฟังก์ชัน index(), show(), และ store() ของ UserController โดยเส้นทางแรก อ่านรายชื่อทั้งหมด เส้นทางที่สองอ่านเฉพาะที่ระบุ id และเส้นทางสุดท้ายบันทึกเพิ่มข้อมูล แต่ในที่นี้จะเพิ่มรายการ user ลงในอาร์เรย์ $users
UserController ใหม่ก็มีฟังก์ชันที่ใช้กับ RESTful ดังเขียนเป็นฟังก์ชันที่จะคืนค่าเป็น JSON โดยไม่ต้องเขียนอะไรเพิ่ม เพราะข้อมูล $users เป็นอาร์เรย์แบบมีคีย์ เมื่อส่งออกโปรแกรมของ Laravelจะแปลงเป็น JSON อัตโนมัติ
Code 23. app/Http/Controllers/UserController.php
<?php
namespace App\Http\Controllers;
use App\Models\User;
class UserController extends Controller{
public function index(){
return User::all();
}
public function show($id){
return User::getById($id);
}
public function store(){
//ใส่โปรแกรมภายหลัง
}
}
ต่อมาก็ทดสอบอ่านข้อมูลในแบบต่าง ๆ ก็พิมพ์ค่า ผ่าน URL :
http://127.0.0.1:8000/users/1
http://127.0.0.1:8000/users
สำหรับการทดสอบคำขอแบบ POST จะทดลองกับฟอร์ม ซึ่งจะอธิบายภายหลังอีกครั้ง เพราะคำขอแบบนี้จะทำให้เกิดเปลี่ยนแปลงข้อมูล จึงต้องมีข้อกำหนดด้านความปลอดภัยเพิ่มเติม
ป้องกัน CSRF
CSRF ย่อมาจาก Cross Side Request Forgery เป็นการสร้างคำขอบริการของ HTTP ที่อาจมุ่งร้ายต่อระบบเว็บ เพราะสามารถส่งคำขอโดยไม่ได้อนุญาตเข้ามาเว็บเซิร์บเวอร์ได้ ยกตัวอย่างเช่น อาจมีเว็บหนึ่งรู้ว่า URL ที่จะส่งคำขอแบบ POST เพื่อบันทึกข้อมูลได้ ดังนั้นหน้าเว็บฟอร์มที่มุ่งร้ายอาจเขียนได้ดังนี้
นอกจากนี้เมื่อมีการทำงานกับ Session หากไม่ใส่ค่า CSRF อาจมีผลกับฟอร์มที่ส่งข้อมูลซ้ำไม่ได้ เพราะอาจเกิดปัญหา SESSION Expired - 419
Code 24. D:\test_csrf.html
<form action="https://your-application.com/user/email" method="POST">
<input type="email" value="malicious-email@example.com">
</form>
<script>
document.forms[0].submit();
</script>
การทดสอบใช้ action="http://127.0.0.1:8000/users" และเก็บไฟล์ไว้ที่ไหนก็ได้ เช่น เก็บไว้ที่ D:\test_csrf.htmlแล้วเรียกไฟล์นี้โดยตรง
จากตัวอย่างนี้ เว็บเซิร์บเวอร์จำเป็นต้องป้องกันทุกการมาของคำขอ โดยเฉพาะทำขอที่จะทำให้เกิดการเปลี่ยนแปลงข้อมูลได้ ได้แก่คำขอแบบ POST, PUT, PATCH หรือ DELETE ซึ่งคำขอบริการเหล่านี้จะตรวจสอบในรหัสเข้าใช้บริการในรูปแบบ รหัส Session ที่เซิร์บเวอร์อนุญาตเท่านั้น
Laravel ใช้การสร้าง CSRF token ส่งมากับฟอร์ม โดยการใส่คำสั่งไดเร็กทีป (Directive) หรือคำสั่งฝั่งของ Laravel ใส่ร่วมไปกับฟอร์ม
<form>
@csrf
</form>
หรือจะใช้ <input> แบบซ่อนการแสดงก็ได้
<form>
<input type="hidden" name="_token" value="{{ csrf_token() }}" />
</form>
ซึ่งถ้าไม่ใส่ รหัสโทเคนนี้ จะไม่สามารถส่งข้อคำขอแบบ POST โดยแจ้งความผิดพลาด ในรหัส 419 ตัวอย่างต่อไปนี้ใช้เป็นฟอร์ม Login โดยที่ส่งคำขอไป เส้นทาง /token
Code 25. resources/views/login.blade.php
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link type="text/css" href="css/bootstrap.min.css" rel="stylesheet">
<script src="js/jquery-3.6.0.min.js"></script>
</head>
<body>
<div style="width:500px; margin:20px auto">
<h2>Login</h2>
<form method="POST" action="/token">
@csrf
<div class="mb-3 row">
<label class="col-sm-2 col-form-label">Email</label>
<div class="col-sm-10">
<input type="email" name="email" class="form-control"
id="inputEmail">
</div>
</div>
<div class="mb-3 row">
<label class="col-sm-2 col-form-label">Password</label>
<div class="col-sm-10">
<input type="password" name="password" class="form-control"
autocomplete="off">
</div>
</div>
<div>
<button type="submit" class="btn btn-primary"
style="float:right">Submit</button>
</div>
</form>
</div>
</body>
</html>
ต่อมาสร้างเส้นทาง ที่ไปยัง login และ token
Code 26. routes/web.php
use Illuminate\Http\Request;
Route::post('/token', function (Request $request) {
$token = $request->session()->token();
$token = csrf_token();
echo "Token:". $token;
echo "<br>";
echo "Email:". $request->input('email') .
"PASSW:". $request->input('password');
});
Route::get('/login', function () {
return view('login');
});
และทดสอบการทำงานของฟอร์ม Login โดยไปยัง URI: /login ก่อน กรองข้อมูลใน <input> ให้ครบแล้วส่งข้อมูลไปเซิร์บเวอร์จะได้พบการแสดงข้อมูลตามที่เขียนเส้นทางของ /token

รูป 1 แสดงผลของ Token
ตามรูปแบบของ RESTful มีการสร้างคำขอในรูปแบบคำกริยาอื่น คือ PUT, PATCH, หรือ DELETE ซึ่งคำขอเหล่านี้ไม่สนับสนุนจาก <form> โดยตรง เพื่อใช้คำเขาเหล่านี้ได้ จึงต้องสร้างตัวแปรในชื่อ _method ดังตัวอย่างต่อไปนี้
<form action="/example" method="POST">
<input type="hidden" name="_method" value="PUT">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
</form>
หรือจะใช้คำสั่งฝั่งของ Laravel @method('PUT')
<form action="/example" method="POST">
@method('PUT')
@csrf
</form>
ก่อนหน้านี้เราได้ค้างการแก้ไข ฟังก์ชัน store สำหรับเก็บข้อมูลใหม่ของ User ตอนนี้เรามาทำให้เสร็จกัน ดูตัวอย่างต่อไปนี้ ฟังก์ชันนี้ ต้องรับข้อมูลจากฟอร์ม จึงต้องใช้ Request ร่วมทำงานด้วย เมื่อเก็บข้อมูลมาครบนำกลับมาสร้างเป็นอาร์เรย์ ในชื่อตัวแปร $user และใส่กลับไปใน Model/User.php
Code 27. app/Http/Controllers/UserController.php
use Illuminate\Http\Request;
public function store(Request $request){
$id = count(User::$users) + 1;
$fname = $request->input('fname');
$lname = $request->input('lname');
$user = array('id'=>$id, 'fname'=>$fname, 'lname'=>$lname);
User::append($user);
return User::$users;
}
ในบางครั้งที่สร้าง Resource Controller (จะมีรายละเอียดในบทที่ว่าด้วย Controller) จะสร้างฟังก์ชันให้หลายตัว เช่น
public function destroy($id){
//ใส่โปรแกรมภายหลัง
}
จะเห็นว่าฟังก์ชันนี้ ได้มีตัวแปรเข้ามาหนึ่งตัวคือ $id แต่ตัวแปรนี้จะรับผ่าน URL เช่น api/books/1 โดยที่ id ก็คือเลข 1 แต่ถ้าต้องการรับผ่านฟอร์ม ต้องปรับปรุงฟังก์ชันให้รับค่าจาก <input> ของฟอร์มได้ ก็ปรับปรุงใหม่ โดยที่ฟังก์ชันนี้ยังรองรับ method DELETE เหมือนเดิม
public function destroy($id, Request $request){
$title = $request→input('title');
//และการดำเนินการอื่น
}
public static function append($user){
array_push(self::$users, $user);
}
สิ่งที่ต้องทำเพิ่มเติมสองรายการคือ
- สร้างฟอร์มเพื่อส่งข้อมูลไปยังเส้นทาง Route::post('/users', [UserController::class, 'store']);
- สร้างเส้นทางเพื่อเรียกฟอร์มกรอกข้อมูล (fname, lname)
ทั้งสองอย่างนี้ ให้ใช้แนวทางการสร้างฟอร์ม และเส้นทาง ก่อนหน้านี้เป็นตัวอย่าง
ใช้งานกับ JavaScript
จากที่ผ่านมาใช้ฟอร์มส่งข้อมูลเป็นยังเซิร์บเวอร์ แต่บางกรณีใช้ภาษา JavaScirpt/jQuery ส่งข้อมูลไปยังเซิร์บเวอร์ ซึ่งก็ทำได้เช่นกัน การส่งตัวแปรของ csrf_token ก็ต้องส่งไปด้วย ซึ่งทำได้หลายวิธี
วิธีแรกคือกำหนดค่า csrf_token ไว้ที่ <meta> ใน <head>
<meta name="csrf-token" content="{{ csrf_token() }}">
และกำหนดค่า ajaxSetup() เป็นส่วนหนึ่งของ JavaScript
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
วิธีที่สอง สร้างตัวแปร _token ไว้กับการข้อมูลที่ส่งไปด้วยกันทีเดียว ซึ่งวิธีนี้จะดูสะดวกกว่า ยกตัวอย่างเช่น ต้องส่งข้อมูล email และ password ไปกับคำสั่ง ajax
Code 29. resources/views/login.blade.php
<script>
function submit() {
var email = $("#inputEmail").val();
var passw = $("#inputPassword").val();
$.ajax({
type: "POST",
url: 'token'',
data: {
'email': email,
'password': passw,
_token: '{{ csrf_token() }}',
},
success: function(data) {
console.log("Success:" + data);
},
error: function(data) {
console.log("Error:" + data);
},
});}
</script>
ข้อมูลที่ส่งไปกับ JavaScript อยู่ในรูปแบบ JSON การรับข้อมูลของเซิร์บเวอร์ จึงอ่านข้อมูลในรูปแบบออบเจ็กต์ที่มีสมาชิกตามชื่อคีย์ของ JSON ตามตัวอย่างนี้ข้อมูล จะแสดงที่ Console.log() ไม่ได้แสดงที่หน้าเว็บ
Code 30. routes/web.php
use Illuminate\Http\Request;
Route::post('/token', function (Request $request) {
echo "Email:". $request->email . "PASSW:". $request->'password';
});
การที่จะทำให้เว็บไปยังหน้าต่อไป จะต้องเขียนในภาษา JavaScript เอง เหมือนตัวอย่างที่ผ่านมา เช่น เพิ่มเติมการเปลี่ยน URI หรือใช้ URL
success: function(data) {
console.log("Success:" + data);
window.location.href = "/users";
}