เนื้อหาตอนต่อไปนี้จะมาดูเกี่ยวกับการจัดการ routing
ซึ่งอาจจะถึอได้ว่าเป็นส่วนของการทำงานหลักของ slim app
การกำหนด route หรือ routing เข้าใจอย่างง่ายก็คือการกำหนด
path เส้นทาง หรือ url path และการทำงาน ให้กับ HTTP request
ที่ส่งเข้ามา
การสร้าง Route
เราสามารถสร้าง route ด้วยรูปแบบคำสั่งต่างๆ ดังนี้
// แบบเจาะจง method ที่ต้องการใช้งาน $app->get('/', function () { ... }); // GET method $app->post('/', function () { ... }); // POST method $app->put('/', function () { ... }); // PUT method $app->delete('/', function () { ... }); // DELETE method $app->options('/', function () { ... }); // OPTIONS method $app->patch('/', function () { ... }); // PATCH method // แบบรองรับทุก method ที่ส่งเข้ามา $app->any('/', function () { ... }); // ทุกๆ method // แบบรองรับเฉพาะ method ที่กำหนดในคราวเดียว $app->map(['GET', 'POST'], '/', function () { ... }); // เฉพาะ method ที่กำหนด
ตัวอย่างที่เราคุ้นเคยในบทความที่ผ่านๆ มา
$app->get('/home', function (Request $request, Response $response, $args) { $response->getBody()->write('Welcome'); return $response; });
ปกติการเรียก url ผ่านบราวเซอร์เพื่อแสดงข้อมูล ก็จะเป็น GET method สมมติเราเปลี่ยนจาก get
เป็น post แล้วเรียกผ่านบราวเซอร์เพื่อแสดง
$app->post('/home', function () { ... });
ก็จะเกิด error 405 หรือก็คือ ไม่อนุญาตให้ใช้งานกับ GET method เพราะว่าเราเปลี่ยนเป็น post
ใน REST API เราจะคุ้ยเคยกับการใช้ Request method ต่างๆ ทำงานในลักษณะดังนี้คือ
- GET สำหรับดึงข้อมูลมาแสดง
- POST สำหรับสร้างข้อมูลใหม่
- PUT สำหรับแก้ไขข้อมูล
- PATCH สำหรับแก้ไขข้อมูล แต่ส่งเฉพาะ ที่ต้องการแก้ไขไปอัพเดท
- DELETE สำหรับลบข้อมูล
- OPTIONS สำหรับตรวจสอบ ก่อนส่ง method ที่รองรับไปทำงาน
ซึ่งการที่ API ใดๆ จะรองรับ method นั้นก็ขึ้นอยู่กับการกำหนดของ server และการกำหนดของตัว API
นั้นๆ ด้วย
การกำหนด Route callbacks
เป็นส่วนที่กำหนดการทำงานให้กับ route สังเกตจากตัวอย่างด้านล่าง
// Route callbacks แบบเป็นฟังก์ชั่น *ค่าเริ่มต้นที่ใช้เป้นรูปแบบนี้ $app->get('/home', function (Request $request, Response $response, array $args) { $response->getBody()->write('Welcome'); return $response; }); // แบบเป็น class $app->get('/home', 'MyRestfulController');
ซึ่งในแบบที่เป็น class ก็ต้องไปกำหนดการใช้งานให้รองรับ request และ response object เหมือนกับ
แบบฟังก์ชั่น หรือรูปแบบคล้ายๆ กับการกำหนด middleware
ตัวอย่างการกำหนดเป็นไฟล์ class แยก เช่นชื่อ
ไฟล์ HomeAction.php
use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; class HomeAction { public function __invoke(Request $request, Response $response, array $args) { $response->getBody()->write('Welcome'); return $response; } }
นำมาใช้งานในไฟล์ index.php
require 'HomeAction.php';
จากนั้นเรียกใช้งานในการกำหนด route เป็นดังนี้
$app->get('/home', 'HomeAction'); // หรือแบบนี้ก็ได้ // $app->get('/home', \HomeAction::class);
เราสามารถกำหนดในไฟล์ class เป็นหลายๆ method แล้วเรียกใช้งานได้ เช่น
ไฟล์ HomeAction.php
use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; class HomeAction { public function home(Request $request, Response $response, array $args) { $response->getBody()->write('Welcome'); return $response; } public function contact(Request $request, Response $response, array $args) { $response->getBody()->write('Contact'); return $response; } }
จากนั้นเรียกใช้งานในการกำหนด route เป็นดังนี้
$app->get('/home', \HomeAction::class. ':home'); $app->get('/contact', '\HomeAction:contact'); $app->get('/contact', [\HomeAction::class, 'contact']);
เราสามารถแยกออกมาเป็นตัวแปร แล้วเรียกใช้งาน
// แยกมาเป็นตัวแปร $callbackRoute = function (Request $request, Response $response, array $args) { $response->getBody()->write('Welcome'); return $response; }; // Routing $app->get('/home', $callbackRoute);
ส่วนของ request และ response เราได้อธิบายไปแล้วในบทความผ่านๆ มา ส่วนสำหรับค่า $args จะเป็นค่า
ข้อมูลที่อยู่ใน route รองรับข้อมูลแบบ array ดูตัวอย่างประกอบ
// /product/1 $app->get('/product/{id}', function ($request, $response, $args) { echo $args['id']; // 1 $response->getBody()->write('Welcome'); return $response; });
id เปรียบเสมือน parameter หนึ่งที่ส่งมาใน route เราสามารถใช้งานผ่านตัวแปร $args ในรูปแบบอ้างอิง
array key ตามตัวอย่างด้านบน
ถ้าต้องการให้ฟังก์ชั่น สามารถเรียกใชังานตัวแปรจากภายนอก เราสามารถส่งค่าเข้าไปโดยใช้ use เช่น
$app->get('/home', function ($request, $response, $args) use ($app, $db) { $response = $app->getResponseFactory()->createResponse(); $response->getBody()->write('Welcome'); return $response; });
การแสดงข้อมูลจาก route เราสามารถทำได้ใน 2 วิธีคือใช้คำส่ง echo ปกติ กับใช้คำสั่ง return ส่วนจอง
ข้อมูลออกไปกับ HTTP Response object
// Routing $app->get('/home', function ($request, $response, $args) { echo 'Welcome'; $response->getBody()->write('Welcome'); return $response; });
กรณีต้องการ redirect จาก route หนึ่งไปยังอีก route หนึ่งสามารถกำหนดได้ดังนี้
$app->redirect('/', '/home', 301);
จำไว้ว่า การ redirect ข้างต้นเป็นการกำหนด route วิธีหนึ่ง ดังนั้น จะต้องไม่มีการกำหนด get route
ให้กับ '/' ซ้ำอีก เช่น
// แบบนี้จะเป็นกำหนดซ้ำ จะ error $app->redirect('/', '/home', 301); $app->get('/', function () { ... });
การกำหนด redirect จะกำหนดด้วยกัน 3 ค่าคือต้นทาง path , ปลายทาง path และ status code
ถ้าใช้งานในโฟลเดอร์ย่อย ส่วนของปลายทาง path อาจจะต้องกำหนดแบบเต็ม เช่น
$app->redirect('/', '/demo/api/home', 301); // 301 ย้ายถาวร // 302 ย้ายชั่วคราว // ค่า status จะมีผลกับ seo มากกว่า
การกำหนด Route placeholders
เป็นการกำหนด route ให้รองรับค่าอัตโนมัติตามรูปแบบที่กำหนด ตัวอย่างเช่น สมมติเรามีสินค้าหลาย
รายการ แต่ละรายการก็มี id เฉพาะ เป็น /product/1 /prodcut/2 .... เราก็สามารถกำหนดโดยใช้งาน name
placeholder ในลักษณะดังนี้ได้
$app->get('/product/{id}', function ($request, $response, $args) { $response->getBody()->write('Product '.$args['id']); return $response; });
การกำหนดในลักษณะข้างต้น อาจจะไม่รัดกุมนักหากเราต้องการให้ id เป็นเพียงตัวเลข เช่น /product/shirt
ก็ยังเข้าเงื่อนไข route ข้างต้น ดังนั้น เราสามารถกำหนด เพิ่มเติมให้กับ id ด้วยรุปแบบ regular expression
หรือเรียกย่อว่า regex ในลักษณะนี้เพิ่มเข้าไปได้
$app->get('/product/{id:[0-9]+}', function () { ... }); $app->get('/product/{id:[[:digit:]]+}', function () { ... }); // ดูการใช้งาน regular expression ได้ที่ http://niik.in/396
การกำหนด name placeholder ข้างต้น เราจำเป็นต้องกำหนดค่าให้ครบ จะเรียกแค่ /product/ ได้ไม่มีค่าที่
ตรงกับเงื่อนไขไม่ได้ อย่างไรก็ตาม เราสามารถกำหนดส่วนนี้เป็น option หรือจะมีหรือไม่ ก็ได้ โดยใช้เครื่องหมาย
ปีกกาสี่เหลี่ยม [] ครอบส่วนที่ต้องการกำหนดอีกที เช่น
$app->get('/product[/{id:[0-9]+}]', function () { ... });
การกำหนดลักษณะนี้ /product[/{id:[0-9]+}] จะขอแยกเป็น 2 ส่วนคือ
- /product
- /product/{id:[0-9]+}
จะรองรับทั้งแบบมีและไม่มีส่วนที่กำหนดเพิ่มเติมใน []
ตัวอย่างเพิ่มเติมเช่น
$app->get('/news[/{year}[/{month}]]', function () { ... });
รูปแบบนี้ก็จะรองรับ
- /news
- /news/{year}
- /news/{year}/{month}
ไม่รองรับ
- /news/{month}
ให้เราสังเกต [] เป็นหลัก กรณีซ้อนกันหลายชั้น ข้างต้น [y[m]] หมายความว่า
- จะมีหรือไม่มี ym ก็ได้
- แต่ถ้าจะมี m ต้องมี y ก่อน
- จะมีแค่ y ไม่มี m ก็ได้
- จะมีแค่ m ไม่ได้
กรณีเราต้องการให้รองรับหลายๆ segment คำว่า url segment คือการแบ่งแต่ละส่วนด้วย /
สามารถกำหนดในลักษณะนี้แทนได้
$app->get('/news[/{params:.*}]', function () { ... });
รูปแบบนี้ก็จะรองรับ
/news /news/2016/ /news/2016/03/ /news/2016/03/20 ... ไปเรื่อยๆ ไม่สิ้นสุด
ตัวแปรค่า $args['params'] จะเป็นข้อความทั้งหมดหลัง /news/ ถ้ามี เช่น
// Routing // /news/2016/03/20 $app->get('/news[/{params:.*}]', function ($request, $response, $args) { echo $args['params']; // 2016/03/20 $response->getBody()->write('News '); return $response; });
การกำหนด Route names
เราสามารถกำหนดชื่อให้กับ route เพื่อใช้อ้างอิงในการสร้าง url กำหนด route เฉพาะที่ต้องการใช้งาน
สมมติเช่น เมื่อผู้ใช้ล็อกอินผ่านมือถือเข้าใช้งาน ก็อยากให้ไปที่ /user/john?ref=mobile ทำได้ดังนี้
// Routing $app->get('/user/{name}', function ($request, $response, array $args) { $response->getBody()->write("Hello, " . $args['name']); return $response; })->setName('user'); $app->get('/signin', function ($request, $response, array $args) use ($app) { $routeParser = $app->getRouteCollector()->getRouteParser(); $userURL = $routeParser->urlFor('user', ['name' => 'john'], ['ref' => 'mobile']); // echo $userURL; // /user/john?ref=mobile // ถ้าอยู่ในโฟลเดอร์ย่อยจะมี path ก่อนหน้าด้วย // /demo/api/user/john?ref=mobile return $response ->withHeader('Location', $userURL); });
สังเกตว่าในส่วนของหน้าแสดงข้อมูลผู้ใช้ เรากำหนด route ให้รองรับในรูปแบบ /user/{name}
แล้วแสดงข้อความ Hello ตามด้วยชื่อคนที่กำลังใช้งาน ในกรณีตัวอย่างของเราก็คือ Hello john
ใน route นี้เรากำหนดชื่อให้กับ route ด้วยคำส่ง setName() และกำหนดชื่อที่ต้องการอ้างอิงใช้งาน
ด้วยชื่อว่า user ชื่อ route นี้ เราสามารถเรียกใช้งาน ใน route ใดๆ ก็ได้ ข้างต้นเราเรียกใช้งานใน
route ที่จำลองการล็อกอินเข้าใช้งานสำเร็จ
// เรียกใช้งานส่วนจัดการข้อมูล route $routeParser = $app->getRouteCollector()->getRouteParser(); $userURL = $routeParser->urlFor('user', ['name' => 'john'], ['ref' => 'mobile']);
เราใช้คำสั่ง urlFor() เพื่อจัดการกับชื่อ route ที่ต้องการ โดยจะสร้าง route url ให้เราตามค่าที่กำหนด
รองรับการกำหนดค่า 3 ส่วนด้วยกันคือ ส่วนแรก เป็นชื่อ route ที่เราจะใช้งาน ส่ว่นที่สองเป็นข้อมูลที่อ้างอิง
กับ name placeholder คือเราต้องการให้ส่วนของ name มีค่าเท่ากับ john และสุดท้ายเป็นส่วนของ
query string เป็น key กับ value ที่เราต้องการเพิ่มไปใน url ที่ต้องการ ในตัวอย่าง เราต้องการอ้างอิงว่า
ล็อกอินผ่านมือถือ เมื่อทำการสร้าง url ก็จะได้ค่า $userURL เป็น /user/john?ref=mobile
ในตัวอย่างเราจำลองการล็อกอิน จากนั้นก็ให้ลิ้งค์ไปยัง route ของ user ที่เรากำหนด
การกำหนด Route groups
ใน route ที่อาจจะมีส่วนจัดการแยกย่อยจำนวนมาก หรือเราต้องการจัดกลุ่ม route ในส่วนที่เกี่ยวข้องกัน
เราสามารถใช้คำสั่ง group() เพื่อจัดการกับ route ต่างๆ ได้ เช่น สมมติสินค้าของเรา มีส่วนของรูปภาพ และ
ก็ส่วนของรายละเอียด เราต้องการแยกจัดการคนละส่วน แต่ก็ยังอยู่ในสินค้าเดียวกัน ก็สามารถจัดกลุ่มในลักษณะ
นี้ได้ ก่อนใช้งาน เราต้องทำการเรียกใช้ RouteCollectorProxy class ก่อน
use Slim\Routing\RouteCollectorProxy;
จากนั้นกำหนดส่วนของ group ดังนี้
$app->group('/product/{id:[0-9]+}', function (RouteCollectorProxy $group) { // /product/200 $group->get('', function ($request, $response, array $args) { $response->getBody()->write('Product'); return $response; }); /* $group->map(['GET'],'', function ($request, $response, array $args) { $response->getBody()->write('Product'); return $response; }); */ // /product/200/detail $group->get('/detail', function ($request, $response, array $args) { $response->getBody()->write('Product Detail'); return $response; }); // /product/200/photo $group->get('/photo', function ($request, $response, array $args) { $response->getBody()->write('Product Photo'); return $response; }); });
ทั้ง group และ route ด้านใน group สามารถกำหนด route middleware ด้วยคำสั่ง add() ได้
การกำหนดให้กับ group มีผลกับ route ด้านใน เราจะกำหนดทั้งกับ group เป็น middleware หนึ่ง
และใน route เป็นอีก middleware หนึ่งก็ได้
ใน route ด้านในเรายังสามารถกำหนดชื่อให้กับ route ด้วยคำสั่ง setName() ได้
เราสามารถกำหนด route ของ group เป้นค่าว่างก็ได้ เพื่อต้องการจัดกลุ่ม route ด้านในเพื่อทำงาน
หรือใช้งาน middleware ร่วมกัน เช่น
use Slim\Routing\RouteCollectorProxy; // ... $app->group('', function (RouteCollectorProxy $group) { $group->get('/billing', function () { ... }); $group->get('/invoice/{id:[0-9]+}', function () { ... })->add(new RouteMiddleware()); })->add(new GroupMiddleware());
เนื้อหาเกี่ยวกับการจัดการ Routing ใน Slim framework 4 ในเบื้องต้นก็มีประมาณนี้ หวังว่าจะเป็นแนวทาง
ทำความเข้าใจ และนำไปประยุกต์ใช้ต่อไป เนื้อหาตอนหน้าจะเป็นอะไรรอติดตาม