สร้าง MCP Server ของคุณเองด้วย Laravel

โดย CyberMAN

TOPIC #7 · Laravel × MCP

สร้าง MCP Server ของคุณเองด้วย Laravel

เปิดประตูสู่ยุค AI-first Development — ให้ Laravel เป็น backend ที่ AI Agent พูดคุยได้โดยตรง

ในช่วงปี 2025–2026 คำว่า MCP (Model Context Protocol) กลายเป็นหัวข้อร้อนแรงในวงการ AI Development เพราะมันเป็นมาตรฐานกลางที่ทำให้ AI Agent เช่น Claude, Cursor หรือ VS Code Copilot สามารถ "เรียกใช้" เครื่องมือของเราได้โดยตรง เหมือน REST API แต่ออกแบบมาเพื่อ LLM โดยเฉพาะ

แล้ว Laravel เกี่ยวอะไรด้วย? คำตอบคือ Laravel มีทุกอย่างที่ต้องการสำหรับสร้าง MCP Server ตั้งแต่ Routing ที่ clean, Middleware, Eloquent ORM ไปจนถึง JSON response ที่สวยงาม — บทความนี้จะพาทุกคนสร้าง MCP Server ตั้งแต่ศูนย์ แบบที่เข้าใจได้แม้ยังเป็นมือใหม่

💡 MCP คืออะไรในสองประโยค?
MCP (Model Context Protocol) คือโปรโตคอลเปิดที่กำหนดวิธีที่ AI Model จะ "เรียกฟังก์ชัน" บน server ของเรา — แทนที่จะต้องตั้ง REST API เอง MCP มี spec กลางที่ AI รู้จักอยู่แล้ว เราแค่ implement ตาม spec นั้น

🚀 ทำความรู้จัก MCP ก่อนลงมือ

MCP ทำงานด้วยโมเดลง่าย ๆ — AI Agent จะส่ง JSON-RPC request มาถามว่า "มี tool อะไรบ้าง?" แล้วเราตอบกลับด้วยรายการ tools พร้อม schema ของ parameter ต่าง ๆ จากนั้นเมื่อ AI ต้องการใช้ tool ก็จะส่ง request อีกครั้งพร้อม arguments เราก็ประมวลผลแล้วตอบกลับด้วยผลลัพธ์

โครงสร้างหลักของ MCP Server มี 3 endpoint ที่ต้องรู้จัก:

Endpoint / Methodหน้าที่ตัวอย่าง Response
initializeAI ทักทาย ขอข้อมูล serverชื่อ server, version, capabilities
tools/listส่งรายการ tool ทั้งหมดArray of tool schemas (JSON Schema)
tools/callรัน tool ที่ AI เลือกผลลัพธ์จาก tool นั้น
resources/listประกาศ data sourceURI, description ของ resource
prompts/listเสนอ prompt templateTemplate สำเร็จรูปสำหรับ AI

⚙️ ติดตั้ง Laravel และสร้างโปรเจกต์ใหม่

เริ่มต้นด้วยการสร้างโปรเจกต์ Laravel ใหม่ผ่าน Composer หรือ Laravel Installer ได้เลย:

terminal
# วิธีที่ 1: ใช้ Laravel Installer
laravel new my-mcp-server

# วิธีที่ 2: ใช้ Composer โดยตรง
composer create-project laravel/laravel my-mcp-server

# เข้าไปในโฟลเดอร์แล้วรัน server
cd my-mcp-server
php artisan serve

เมื่อ server ขึ้นแล้ว เราจะสร้างโครงสร้างไฟล์สำหรับ MCP Server ของเรา โดยมีไฟล์หลัก 3 ไฟล์ที่ต้องสร้าง:

terminal
php artisan make:controller McpController
php artisan make:service McpToolService  # สร้างเอง หรือวาง class ใน app/Services/

🛣️ กำหนด Routes สำหรับ MCP

MCP ใช้ JSON-RPC 2.0 ผ่าน HTTP POST ที่ endpoint เดียว ซึ่ง Laravel จัดการได้ง่ายมาก เปิดไฟล์ routes/api.php แล้วเพิ่ม:

routes/api.php
use App\Http\Controllers\McpController;
use Illuminate\Support\Facades\Route;

// MCP Server endpoint — รับ JSON-RPC ทุก method ที่นี่
Route::post('/mcp', [McpController::class, 'handle']);

// SSE endpoint สำหรับ streaming (optional)
Route::get('/mcp/sse', [McpController::class, 'sse']);

🧩 สร้าง McpController — หัวใจของ Server

Controller นี้คือตัวกลางที่รับ JSON-RPC request จาก AI แล้ว dispatch ไปยัง handler ที่ถูกต้อง:

app/Http/Controllers/McpController.php
<?php

