คอนโทรลเลอร์ (Controller)

การจัดการกับการทำงานภายในระบบ ซึ่งสามารถใช้ เส้นทางแล้วตามด้วยตัวแปรฟังก์ชัน (closures) ซึ่งก็ทำงานได้ แต่ถ้าต้องการแยกการทำออกเป็นกลุ่มงานเฉพาะ หรือแยกออกจากเส้นทาง ก็สามารถทำได้เป็นโดยใช้คอนโทรลเลอร์ คอนโทรลเลอร์เป็นคลาสที่รวมกับความสัมพันธ์ที่เกี่ยวข้องกันให้อยู่ในที่เดียวกัน ทำให้ง่ายแต่การจัดการดูแลระบบเว็บได้ดี โดยค่าปริยายคอนโทรลเลอร์เก็บไว้ที่โฟลเดอร์ app/Http/Controllers

สร้างคอนโทรลเลอร์

การสร้างคอนโทรลเลอร์วิธีที่ง่ายที่สุดคือ สร้างผ่าน CLI ซึ่งจะได้คลาสคอนโทรลเลอร์ การตั้งชื่อคลาสควรใช้อักษรแรกเป็นพิมพ์ใหญ่ตามธรรมเนียมการตั้งชื่อคลาส

CLI 1.


> php artisan make:controller HomeController
        

เมื่อสร้างคอนโทรลเลอร์แล้วจะได้โครงสร้างของคลาส ที่มาพร้อมกับ namespace และคลาสที่จำเป็นต้องใช้ อย่างไรก็ตาม ให้สร้างฟังก์ชัน home( ) เพิ่ม ดังตัวอย่างต่อไปนี้

Code 1. app/Http/Controllers/HomeController.php


<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class HomeController extends Controller{
    public function home(){
        return "<h2>Home</h2>";
    }
}
    

ฟังก์ชัน home( ) ไม่ได้มีอะไรมากนอกจากจะคืนค่า อักษร เพื่อแสดงผลที่หน้าเว็บ

ต่อไปก็ให้สร้างเส้นทาง ให้เพิ่มเติมที่ไปยังคอนโทรลเลอร์นี้ โดยให้การเรียกใช้ฟังก์ชัน home() และต้องไม่ลืมนำเข้า (use) HomeController ด้วย

Code 2. routes/web.php


<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\HomeController;

Route::get('/', function () {
    return view('welcome');
});
Route::get('/home', [HomeController::class, 'home']);
    

เมื่อทดสอบหน้าเว็บ http://localhost:8000/home ก็จะได้ค่าอักษรที่คืนมาจากคอนโทรลเลอร์

คอนโทรลเลอร์อ่านแบบจำลอง

สร้างไฟล์ในชื่อ UserController.php การสร้างจะสร้างแค่ User ก็ได้ แต่ที่เลือกให้มีคำว่า Controller ต่อท้ายด้วยเพื่อจะไม่ได้สับสนในการอ้างอิงที่ได้ประเภทของไฟล์มาด้วย ชื่อไฟล์ก็จะกลายเป็นชื่อคลาสไปด้วย ให้มี namespace ในลำดับของ App\Http\Controllers ซึ่งใช้อักษรตัวใหญ่ หรือจะสร้างผ่าน CLI ก็ได้

CLI 2.


> php artisan make:controller UserController
> php artisan make:model UserModel
    

คลาสของ UserController มีการสืบทอดจาก Controller เป็นมาตรฐาน และสร้างฟังก์ชัน show($id) รับตัวแปรหนึ่งตัว เพื่อใช้ในการสืบค้น รายชื่อผู้ใช้ตามรหัส (id) การคืนค่าของฟังก์ชันนี้จะคืนค่า view(A, B) โดยมีตัวแปรสองตัว ตัวแปรแรกแทน HTML Template ที่ชื่อ user ซึ่งคือไฟล์ user.blade.php นั้นเอง ส่วนตัวแปรที่สองเป็นอาร์เรย์ของข้อมูลในรูปแบบอาร์เรย์แบบมีคีย์ (Associative Array)

code 3. app/Http/Controllers/UserController.php


<?php
namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Models\UserModel;

class UserController extends Controller
{
    public function show($id)
    {
        return view('user', [
            'user' => UserModel::find($id)
        ]);
    }
}
    

