การทำ Routing ใน PHP ด้วย AltoRouter

  3959

 รู้หรือไม่ 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 

r1.PNG

 ติดตั้ง AltoRouter โดยใช้ Composer ผ่าน Terminal จาก Path project ของเรา โดยพิมพ์คำสั่งด้านล่าง

composer
composer require altorouter/altorouter

จากนั้นให้ประกาศ use AltoRouter as Router; และเรียกไฟล์ autoload เข้ามาในหน้า index.php

index.php
<?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
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule . index.php [L]

Nginx.conf

Nginx.conf
try_files $uri /index.php;

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
php -S localhost:8002 index.php

สามารถเปลี่ยน 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 ค่าคือ:

  1. $method คือ Request method ที่ระบุด้านบน
  2. $path คือ url path ที่ต้องการ
  3. $callback เป็น callback function ที่เราจะเรียกใช้เมื่อมีการเรียกใช้ $path นี้
index.php
<?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
$match = $router->match();
if( is_array($match) && is_callable( $match['target'] ) ) {
call_user_func_array( $match['target'], $match['params'] );
} else {
echo "ไม่พบหน้าที่ต้องการ";
}

จากตัวอย่างโค้ด จะเห็นได้ว่าเราสามารถทำ Path  /product ได้ 2 ครั้ง คือการทำ GET และ POST โดยแยก Method ไป

r2.PNG

r3.PNG

r4.PNG

r5.PNG

ในตัวอย่างจะเห็นว่า 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

  AltoRouter   Routing    PHP Routing    การทำ Routing ใน PHP ด้วย AltoRouter