namespace App\Http\Controllers;

use App\Services\McpToolService;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;

class McpController extends Controller
{
    public function __construct(
        private McpToolService $toolService
    ) {}

    public function handle(Request $request): JsonResponse
    {
        $method  = $request->input('method');
        $params  = $request->input('params', []);
        $id      = $request->input('id');

        $result = match ($method) {
            'initialize'  => $this->initialize(),
            'tools/list'  => $this->$this->listTools(),
            'tools/call'  => $this->callTool($params),
            default       => ['error' => 'Method not found'],
        };

        return response()->json([
            'jsonrpc' => '2.0',
            'id'      => $id,
            'result'  => $result,
        ]);
    }

    private function initialize(): array
    {
        return [
            'protocolVersion' => '2024-11-05',
            'serverInfo'      => [
                'name'    => 'my-laravel-mcp',
                'version' => '1.0.0',
            ],
            'capabilities'    => ['tools' => (object) []],
        ];
    }

    private function listTools(): array
    {
        return ['tools' => $this->toolService->getToolSchemas()];
    }

    private function callTool(array $params): array
    {
        $name = $params['name'] ?? '';
        $args = $params['arguments'] ?? [];

        $output = $this->toolService->execute($name, $args);

        return [
            'content' => [['type' => 'text', 'text' => $output]],
        ];
    }
}

🔧 สร้าง Tool จริง — ตัวอย่าง: ค้นหาสินค้าจาก Database

ถึงเวลาสร้าง Tool ที่ AI จะเรียกใช้ได้จริง ตัวอย่างนี้เราสร้าง tool ชื่อ search_products ที่ให้ AI ค้นหาสินค้าจากฐานข้อมูล:

app/Services/McpToolService.php
<?php

namespace App\Services;

use App\Models\Product;

class McpToolService
{
    /**
     * ประกาศ schema ของ tools ทั้งหมดที่ server นี้มี
     * AI จะนำไปเข้าใจว่าแต่ละ tool รับ argument อะไร
     */
    public function getToolSchemas(): array
    {
        return [
            [
                'name'        => 'search_products',
                'description' => 'ค้นหาสินค้าจากชื่อหรือหมวดหมู่',
                'inputSchema' => [
                    'type'       => 'object',
                    'properties' => [
                        'keyword' => [
                            'type'        => 'string',
                            'description' => 'คำค้นหา',
                        ],
                        'limit' => [
                            'type'        => 'integer',
                            'description' => 'จำนวนผลลัพธ์สูงสุด',
                            'default'     => 10,
                        ],
                    ],
                    'required' => ['keyword'],
                ],
            ],
            [
                'name'        => 'get_order_status',
                'description' => 'ตรวจสถานะ order จาก order ID',
                'inputSchema' => [
                    'type'       => 'object',
                    'properties' => [
                        'order_id' => [
                            'type' => 'integer',
                        ],
                    ],
                    'required' => ['order_id'],
                ],
            ],
        ];
    }

    /**
     * Execute tool ตาม name ที่ AI ส่งมา
     */
    public function execute(string $name, array $args): string
    {
        return match ($name) {
            'search_products'  => $this->searchProducts($args),
            'get_order_status' => $this->getOrderStatus($args),
            default            => 'ไม่พบ tool ที่ระบุ',
        };
    }

    private function searchProducts(array $args): string
    {
        $keyword = $args['keyword'];
        $limit   = $args['limit'] ?? 10;

        // ใช้ Eloquent ค้นหาสินค้า
        $products = Product::where('name', 'like', "%{$keyword}%")
            ->orWhere('category', 'like', "%{$keyword}%")
            ->limit($limit)
            ->get(['id', 'name', 'price', 'category']);

        if ($products->isEmpty()) {
            return "ไม่พบสินค้าที่ตรงกับ: {$keyword}";
        }

        // ส่งกลับเป็น JSON string ให้ AI อ่านได้
        return json_encode($products->toArray(), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
    }

    private function getOrderStatus(array $args): string
    {
        $orderId = (int) ($args['order_id'] ?? 0);
        // ... query จาก orders table ...
        return "Order #{$orderId}: กำลังจัดส่ง";
    }
}

📋 เปรียบเทียบ: MCP Server vs REST API

หลายคนอาจสงสัยว่า MCP Server กับ REST API ต่างกันยังไง ตารางนี้สรุปให้ชัด:

ประเด็นREST APIMCP Server
เป้าหมายหลักFrontend / Mobile / Third-partyAI Agent โดยเฉพาะ
ProtocolHTTP + JSON (custom schema)JSON-RPC 2.0 ตาม MCP Spec
AI รู้จักต้อง describe ใน system promptAI อ่าน tool schema ได้เอง
Documentationต้องเขียน Swagger / OpenAPISchema คือ doc ในตัว
ความซับซ้อนยืดหยุ่นกว่า มาก endpointSimple — endpoint เดียว
ใช้กับ Laravel✅ ทำได้สะดวก✅ ทำได้เช่นกัน (บทความนี้!)

🔒 เพิ่ม Middleware รักษาความปลอดภัย

MCP Server ที่ Production-ready ต้องมี authentication ด้วย วิธีง่ายที่สุดคือใช้ Bearer Token ผ่าน Laravel Middleware:

app/Http/Middleware/McpAuthMiddleware.php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class McpAuthMiddleware
{
    public function handle(Request $request, Closure $next)
    {
        $token = $request->bearerToken();

        if ($token !== config('mcp.secret_token')) {
            return response()->json([
                'jsonrpc' => '2.0',
                'error'   => [
                    'code'    => -32001,
                    'message' => 'Unauthorized',
                ],
            ], 401);
        }

        return $next($request);
    }
}

// ใน routes/api.php — ครอบ middleware
Route::middleware('mcp.auth')->post('/mcp', [McpController::class, 'handle']);

🧪 ทดสอบ MCP Server ด้วย curl

ก่อนต่อกับ AI จริง ทดสอบด้วย curl ได้เลย — ลอง call tools/list ดูก่อน:

terminal — ทดสอบ tools/list
curl -X POST http://localhost:8000/api/mcp \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-secret-token" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/list",
    "params": {}
  }'