จากตัวอย่างนี้ อาร์เรย์แบบมีคีย์ โดยมีคีย์ชื่อ user ที่มาจาก UserModel ซึ่งเป็นคลาสที่มาจาก Model และเรียกใช้ฟังก์ชัน find($id) ที่มีตัวแปร $id หนึ่งตัว ตัวแปรนี้ส่งผ่านเส้นทาง ดังนั้นคอนโทรลเลอร์จึงมีการเรียกใช้จากเส้นทาง ดังให้เพิ่ม เส้นทางใหม่นี้

code 4. routes/web.php


use App\Http\Controllers\UserController;

Route::get('/user/{id}', [UserController::class, 'show']);
    

จากตัวอย่างเส้นทางนี้ ใช้เส้นทาง /user/{id} เป็น URI ที่มีตัวแปร {id} ติดมาด้วย ตัวแปรนี้จะส่งต่ออัตโนมัติเข้าฟังก์ชัน show ของคอนโทรลเลอร์ UserController

สำหรับ UserModel เป็นแบบจำลองที่สมมุติให้ส่งข้อมูล ซึ่งอาจมาจากฐานข้อมูล แต่ในตอนนี้ให้สมมุติให้มาจากอาร์เรย์ดังนี้

code 5. app/Models/UserModel.php


<?php
namespace App\Models;
class UserModel{
    const USERS = array(
        array('id' => 1, 'name' => 'Pol L.', 'tel' => "021234567"),
        array('id' => 2, 'name' => 'Mon L.', 'tel' => "021234568"),
        array('id' => 3, 'name' => 'Tee K.', 'tel' => "021234569"),
    );
    public static function find($id)    {
        $user = null;
        foreach (self::USERS as $u) {
            if ($u['id'] == $id) {
                $user = $u;
                break;
            }
        }
        return $user;
    }
    public static function all(){
        return self::USERS;
    }
}
    

จาก URI ที่ระบุเส้นทาง /user/{id} นั้น ในส่วน user หมายถึงชื่อ view ดังนั้นจะต้องสร้าง view นี้ หากจำกันได้ข้อมูลที่ส่งมาจากคอนโทรลเลอร์ ในชื่อ user ดังนั้นเรานำชื่อนี้ไปแสดงผลได้

code 6. resources/views/user.blade.php


<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>User</title>
        <style>

        </style>
    </head>
    <body>
        <div class="flex-center position-ref full-height">
            <div class="content">
              <h1>User</h1>
              {{$user['id']}} {{$user['name']}} TEL:{{$user['tel']}}
            </div>
        </div>
    </body>
</html>
    

เมื่อทุกอย่างเขียนเตรียมไว้หมดแล้ว ทดสอบให้อ่านรายชื่อเลขที่ 1 ผ่าน URL: http://localhost:8000/user/1

สร้างคอนโทรลเลอร์ปริยาย

ปริยายนี้หมายถึงค่า default หรือค่าที่ไม่ต้องเรียกชื่อฟังก์ชัน ก็จะหมายถึงฟังก์ชันปริยายในคอนโทรลเลอร์ ซึ่งฟังก์ชันนี้คือ __invoke( )

public function __invoke(){ }

สมมุติให้ฟังก์ชันนี้ อ่านค่าทั้งหมดของรายการ user การสร้างฟังก์ชันนี้ เท่าที่ทดลองทำ สามารถสร้างไฟล์ขึ้นเองได้ หรือใช้การสร้างผ่าน CLI ก็ได้

CLI 3.


> php artisan make:controller UsersController --invokable
    

ผลจากใช้ CLI นี้จะสร้างไฟล์ UsersController.php มาให้ พร้อมกับฟังก์ชัน __invoke( ) มาให้ เราเพียงเติมเต็มการทำงานให้ดึงข้อมูลมาแสดงใน view ชื่อ users

code 7. app/Http/Controllers/UsersController.php


<?php
namespace App\Http\Controllers;

use App\Models\UserModel;
use Illuminate\Http\Request;

class UsersController extends Controller
{
    public function __invoke(Request $request)
    {
        return view('users', [
            'users'=> UserModel::all()
        ]);
    }
}
    

ฟังก์ชัน __invoke(Request $request) มีใส่ตัวแปรเข้ามาด้วย แต่ในตัวอย่างไม่ได้นำไปใช้ประโยชน์อะไร ณ ขณะนี้

ต่อมาเส้นทางไปยัง /users โดยไม่จำเป็นต้องระบุชื่อเมธอดอีกต่อไป แต่มีข้อแตกต่างตรงที่ไม่ได้อยู่ในเครื่องหมาย [ ] อีกต่อไป

