Apichart Nakarungutti
The Cake is a Lie
ในการใช้ PHP ทำงานร่วมกับฐานข้อมูล จำเป็นต้องรู้ภาษา SQL เสียก่อน และต้องรู้ด้วยว่า ฐานข้อมูลที่ต้องทำงานด้วยนั้นเป็นระบบไหน เพราะ PHP มีคำสั่งเฉพาะสำหรับฐานข้อมูลแต่ละระบบแยกกันอยู่
แต่เมื่อมีเทคโนโลยีมากขึ้นระบบฐานข้อมูลมีความหลากหลายมากขึ้น ความคล่องตัวในการย้ายฐานข้อมูลจากระบบหนึ่งไปยังอีกระบบหนึ่งก็ยุ่งยากขึ้น ทาง PHP จึงสร้าง extension ชื่อ PDO (PHP Data Objects) ขึ้นมาเพื่อเป็นตัวกลางในการจัดการฐานข้อมูล โดยโปรแกรมเมอร์ แทบ ไม่ต้องสนใจว่า ระบบฐานข้อมูลที่จะทำงานร่วมกันเป็นแบบไหน
การเชื่อมต่อกับระบบฐานข้อมูลผ่าน PDO ก็ให้สร้างออปเจ็กของคลาสขึ้นมา
<?php
// prepare database connection variables
$db_host = 'localhost';
$db_name = 'keancode';
$db_user = 'keanuser';
$db_pass = 'keanpass';
// connect
try {
// If you change db server system, change this too!
$conn = new PDO("mysql:host=$db_host; dbname=$db_name", $db_user, $db_pass);
echo "Connected to database";
}
catch (PDOException $e) {
echo $e->getMessage();
}
ตัวอย่างข้างต้นใช้ MySQL แต่ถ้าต้องการเปลี่ยนเป็นอย่างอื่นก็แก้ไขบรรทัดที่ 12 ให้เป็นระบบฐานข้อมูลนั้น ๆ หากไม่มีการใช้คำสั่ง SQL ที่จำเพาะต่อระบบฐานข้อมูลนั้น ๆ ก็จะไม่ต้องแก้ไขโค้ดในส่วนอื่น ๆ อีก
<?php
// PostgreSQL
$conn = new PDO("pgsql:host=$db_host port=5432 dbname=$db_name", $db_user, $db_pass);
// Oracle
$conn = new PDO("OCI:dbname=$db_name;charset=UTF-8", $db_user, $db_pass);
// ODBC with MS Access database
$conn = new PDO("odbc:Driver={Microsoft Access Driver (*.mdb)};Dbq="C:\database.mdb;Uid=Admin");
// SQLite
$conn = new PDO("sqlite:/path/to/database.sqlite");
ส่วนการปิดการเชื่อมต่อนั้น ตามหลักแล้ว PHP จะปิดการเชื่อมต่อให้โดยอัตโนมัติเมื่อจบสคริปท์ แต่หากต้องการสั่งเองก็ใช้
<?php
$conn = null;
เมื่อเชื่อมต่อกับฐานข้อมูลได้แล้ว ก็สามารถใช้คำสั่ง SQL จัดการกับฐานข้อมูลได้ 2 วิธี คือ
exec()
และ query()
prepare()
และ excute()
exec()
จะใช้กับคำสั่ง SQL ที่ไม่คืนข้อมูลกลับมา อย่าง INSERT, UPDATE, DELETE
เป็นต้น เพราะตัวฟังชั่นจะคืนแต่จำนวนแถวที่ได้รับผลกระทบกลับมา หรือหากทำงานไม่สำเร็จมันจะคืนค่า false
กลับมา
<?php
try {
// connect to database
$conn = new PDO("mysql:host=$db_host; dbname=$db_name", $db_user, $db_pass);
echo "Connected to database \n";
// prevent unreadable characters in many languages
$conn->exec("SET CHARACTER SET utf8");
// add data into database
$count = $conn->exec("INSERT INTO application VALUES (null, 'Opera', 'Browser', 'Opera Software ASA', 1994)");
// display result
echo 'Rows add: ' . $count . "\n";
// close connection
$conn = null;
}
catch (PDOException $e) {
echo $e->getMessage();
}
หากต้องการข้อมูลกลับมาแสดง หรือประมวลผลให้ใช้ query
แทน เวลาจะใช้ก็ใช้ foreach
ดึงออกมา
<?php
// get data
$result = $conn->query("SELECT name,year FROM application");
// display it
if ($result !== false) {
echo 'There is ' . $result->rowCount() . " application(s) in database.\n";
foreach($result as $row) {
echo '- ' . $row['name'] . ' was released on ' . $row['year'] . "\n";
}
}
แต่ข้อมูลนี้ไม่ใช่อาเรย์ แต่เป็นออปเจ็กของคลาส PDOStatement โดยมีกระบวนการภายในของคลาสคอยจัดการข้อมูลให้ หากไม่ได้กำหนดพารามิเตอร์เพิ่มเติมให้กับฟังชั่น query()
ค่าที่ได้จะเป็นทั้ง associative array ที่ใช้ชื่อคอลัมภ์เป็นคีย์ และ numberic array ที่ใช้ตัวเลข (เริ่มจาก 0) เป็น index (ซึ่งภายในมันก็คือตัวเดียวกันนั่นแหละ)
นอกจากใช้ foreach
แล้ว สามารถใช้ while()
ร่วมกับ fetch()
ซึ่งสามารถกำหนดรูปแบบของข้อมูลเพิ่มเติมเข้าไปได้หากต้องการ
<?php
// get data
$result = $conn->query("SELECT name,year FROM application");
// display it
if ($result !== false) {
echo 'There is ' . $result->rowCount() . " application(s) in database.\n";
while($row = $result->fetch()) {
echo '- ' . $row['name'] . ' was released on ' . $row['year'] . "\n";
}
}
ฟังชั่น exec()
และ query()
นั้นมีความเสี่ยงด้านความปลอดภัยอยู่ เราจำเป็นต้องใช้คำสั่ง quote()
กับคำสั่ง SQL ด้วยตัวเอง เพื่อป้องกัน SQL Injection หรืออีกวิธีหนึ่งคือ ฟังชั่น prepare()
ร่วมกับ execute()
prepare()
จะเป็นการเตรียมคำสั่ง SQL ให้พร้อมก่อนที่จะคิวรี่ไปยังฐานข้อมูล
<?php
// prepare and query (direct)
$result = $conn->prepare("SELECT * FROM application WHERE category='Browser'");
$result->execute();
// display it
if ($result !== false) {
echo $result->rowCount() . " application(s) in Browser category.\n";
while($row = $result->fetch()) {
echo '- ' . $row['name'] . ' was released on ' . $row['year'] . "\n";
}
}
นอกจากนี้ prepare()
ยังรองรับการผูก (bind) ระหว่างอาเรย์เข้ากับคำสั่ง SQL ซึ่งช่วยให้การคิวรี่ข้อมูลมาก ๆ จากฐานข้อมูลทำได้สะดวกขึ้น ซึ่งจะมี 2 แบบ คือ
แบบไม่ตั้งชื่อ จะใช้เครื่องหมาย ? วางไว้ในตำแหน่งที่จะแทรกข้อมูล โดย PHP จะนำข้อมูลไปใส่ให้ตามลำดับ
<?php
// category that I want to show
$cat = {'OS', 'Office Suite'};
$result = $conn->prepare("SELECT * FROM application WHERE category=? OR category=?");
$result->execute($cat);
if ($result !== false) {
echo 'Found ' . $result->rowCount() . " application(s).\n";
while($row = $result->fetch()) {
echo '- ' . $row['name'] . ' was released on ' . $row['year'] . "\n";
}
}
อีกแบบคือมีการตั้งชื่อให้กับตำแหน่งที่จะวาง โดยใช้ : นำหน้าชื่อ แล้วใช้ associative array ที่มีคีย์เป็นชื่อเดียวกัน
<?php
$term = array(
'cat' => 'Browser',
'year' => 2000,
);
// prepare query
$result = $conn->prepare("SELECT * FROM application WHERE category=:cat AND year>:year");
// bind statement and query it
$result->execute($term);
if ($result !== false) {
echo 'Found ' . $result->rowCount() . " application(s).\n";
while($row = $result->fetch()) {
echo '- ' . $row['name'] . ' was released on ' . $row['year'] . "\n";
}
}
เมื่อจะเปลี่ยนแปลงค่าก็แก้ไขค่าในอาเรย์ แล้วสั่ง execute()
ใหม่ได้ทันที
<?php
function display_data($result) {
if ($result !== false) {
echo 'Found ' . $result->rowCount() . " application(s).\n";
while($row = $result->fetch()) {
echo '- ' . $row['name'] . ' was released on ' . $row['year'] . "\n";
}
}
}
$term = array(
'cat' => 'Browser',
'year' => 2000,
);
// prepare query
$query = $conn->prepare("SELECT * FROM application WHERE category=:cat AND year>:year");
// bind statement and query it
$query->execute($term);
display_data($query);
// change bind data
$term = array(
'cat' => 'Graphic Editor',
'year' => 1991,
);
// bind with new data and query it
$query->execute($term);
display_data($query);
แม้ว่า PDO จะมีข้อดีเรื่องการทำงานกับระบบฐานข้อมูลได้หลากหลาย แต่หากต้องการรีดประสิทธิภาพ และความเร็วในการจัดการฐานข้อมูลให้มากที่สุด รวมทั้งใช้คำสั่งพิเศษที่มีเฉพาะระบบฐานข้อมูลนั้น ๆ การใช้คำสั่งสำหรับฐานข้อมูลแต่ละตัวเป็นสิ่งจำเป็น ซึ่งก็แลกมาด้วยการแก้ไขโค้ดจำนวนมาก เมื่อต้องการเปลี่ยนฐานข้อมูลเป็นระบบอื่น หรือต้องการให้โปรแกรมที่เขียนขึ้นรองรับฐานข้อมูลที่หลากหลายขึ้น อันนี้ก็ต้องช่างใจกันเอาเอง
แต่หากใช้ framework ต่าง ๆ ในการทำงาน แนะนำให้ใช้คำสั่งเฉพาะที่ framework นั้น ๆ มีให้ แทนที่จะเข้าถึงฐานข้อมูลโดยตรง เพราะ framework จะคอยจัดการเชื่อมต่อ และเตรียมข้อมูลที่คิวรี่ได้มาให้โดยอัตโนมัติ ซึ่งง่าย สะดวก และปลอดภัยกว่า