ถ้า response ส่ง array ของ tools กลับมา แสดงว่า MCP Server ทำงานถูกต้องแล้ว จากนั้นลอง call tool จริง:

terminal — ทดสอบ tools/call
curl -X POST http://localhost:8000/api/mcp \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-secret-token" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": {
      "name": "search_products",
      "arguments": { "keyword": "กาแฟ", "limit": 5 }
    }
  }'
🔗 ต่อกับ Claude Desktop
เมื่อ server พร้อมแล้ว เพิ่มใน claude_desktop_config.json ได้เลย:
"url": "http://localhost:8000/api/mcp" พร้อม header Authorization — Claude จะเห็น tools ของเราทันที!

🎯 สรุป

การสร้าง MCP Server ด้วย Laravel ไม่ได้ซับซ้อนอย่างที่คิด เพราะ Laravel มี building block ครบครัน — Routing, Controller, Service class, Middleware และ Eloquent — ทำให้เราโฟกัสที่ logic ของ tool ได้เลย โดยไม่ต้องเสียเวลาสร้าง infrastructure ใหม่

สิ่งที่เราได้เรียนรู้ในบทความนี้:

✅ MCP คืออะไรและทำงานอย่างไรในแบบ JSON-RPC 2.0
✅ โครงสร้าง Laravel สำหรับ MCP Server (Controller + Service)
✅ การประกาศ Tool Schema ให้ AI เข้าใจ
✅ การรักษาความปลอดภัยด้วย Bearer Token Middleware
✅ วิธีทดสอบก่อน deploy จริง

ก้าวต่อไปคือเพิ่ม tools ที่จำเป็นสำหรับธุรกิจของคุณ เช่น ค้นหาข้อมูล, อัปเดตสถานะ, หรือดึงรายงาน — แล้ว AI Agent จะกลายเป็น "พนักงาน" ที่ใช้งาน Laravel app ของคุณได้จริง

🚀 พร้อมต่อยอดเพิ่มเติมไหม?

ติดตาม PHP Code Mania เพื่อรับบทความ Laravel, CodeIgniter และ AI Development ในภาษาไทย — อัปเดตสม่ำเสมอสำหรับนักพัฒนา PHP ทุกระดับ

📖 ติดตาม PHP Code Mania





PHP CI MANIA - PHP Code Generator 

โปรแกรมช่วยสร้างโค้ด "ลดเวลาการเขียนโปรแกรม"
ราคาสุดคุ้ม  
http://www.phpcodemania.com

คู่มือเปลี่ยนเว็บเป็น Real-Time UX ไม่ต้องง้อ WebSockets ด้วย Livewire Polling, Events และ Lazy Loading

โดย CyberMAN


สวัสดีครับเพื่อนๆ นักพัฒนาเว็บทุกคน! ยินดีต้อนรับสู่ยุคที่ผู้ใช้งานคาดหวังความลื่นไหลระดับสิบ แอปพลิเคชันยุคนี้ถ้าต้องคอยกด F5 เพื่อรีเฟรชหน้าจอเพื่อดูข้อมูลใหม่ๆ คงโดนผู้ใช้บ่นแน่นอน ไม่ว่าจะเป็นระบบแสดงผลยอดขาย Real-time, ระบบแจ้งเตือน (Notifications), หรือแดชบอร์ดสรุปผลวิเคราะห์ข้อมูล (Analytics Dashboard)