code 8. routes/web.php


use App\Http\Controllers\UsersController;

Route::get('/users', UsersController::class);
    

ที่เหลือคือหน้า view:users ที่แสดงรายการทั้งหมดของผู้ใช้ (users) ที่ส่งข้อมูลจากคอนโทรลเลอร์ ตัวอย่างแสดงผลต่อไปนี้แสดงรายการผู้ใช้ทั้งหมดในรูปตาราง (ใช้ Bootstrap ประกอบการจัดรูปตาราง ซึ่งไม่ใช้รูปตาม bootstrap ก็ได้) และทดสอบผ่าน URL: http://localhost:8000/users

code 9. resources/views/users.blade.php


<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, 
              initial-scale=1">
        <title>User</title>
        <link rel="stylesheet" href="assets/css/bootstrap.min.css">
    </head>
    <body>
        <div class="flex-center position-ref full-height">
            <div class="content">
              <h1>User</h1>
              <table class='table'>
                <thead>
                    <tr><th>Id</th><th>Name</th><th>Tel</th></tr>
                </thead>
                <tbody>
                @php
                foreach($users as $user){
                    echo("<tr>");
                    echo("<td>". $user['id']. "</td>");
                    echo("<td>". $user['name']."</td>");
                    echo("<td>". $user['tel'] ."</td>");                
                    echo("</tr>");
                }
                @endphp
                </tbody>
            </table>
            </div>
        </div>
    </body>
</html>
    

รีซอร์สคอนโทรลเลอร์ (Resource Controller)

ในแบบจำลองข้อมูลของ Eloquent ทำตัวเสมือนเป็น รีซอร์ส (resource) ซึ่งหมายความว่า จะมีต้นแบบของฟังก์ชันอยู่ในคอนโทรลเลอร์อยู่แล้ว ยกตัวอย่างเช่น เมื่อเราต้องการสร้าง รีซอร์สคอนโทรลเลอร์ ProductController ด้วยคำสั่ง: CLI 4

CLI 4.


> php artisan make:controller ProductController --resource
    

ด้วย CLI นี้ของลาราเวล จะทำให้คลาส ProductController ที่มีฟังก์ชันมาตรฐานตามการดำเนินงานกับรีซอร์ส และฟังก์ชันเหล่านี้ทำงานได้ดีกับการจัดการฐานข้อมูลที่อยู่ในกรอบงาน Eluquent

สำหรับการเส้นทางตาม URI ที่ใช้ในไฟล์ web.php ก็ใช้เพียงการลงทะเบียน

code 10. routes/web.php


use App\Http\Controllers\ProductController;

Route::resource('products', ProductController::class); 
    

ด้วยการลงทะเบียนเพียงแค่นี้ ก็ถือว่าได้ลงทะเบียนเส้นทางมาตรฐานไปด้วย ดูเส้นทางมาตรฐานตาม เช่น ถ้าผู้ใช้ไปที่ URL: /products จะหมายถึง การส่งคำขอแบบ GET และจะเรียกฟังก์ชัน index( ) ในคลาสคอนโทรลเลอร์ให้ทำงาน หรือถ้าผู้ใช้ส่งคำขอแบบ POST ด้วย URL เดิมจะเป็นการเรียกฟังก์ชัน store( ) ให้ทำงาน

ตาราง 1. ฟังก์ชันมาตรฐานของรีซอร์ส
Verb URI Action Route Name
GET /products index products.index
GET /products/create create products.create
POST /products store products.store
GET /products/{product} show products.show
GET /products/{product}/edit edit products.edit
PUT/PATCH /products/{product} update products.update
DELETE /products/{product} destroy products.destroy

เพื่อทดสอบการใช้งานเส้นทางตาม รีซอร์สคอนโทรลเลอร์ ว่าใช้งานได้จริงหรือไม่ ให้สมมุติการคืนค่าของฟังก์ชันที่รองรับคำขอแบบ GET บางฟังก์ชัน

code 11. app/Http/Controllers/UsersController.php


public function index(){
     return "<h2>Product index</h2>";
}
public function create(){
     return "<h2>Product create</h2>";
}
public function show($id){
     return "<h2>Product show ". $id."</h2>";
}
    

