การทำ Routing ใน PHP ด้วย AltoRouter
รู้หรือไม่ PHP สามารถทำ Routing ได้ด้วยนะ....
ปกติแล้ว PHP ที่เราเคยเขียนมาจะทำงานร่วมกับเว็บเซิร์ฟเวอร์อย่าง Apache หรือ Nginx ที่เราสามารถเรียกใช้ไฟล์ PHP ผ่าน URL โดยตรงได้เลย เช่น กรณีที่เราจะเรียกใช้ไฟล์ get.php ขึ้นมา ก็สามารถเรียกผ่าน URL https://domain.com/get.php ตรงๆ ได้เลย แต่ทั้งนี้นักพัฒนาหลายๆ คนจะใช้วิธีการแก้ปัญหาการเรียกไฟล์ตรงๆ ด้วยการทำ Query String ผ่าน URL แล้วนำมา include ผ่าน index.php หรือไฟล์หลักต่างๆ เช่น https://domain.com/?page=getdata ซึ่งตัวแปล page นี้จะเป็นตัวแปรกำกับในการเรียกใช้งานไฟล์ต่างๆ คือ ถ้า page มีค่าเท่ากับ getdata ก็จะเรียกไฟล์ get.php ขึ้นมาแสดงผล
แต่พอเมื่อเราไปใช้งานภาษาอื่น เช่น NodeJS จะเห็นได้ว่า URL มีการกำหนดแพทเทิร์นของ URL ระบุว่า URL มาในลักษณะนี้จะเรียกหน้าไหนมาแสดง เราเรียกวิธีกำหนด URL แบบนี้ว่าการทำ routing เช่น https://domain.com/product/123 ไม่เพียงเท่านี้ ยังมี Framewwork ใหญ่ๆ หลายตัวที่ทำ ไม่ว่าจะเป็น MVC อย่าง Laravel ,YII ที่เป็นภาษาของ PHP ก็สามารถทำ routing ได้ อีกทั้ง Django Framework ที่เป็น MVT ในภาษาของ Python ก็ยังสามารถกำหนด routing ได้อีกด้วย
เรามักจะเห็นการทำ routing แบบนี้เมื่อเราใช้ Framework ซักตัวในการพัฒนาเว็บไซต์หรือเว็บแอปพลิเคชัน แต่ถ้าเราต้องการพัฒนา Project แบบเล็กๆ ละ เช่น API เล็กๆ ซักตัว หรือเว็บไซต์ที่ไม่ได้มีความซ้ำซ้อนมาก โดยที่ไม่ต้องเสียเวลาไปติดตั้ง Framework ให้ยุ่งยาก AltoRouter คือคำตอบของงานนี้
Request Methods
ก่อนที่เราจะไปดูเรื่องการทำ routing นั้น เรามาดูเรื่อง HTTP Request Methods กันก่อน ซึ่งโดยปกติจะมีอยู่ 9 เมท็อด คือ GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, และ PATCH
แต่ในการทำเว็บ หลักๆ เราจะได้ยุ่มย่ามอยู่แค่ 2 Method เท่านั้น คือ GET และ POST (ถ้าใครทำ API อาจจะได้เล่นกับ Method อื่นด้วย) นิยามสั้นๆ ของสอง Method นี้ในการทำเว็บไซต์ก็คือ
- GET ใช้เรียกดูข้อมูลจากเซิร์ฟเวอร์ เช่นเรียกดูหน้าเว็บปกติ
- POST ใช้ส่งข้อมูลกลับไปที่เซิร์ฟเวอร์ เช่นส่งข้อมูลไปกับฟอร์มเพื่อบันทึก
สำหรับ Method อื่นๆ สามารถ ดูรายละเอียดได้ที่เว็บไซต์ MDN
AltoRouter
หลายๆ ครั้งเรามักจะได้คำแนะนำให้เขียนไฟล์ .htaccess เพื่อซ่อนนามสกุลไฟล์ .php และทำเป็น path-based routing หลอกๆ (แต่จริงๆ ยังเป็นการเปิดไฟล์ตาม URL เหมือนเดิม) หรือบางคนก็เขียน RewriteRule เพื่อแก้ request เอาดื้อๆ เลย
แม้วิธีนี้จะพอใช้งานได้ แต่ในความเป็นจริงมันมักจะสร้างปัญหาให้ในภายหลัง และมักจะผูกอยู่กับเซิร์ฟเวอร์แบบเดียว (ในที่นี้คือ Apache ถ้าย้ายไปใช้ Nginx หรือ IIS จะต้องเขียน rules ใหม่ทั้งหมด)
วิธีที่แนะนำจริงๆ นั่นคือหาไลบรารี่ router มาใช้สักตัวหนึ่ง โดยเรายังต้องเขียน .htaccess เพิ่มเติมเล็กน้อย เพื่อส่ง request ทั้งหมดกลับมาที่ index.php จากนั้นจึงค่อยใช้ router มาวิเคราะห์ request path และดึงหน้าที่ถูกต้องมาแสดงอีกทีหนึ่ง
ไลบรารี่ที่เราจะลองใช้กันในวันนี้คือ AltoRouter เราสามารถติดตั้งไลบรารี่นี้ได้ง่ายๆ ผ่าน Composer
ก่อนจะติดตั้ง Package ใดๆ ของ PHP เราจะต้องติดตั้ง Composer ก่อน
ตรวจสอบหลังการติดตัั้ง composer --version
ติดตั้ง AltoRouter โดยใช้ Composer ผ่าน Terminal จาก Path project ของเรา โดยพิมพ์คำสั่งด้านล่าง
composer
จากนั้นให้ประกาศ use AltoRouter as Router; และเรียกไฟล์ autoload เข้ามาในหน้า index.php
index.php
use AltoRouter as Router;
require_once 'vendor/autoload.php';
เพียงเท่านี้ router ของเราก็พร้อมใช้งานแล้ว
การสั่ง use ... as ... จะเป็นการเปลี่ยนชื่อคลาสที่เราจะเรียก ในกรณีนี้คือเราเปลี่ยนชื่อคลาส AltoRouter ให้เหลือแค่ Router
รีไดเรค request ทั้งหมดให้มาที่ index.php
สิ่งสำคัญของการทำ routing นั้นคือการโยน request ทั้งหมดให้มาลงที่ index.php เพื่อให้ router สามารถเอาพาทมาวิเคราะห์ได้ ซึ่งจริงๆ ในส่วนนี้ก็จะเหมือนกับการเปิด pretty url ของ WordPress นั่นแหละ โดยเราสามารถระบุคอนฟิกของเว็บเซิร์ฟเวอร์แต่ละตัวได้ดังนี้
Apache .htaccess
Apache .htaccess
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule . index.php [L]
Nginx.conf
Nginx.conf
Microsoft IIS web.config
Microsoft IIS web.config
Rewrites requires Microsoft URL Rewrite Module for IIS
Download: https://www.microsoft.com/en-us/download/details.aspx?id=47337
Debug Help: https://docs.microsoft.com/en-us/iis/extensions/url-rewrite-module/using-failed-request-tracing-to-trace-rewrite-rules
-->
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="Imported Rule 1" stopProcessing="true">
<match url="^(.*)/$" ignoreCase="false" />
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
</conditions>
<action type="Redirect" redirectType="Permanent" url="/{R:1}" />
</rule>
<rule name="Imported Rule 2" stopProcessing="true">
<match url="^" ignoreCase="false" />
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
</conditions>
<action type="Rewrite" url="index.php" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
PHP built-in server
ส่วนใครที่ทดสอบโปรเจ็กท์ด้วย built-in server ของ PHP ก็สั่งคำสั่งนี้ได้เลย
PHP built-in server
สามารถเปลี่ยน port ได้ตามใจชอบ
กำหนด Path ของ URL
ตัวอย่างจะทำการเรียกสินค้า
- / หน้าเริ่มต้น
- /product หน้าสร้างและดึงข้อมูล (ถ้ามาเป็น GET จะเป็นฟอร์มดีงข้อมูลสินค้า ถ้ามาเป็น POST จะเป็นเพิ่มสินค้า)
- /product /<id> หน้าแสดงสินค้าตาม id แบบ GET (เวลาเรียกจะเป็น /product /202303/ เพื่อดึงสินค้าไอดี 202303 ออกมาแสดง)
- /product /<id> หน้าแสดงสินค้าตาม id แบบ DELETE (เวลาเรียกจะเป็น /product /202303/ เพื่อลบสินค้าไอดี 202303)
ในการกำหนด Pattern จะมีขั้นตอนง่ายๆ คือประกาศสร้างออพเจ็กท์ Router ขึ้นมาก่อน จากนั้นใช้เมท็อด map() เพื่อ Map path ต่างๆ เข้ากับ callback ที่ต้องการ โดยเมท็อด map() จะรับพารามิเตอร์ 3 ค่าคือ:
- $method คือ Request method ที่ระบุด้านบน
- $path คือ url path ที่ต้องการ
- $callback เป็น callback function ที่เราจะเรียกใช้เมื่อมีการเรียกใช้ $path นี้
index.php
use AltoRouter as Router;
require_once 'vendor/autoload.php';
$router = new Router();
$router->map("GET", "/", function () {
echo "หน้าแรกของเว็บ";
});
$router->map("GET", "/product", function () {
echo "สินค้าทั้งหมด";
});
$router->map("POST", "/product", function () {
echo "เพิ่มสินค้า";
});
$router->map("GET", "/product/[i:id]", function ($id) {
echo "สินค้ารหัส: " . $id;
});
$router->map("DELETE", "/product/[i:id]", function ($id) {
echo "ลบสินค้ารหัส: " . $id;
});
ใน AltoRouter นั้นจะไม่มีการประมวลผล route ให้อัตโนมัติ เราจะต้องสั่ง match() แล้วเรียกใช้ฟังก์ชัน callback เอาเอง (วิธีนี้ทำให้เราสามารถเอา AltoRouter ไปเสียบกับโค้ดที่มีอยู่แล้วได้ง่ายขึ้น รวมถึงสามารถเอาไปเสียบกับเว็บเวิร์ดเพรสเพื่อกำหนด route เองได้ด้วย)
index.php
if( is_array($match) && is_callable( $match['target'] ) ) {
call_user_func_array( $match['target'], $match['params'] );
} else {
echo "ไม่พบหน้าที่ต้องการ";
}
จากตัวอย่างโค้ด จะเห็นได้ว่าเราสามารถทำ Path /product ได้ 2 ครั้ง คือการทำ GET และ POST โดยแยก Method ไป
ในตัวอย่างจะเห็นว่า path /product หรือหน้าเปิดอ่านสินค้านั้น จะเห็นว่ามีการระบุ Pattern เอาไว้เป็น /product /[i:id] และในฟังก์ชัน callback ก็มีรับค่า $id เข้ามา
ประเภทของ Pattern ใน AltoRouter หลักๆ จะมีดังนี้
- * ใช้ดัก request ทั้งหมด อะไรก็ได้
- [i] ดักค่าที่เป็นตัวเลขเท่านั้น
- [a] ดักค่าที่เป็นตัวหนังสือและตัวเลข (alphanumeric)
- [h] ดักค่าที่เป็นเลขฐาน 16 (ตัวเลข 0-9 และหนังสือ A-F)
- [*] ดักค่าอะไรก็ได้ จนกว่าจะถึง / ตัวต่อไป
- [**] ดักค่าทั้งหมดจนสุด URL
แพทเทิร์นที่อยู่ใน [] เราก็สามารถเติม :param_name เข้าไปเพื่อดักมาใช้เป็นพารามิเตอร์ได้ทันที เช่น
- [i:id] ดักตัวเลขมาใช้เป็น $id
- [a:category] ดักตัวเลขและตัวหนังสือมาใช้เป็น $category
- [h:key] ดักเลขฐาน 16 มาใช้เป็น $key
- [*:slug] ดักค่าอะไรก็ได้มาใช้เป็น $slug
- [**:trailing] ดักค่าทั้งหมดจากจุดนี้ไปจนถึงสุด URL มาใช้เป็น $trailing