เมื่อพูดถึง "Real-Time Web App" ภาพในหัวของใครหลายคนคงนึกถึงระบบที่ขับเคลื่อนด้วย WebSockets หรือเครื่องมือยอดฮิตอย่าง Laravel Echo ร่วมกับ Pusher / Soketi แต่สำหรับเหล่านักพัฒนามือใหม่ หรือทีมที่กำลังเริ่มทำโปรเจกต์ด้วย Laravel และ CodeIgniter 4 (CI4) การตั้งค่าเซิร์ฟเวอร์สำหรับ WebSocket, การจัดการสิทธิ์, และการควบคุม Memory Leak อาจเป็นเรื่องที่ยุ่งยาก ซับซ้อน และเกินความจำเป็นสำหรับแอปพลิเคชันที่เพิ่งเริ่มต้น

วันนี้ผมจะมาแนะนำ "อาวุธลับ" ที่จะช่วยให้แอปพลิเคชันของคุณเปลี่ยนเป็นระบบ Real-time ได้ในพริบตาเดียวโดยไม่ต้องใช้ WebSockets แม้แต่บรรทัดเดียว! ผ่านฟีเจอร์เด่นของ Laravel Livewire v4 ได้แก่ Polling, Events และ Lazy Loading ที่ใช้งานง่ายจนคุณต้องร้องว้าว!


ทำความเข้าใจก่อนเริ่ม: Polling vs WebSockets

ก่อนที่เราจะดิ่งลึกไปที่โค้ด เรามาเข้าใจความแตกต่างระหว่างสองแนวคิดนี้ก่อน เพื่อให้คุณเลือกใช้งานได้อย่างเหมาะสมกับสเกลของระบบ

  • WebSockets: เป็นการเปิดท่อการเชื่อมต่อแบบถาวร (Persistent Connection) ระหว่างเบราว์เซอร์กับเซิร์ฟเวอร์ ข้อดีคือเซิร์ฟเวอร์สามารถดันข้อมูล (Push) หาคลายเอนต์ได้ทันทีเมื่อมีข้อมูลใหม่เกิดขึ้น แต่ข้อเสียคือต้องการแรมเซิร์ฟเวอร์สูง และสถาปัตยกรรมค่อนข้างซับซ้อน
  • HTTP Polling: คือเทคนิคการสั่งให้เบราว์เซอร์ส่งคำขอ (Request) ไปถามเซิร์ฟเวอร์เป็นระยะๆ (เช่น ทุกๆ 5 วินาที) ว่า "มีข้อมูลใหม่ไหม?" ถ้ามีก็นำมาแสดงผล แม้จะดูเป็นวิธีดั้งเดิม แต่ด้วยเทคโนโลยีของ Livewire v4 ทำให้การทำ Polling มีประสิทธิภาพสูงมาก เขียนง่าย และลดภาระของเซิร์ฟเวอร์ได้อย่างชาญฉลาด

ตารางเปรียบเทียบต่อไปนี้จะช่วยให้คุณเห็นภาพชัดเจนขึ้นว่าเมื่อไหร่ควรใช้เครื่องมือตัวไหนสำหรับโปรเจกต์ของคุณ:

คุณสมบัติ Livewire Polling (HTTP) WebSockets (Laravel Echo)
ความยากในการตั้งค่า ง่ายมาก (เขียนแค่คำสั่งบน Blade แท็กเดียว) ปานกลาง - สูง (ต้องตั้งค่า Server เพิ่มเติม)
การกินทรัพยากร ต่ำ (Livewire v4 มีระบบ Throttling อัจฉริยะ) ปานกลาง (ต้องคงสถานะการเชื่อมต่อตลอดเวลา)
ความเร็วในการอัปเดต ใกล้เคียง Real-time (ขึ้นอยู่กับ Interval) Real-time ทันทีในระดับมิลลิวินาที
เหมาะสำหรับระบบ Dashboard, สถิติยอดขาย, ดึงข้อมูลจาก APIs แชทสด (Chat App), ระบบประมูล, หุ้นคริปโต

ฟีเจอร์ที่ 1: ตื่นตาตื่นใจกับ wire:poll ของ Livewire

ใน Livewire เวอร์ชันล่าสุด การทำ Polling ทำได้ง่ายมาก เพียงแค่เพิ่มแอตทริบิวต์ wire:poll เข้าไปในโครงสร้าง HTML ของคุณ คอมโพเนนต์นั้นก็จะรีเฟรชตัวเองโดยอัตโนมัติ

ตัวอย่างโค้ด: ระบบนับยอดผู้ติดตาม (Subscriber Counter)