จากสามฟังก์ชันนี้ใช้ผ่านคำขอ GET ทั้งหมด เราจะทดสอบคำขอเรียงตามลำดับฟังก์ชัน ซึ่งจะคืนค่าข้อมูลที่เขียนตามแต่ละฟังก์ชัน

แน่นอนว่าได้ผลที่แนะตาม ตาราง 1 ซึ่งนับว่าประหยัดเวลาการเขียนโปรแกรมได้มาก นับเป็นความฉานฉลาดของลาราเวล แล้วจะได้พบการใช้แน่นอนกับรูปแบบคอนโทรลเลอร์นี้ที่ใช้งานคู่กับฐานข้อมูล

ยังมีรีซอร์สอีกประเภทที่เรียกว่า apiResource ซึ่งก็ทำงานได้เหมือนรีซอร์สคอนโทรลเลอร์ เพราะถูกสร้างในแบบเดียวกับ รีซอร์สคอนโทรลเลอร์ เพียงแต่มีสองฟังก์ชันไม่ถูกสร้างมาด้วย (edit( ) กับ create( )) อย่างไรก็ตามก็เขียนเพิ่มเติมให้เหมือนรีซอร์สคอนโทรลเลอร์ได้ การสร้าง apiResource เพื่อต้องการสร้าง API ขึ้นเองโดยไม่จำเป็นต้องทำตามแบบ รีซอร์สที่ทำงานคู่กับฐานข้อมูลอย่าง Eloquent

สมมติว่าต้องการสร้าง สร้าง apiResource ขึ้นเองผ่านการใช้งานคอนโทรลเลอร์ในชื่อ BookController

CLI 5.


> php artisan make:controller BookController --api
    

สำหรับการลงทะเบียนเส้นทาง จะลงทะเบียนผ่านไฟล์ web.php ก็ได้ ซึ่งจะทำงานเหมือน รีซอร์สคอนโทรลเลอร์ หรือจะลงทะเบียนผ่านไฟล์ api.php

เพื่อให้เกิดความแตกต่างจากตัวอย่างที่ผ่านมา ให้ลงทะเบียนผ่านไฟล์ api.php และใช้ฟังก์ชัน apiResourece() ในการลงทะเบียน โดยการเขียนเพิ่มเติมตามตัวอย่างนี้

code 12. routes/api.php


use App\Http\Controllers\BookController;

Route::apiResource('books', BookController::class); 
    

ในการทดสอบใช้การทดสอบเหมือนกับตัวอย่างที่ผ่านมา แต่ที่ต่างไปจากเดิม คือการใช้ URL ที่เพิ่มคำว่า “api” ดังตัวอย่าง URL: http://localhost:8000/api/books/1 ด้วย URL ใหม่นี้ก็จะเป็นการเรียกฟังก์ชัน show($id) ของ BookController

Middleware ในคอนโทรลเลอร์

เมื่อต้องการใช้สื่อตัวกลางกับคอนโทรลเลอร์ สามารถเชื่อมต่อท้ายกับเส้นทางได้เลย เช่น ต้องการให้ UsersController ต้องผ่านการตรวจสอบสิทธิ์ก่อน

ตัวอย่างเช่น ให้ UsersController มีการใช้ให้ middleware(‘auth’) ทำงานก่อน สำหรับสื่อตัวกลางนี้มีให้มาแล้วในไฟล์ชื่อ Authenticate.php และลงทะเบียนแล้วที่ไฟล์ Kernel.php ในชื่อ auth ดังนั้นการทำให้คอนโทรลเลอร์เรียกใช้สื่อตัวกลางก่อนจะต้องลงทะเบียนเส้นทาง

code 13. routes/web.php


Route::get('/users', 'UsersController')->middleware('auth');

//และลงทะเบียนเส้นทาง login
Route::get('/login', function(){ 
    return view('login');
})->name('login');
    

และจากการตรวจสอบไฟล์ Authenticate.php มีฟังก์ชัน redirectTo( ) ที่ใช้ตรวจสอบการตอบสนองในรูปแบบ JSON หรือไม่ ถ้าไม่ใช่จะส่งไปยังเส้นทาง login

สำหรับเส้นทาง login มีการตั้งชื่อเส้นทางไว้ด้วยตามชื่อเส้นทาง และจะส่งไปเรียกหน้า view: login.blade.php เราอาจทดลองสร้างรอไว้เพื่อการทดสอบ

ทดสอบการใช้งานกับ URL: http://localhost:8000/users จะพบว่าจะได้ไปที่ http://localhost:8000/login แทน