สมมติว่าคุณกำลังทำหน้าแดชบอร์ดส่วนตัว และต้องการให้ตัวเลขผู้ติดตามอัปเดตตลอดเวลาโดยไม่ต้องกดรีเฟรชหน้าเว็บ

SubscriberCounter.php (Backend) PHP
<?php

namespace App\Livewire;

use Livewire\Component;
use App\Models\User;

class SubscriberCounter extends Component
{
    public function render()
    {
        return view('livewire.subscriber-counter', [
            'count' => User::where('is_subscribed', true)->count(),
        ]);
    }
}
subscriber-counter.blade.php (Frontend) Blade
<div wire:poll>
    <div class="card">
        <h3>ยอดผู้ติดตามปัจจุบัน</h3>
        <p>{{ $count }} คน</p>
    </div>
</div>

เพียงแค่คุณใส่คำว่า wire:poll ลงไปในแท็กครอบด้านบนสุด ตัว Livewire จะแอบส่งสัญญาณ Request ไปอัปเดตข้อมูลให้คุณทุกๆ 2.5 วินาทีโดยอัตโนมัติ! สะดวกสุดๆ ไปเลยใช่ไหมครับ?

⚠️ ควบคุม Performance ด้วย Modifiers ของ Livewire v4:

- wire:poll.15s: ปรับเวลาเป็นทุกๆ 15 วินาที เพื่อไม่ให้เซิร์ฟเวอร์ทำงานหนักเกินไป
- wire:poll.visible: ระบบจะทำ Polling เฉพาะเมื่อผู้ใช้งานสกรอลล์หน้าจอมาเห็นคอมโพเนนต์นี้เท่านั้น!
- Background Tab Throttling: เมื่อผู้ใช้เปิดแท็บอื่นค้างไว้ Livewire จะลดการยิงคำขอลง 95% ทันทีโดยอัตโนมัติ


ฟีเจอร์ที่ 2: ผสานพลังไอเดียด้วย Livewire Events

บางครั้งเราไม่จำเป็นต้องตั้งเวลา Polling ตลอดเวลา แต่เราต้องการให้ Component หนึ่ง สั่งให้อีก Component หนึ่งอัปเดตตัวตามเมื่อเกิดการกระทำบางอย่าง (Event-driven Architecture)

ProductComponent.php (ฝั่งผู้ส่ง) PHP
public function addToCart($productId)
{
    // โค้ดเพิ่มสินค้าลงระบบตะกร้า
    
    // ส่งสัญญาณ Event ออกไปทั่วทั้งหน้าเว็บ
    $this->dispatch('cart-updated');
}
CartIcon.php (ฝั่งผู้รับ) PHP
use Livewire\Attributes\On;

class CartIcon extends Component
{
    #[On('cart-updated')]
    public function refreshCartCount()
    {
        // เมธอดนี้จะทำงานและดึงข้อมูลยอดใหม่ทันทีที่ได้รับสัญญาณ
    }
}

ฟีเจอร์ที่ 3: โหลดหน้าเว็บเร็วปานสายฟ้าด้วย Lazy Loading

เมื่อเรานำข้อมูลภายนอก (Third-party APIs) เช่น การดึงราคาน้ำมัน หรือวิเคราะห์ SQL ซับซ้อน หากเขียนแอปพลิเคชันแบบปกติ หน้าเว็บจะหมุนค้างและแสดงหน้ากระดาษสีขาวจนกว่าข้อมูลจะเสร็จ แต่ Livewire มอบฟีเจอร์ Lazy Loading ที่จะโหลดหน้าเว็บโครงสร้างหลักขึ้นมาก่อน แล้วค่อยโหลด Component ตามมาทีหลัง

WeatherWidget.php (Lazy Component) PHP
namespace App\Livewire;

// เปิดใช้งาน Lazy Loading ด้วย Attribute
#[\Livewire\Attributes\Lazy]
class WeatherWidget extends Component
{
    public function render()
    {
        $response = Http::get('https://api.weatherapi.com/v1/current.json');
        return view('livewire.weather-widget');
    }

    // แสดง Skeleton Screen ระหว่างรอข้อมูล
    public function placeholder()
    {
        return '<div class="loading">กำลังโหลดข้อมูลสภาพอากาศ...</div>';
    }
}

แล้วถ้าหากคุณเป็นนักพัฒนา CodeIgniter 4 ล่ะ?

ถึงแม้ว่า Livewire จะเป็นของ Laravel แต่สำหรับสาย CodeIgniter 4 (CI4) คุณก็สามารถสร้าง UX แบบ Real-time ที่ดีเยี่ยมนี้ได้เช่นเดียวกัน โดยผสมผสานสถาปัตยกรรม AJAX Polling ร่วมกับฟังก์ชันพื้นฐานของ CI4 ดังนี้ครับ:

app/Controllers/Dashboard.php PHP
public function getLatestSales()
{
    $model = new OrderModel();
    $totalSales = $model->sum('amount');

    return $this->response->setJSON([
        'status' => 'success',
        'total_sales' => number_format($totalSales, 2)
    ]);
}
dashboard_view.php (Frontend AJAX) JavaScript
function updateSales() {
    fetch('<?= base_url(\'dashboard/getLatestSales\') ?>')
        .then(response => response.json())
        .then(data => {
            if (data.status === 'success') {
                document.getElementById('sales-amount').innerText = '฿' + data.total_sales;
            }
        });
}
// สั่งให้ระบบส่งคำขอซ้ำทุกๆ 5 วินาที
setInterval(updateSales, 5000);

สรุป: เทคนิคไหนที่ใช่สำหรับคุณ?

การทำให้เว็บแอปพลิเคชันตอบสนองแบบเรียลไทม์ไม่จำเป็นต้องลงเอยด้วยสถาปัตยกรรม WebSockets ที่ซับซ้อนเสมอไป การเลือกใช้ HTTP Polling ที่มีการจัดการอัจฉริยะอย่าง Livewire v4 หรือโครงสร้าง AJAX บน CI4 ก็สามารถส่งมอบประสบการณ์ผู้ใช้ที่ดีเยี่ยม ลื่นไหล และประหยัดทรัพยากรฝั่งเซิร์ฟเวอร์ได้อย่างน่าอัศจรรย์ใจครับ

🚀 มาร่วมสนุกและแชร์ไอเดียกัน!

ตอนนี้แอปพลิเคชันของคุณใช้ระบบอัปเดตข้อมูลแบบไหนกันอยู่ครับ? ประสบปัญหาเว็บหน่วงหรือเซิร์ฟเวอร์ล่มกันบ้างไหม? คอมเมนต์พูดคุยแลกเปลี่ยนประสบการณ์ หรือสอบถามวิธีการเขียนโค้ดเพิ่มเติมด้านล่างนี้ได้เลยนะครับ! และอย่าลืมกดแชร์บทความนี้ให้กับเพื่อนๆ นักพัฒนาสาย PHP ด้วยล่ะ!




PHP CI MANIA - PHP Code Generator 

โปรแกรมช่วยสร้างโค้ด "ลดเวลาการเขียนโปรแกรม"
ราคาสุดคุ้ม  
http://www.phpcodemania.com

Scheduler List: Web Dashboard สำหรับจัดการ Laravel Scheduled Tasks

โดย CyberMAN



Laravel · Scheduled Tasks · Dashboard

Scheduler List: Web Dashboard
สำหรับจัดการ Laravel Scheduled Tasks

สร้าง Dashboard ดู Cron Jobs ทั้งหมดของโปรเจกต์ Laravel ผ่านหน้าเว็บ แบบไม่ต้อง SSH เข้า Server ทุกครั้ง

LaravelScheduled TasksCron JobPHPWeb Dashboard



ปัญหาคลาสสิกของ Cron Jobs

ใครที่เคยทำงาน Backend มาสักระยะ คงเคยเจอคำถามนี้จากทีมหรือลูกค้า: "Cron Job มันรันอยู่ไหม? ทำอะไรไปบ้าง?" — แล้วคุณก็ต้อง SSH เข้า Server ไปดู log หรือพิมพ์คำสั่ง crontab -l ซึ่งไม่ใช่วิธีที่สะดวกเลย โดยเฉพาะถ้าทีมมีคนที่ไม่ถนัด Command Line

บทความนี้จะพาคุณสร้าง Scheduler List Dashboard — หน้าเว็บที่แสดงรายการ Scheduled Tasks ทั้งหมดของ Laravel พร้อม Schedule Expression, สถานะ, และประวัติการรัน โดยใช้ฟีเจอร์ที่มีอยู่ใน Laravel อยู่แล้ว ไม่ต้องลง Package เพิ่ม

💡 เหมาะสำหรับ: ผู้ที่เริ่มต้นกับ Laravel และต้องการเข้าใจ Task Scheduling รวมถึงทีมที่ต้องการ Visibility ของ Background Jobs โดยไม่ต้องเปิด Terminal

Laravel Task Scheduling คืออะไร?

Laravel มี Built-in Task Scheduler ที่ให้คุณนิยาม Scheduled Jobs ทั้งหมดใน โค้ด PHP แทนการเขียน Cron หลาย ๆ บรรทัดใน Server — ทำให้ Version Control ได้, อ่านง่าย, และ Deploy พร้อมกับโค้ดเลย

สิ่งที่ต้องมีใน Server มีเพียงบรรทัดเดียวใน Crontab:

crontab
# Server Cron - รันทุกนาที แค่บรรทัดเดียว * * * * * cd /var/www/html && php artisan schedule:run >> /dev/null 2>&1

แล้ว Laravel จะจัดการ Tasks ที่ถึงเวลาแต่ละตัวให้เอง งานทั้งหมดนิยามใน app/Console/Kernel.php (Laravel 10 ลงไป) หรือใช้ Schedule Facade ใน routes/console.php (Laravel 11+)

app/Console/Kernel.php (Laravel 10)
protected function schedule(Schedule $schedule): void { // รายงานยอดขายทุกวันเที่ยงคืน $schedule->command('report:daily-sales') ->dailyAt('00:00') ->withoutOverlapping(); // ลบ Log เก่ากว่า 30 วัน ทุกอาทิตย์ $schedule->command('logs:cleanup') ->weekly() ->sundays() ->at('02:00'); // ส่ง Email สรุปทุก 6 ชั่วโมง $schedule->command('email:summary') ->everySixHours(); // Sync ข้อมูลกับ API ภายนอก ทุก 15 นาที $schedule->call(function() { ExternalApiSync::run(); })->everyFifteenMinutes(); }

Frequency Methods ที่ใช้บ่อย

Laravel มี Helper Methods สำหรับกำหนดความถี่ในการรัน Task ให้ใช้ได้เลยโดยไม่ต้องจำ Cron Syntax:

MethodCron ExpressionความหมายUse Case
->everyMinute()* * * * *ทุกนาทีHealth Check
->everyFifteenMinutes()*/15 * * * *ทุก 15 นาทีAPI Sync
->hourly()0 * * * *ทุกชั่วโมงCache Refresh
->dailyAt('08:00')0 8 * * *ทุกวัน เวลาที่กำหนดรายงานประจำวัน
->weekly()0 0 * * 0ทุกอาทิตย์Cleanup
->monthly()0 0 1 * *ทุกเดือนInvoice
->cron('30 9 * * 1-5')Customวันทำการ 09:30 น.Custom Schedule

สร้าง Scheduler List Dashboard

เราจะสร้าง Route + Controller + Blade View เพื่อดึง Scheduled Tasks ออกมาแสดงบนหน้าเว็บ Laravel มี app('Illuminate\Console\Scheduling\Schedule') ที่ให้ดึงรายการ Events ออกมาได้เลย

Step 1: สร้าง Controller

app/Http/Controllers/SchedulerController.php
<?php namespace App\Http\Controllers; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Http\Request; use Carbon\Carbon; class SchedulerController extends Controller { public function index(Schedule $schedule) { $events = collect($schedule->events()) ->map(function($event) { return [ 'command' => str_replace('\'artisan\' ', '', $event->command), 'expression' => $event->expression, 'description'=> $event->description ?? '-', 'timezone' => $event->timezone ?? config('app.timezone'), 'without_overlapping' => $event->withoutOverlapping, 'run_in_background' => $event->runInBackground, 'next_run' => $event->nextRunDate(Carbon::now(), 0, false, true), ]; }); return view('scheduler.index', compact('events')); } }

Step 2: เพิ่ม Route

routes/web.php
use App\Http\Controllers\SchedulerController; // ป้องกันด้วย Middleware auth ก่อน Deploy จริง Route::middleware(['auth'])->group(function() { Route::get('/admin/scheduler', [SchedulerController::class, 'index']) ->name('scheduler.index'); });

Step 3: สร้าง Blade View

resources/views/scheduler/index.blade.php
@extends('layouts.app') @section('content') <div class="container mx-auto py-8 px-4"> <div class="flex items-center justify-between mb-6"> <h1 class="text-2xl font-bold text-gray-800"> 📅 Scheduled Tasks </h1> <span class="text-sm text-gray-500"> ทั้งหมด {{ $events->count() }} Tasks </span> </div> <div class="overflow-x-auto rounded-xl shadow"> <table class="w-full bg-white text-sm"> <thead class="bg-indigo-50 text-indigo-900"> <tr> <th class="px-5 py-3 text-left">Command</th> <th class="px-5 py-3 text-left">Expression</th> <th class="px-5 py-3 text-left">Next Run</th> <th class="px-5 py-3 text-left">Options</th> </tr> </thead> <tbody class="divide-y divide-gray-100"> @forelse($events as $event) <tr class="hover:bg-gray-50"> <td class="px-5 py-4"> <code class="bg-indigo-50 text-indigo-700 px-2 py-1 rounded text-xs"> {{ $event['command'] }} </code> @if($event['description'] !== '-') <p class="text-gray-500 mt-1 text-xs">{{ $event['description'] }}</p> @endif </td> <td class="px-5 py-4 font-mono text-xs text-gray-600"> {{ $event['expression'] }} </td> <td class="px-5 py-4 text-gray-700"> {{ $event['next_run'] }} </td> <td class="px-5 py-4"> @if($event['without_overlapping']) <span class="bg-green-100 text-green-700 text-xs px-2 py-0.5 rounded-full mr-1"> no-overlap </span> @endif @if($event['run_in_background']) <span class="bg-blue-100 text-blue-700 text-xs px-2 py-0.5 rounded-full"> background </span> @endif </td> </tr> @empty <tr> <td colspan="4" class="px-5 py-8 text-center text-gray-400"> ยังไม่มี Scheduled Tasks </td> </tr> @endforelse </tbody> </table> </div> </div> @endsection

เพิ่ม Description และ Security

ก่อน Deploy จริง ต้องทำ 2 อย่างนี้เสมอ:

เพิ่ม Description ให้แต่ละ Task

app/Console/Kernel.php
$schedule->command('report:daily-sales') ->dailyAt('00:00') ->withoutOverlapping() ->description('สร้างรายงานยอดขายประจำวัน ส่ง Email Admin') // ✅ ->appendOutputTo(storage_path('logs/daily-sales.log')); $schedule->command('logs:cleanup') ->weekly()->sundays()->at('02:00') ->description('ลบ Log ไฟล์เก่ากว่า 30 วัน'); // ✅

ป้องกันด้วย Middleware

routes/web.php
// วิธีที่ 1: ใช้ Auth Middleware (แนะนำ) Route::middleware(['auth', 'can:view-admin']) ->get('/admin/scheduler', [SchedulerController::class, 'index']); // วิธีที่ 2: จำกัด IP (สำหรับ Internal Tools) Route::middleware(['auth']) ->get('/admin/scheduler', function() { if (!in_array(request()->ip(), explode(',', env('ADMIN_IPS')))) { abort(403); } return app(SchedulerController::class)->index(app(Schedule::class)); });
⚠️ สำคัญมาก: หน้า Scheduler Dashboard ควรอยู่หลัง Authentication เสมอ เพราะแสดงข้อมูล Internal Tasks ของระบบ อย่าเปิด Public โดยเด็ดขาด

คำสั่งที่ควรรู้จัก

Laravel มี Artisan Commands สำหรับจัดการ Scheduler โดยเฉพาะ ใช้ได้ตั้งแต่ Development จนถึง Production:

Terminal
# ดูรายการ Scheduled Tasks ทั้งหมด (แบบ CLI) php artisan schedule:list # รัน Scheduler ทันที (ทดสอบ) php artisan schedule:run # รัน Scheduler แบบ Loop ทุกนาที (สำหรับ Local Dev) php artisan schedule:work # รัน Task เฉพาะตัว โดยไม่รอ Schedule php artisan schedule:run --task="report:daily-sales" # ดู Log ของ Scheduler tail -f storage/logs/laravel.log

คำสั่ง php artisan schedule:list คือต้นแบบของ Dashboard ที่เราสร้าง แต่เราเปลี่ยนจาก Terminal Output มาเป็นหน้าเว็บที่ทีมทุกคนเข้าดูได้แทน

สรุป: ทำไม Scheduler Dashboard ถึงสำคัญ

การมี Web Dashboard สำหรับ Scheduled Tasks ช่วยให้ทีมพัฒนาและ Operations เห็นภาพรวมของ Background Jobs ทั้งหมดได้ทันที โดยไม่ต้องให้ทุกคนมีสิทธิ์ SSH เข้า Production Server

สิ่งที่ได้ประโยชน์
ดู Tasks ทั้งหมดบนเว็บไม่ต้อง SSH เข้า Server
แสดง Cron Expressionทีม Non-tech เข้าใจ Schedule ได้
แสดง Next Run Timeวางแผน Maintenance ได้ถูกต้อง
ป้องกันด้วย Authปลอดภัย เฉพาะ Admin เท่านั้น
Code อยู่ใน CodebaseVersion Control ได้, Deploy พร้อมกัน

ขั้นต่อไปที่ทำได้คือ เพิ่ม Audit Log บันทึกว่า Task รันสำเร็จหรือไม่, เพิ่มปุ่ม "Run Now" สำหรับ Admin, หรือแม้แต่ต่อ Webhook ส่ง Notification ไปที่ LINE/Slack เมื่อ Task ล้มเหลว

📖 อ่านบทความ PHP / Laravel เพิ่มเติม

บทความใหม่ทุกสัปดาห์เกี่ยวกับ PHP, Laravel, CodeIgniter 4 และ Web Development สำหรับนักพัฒนาไทย

ไปที่ PHP Code Mania →





PHP CI MANIA - PHP Code Generator 

โปรแกรมช่วยสร้างโค้ด "ลดเวลาการเขียนโปรแกรม"
ราคาสุดคุ้ม  
http://www.phpcodemania.com