เลิกใช้ฟังก์ชั่น mysql_xxxxx() ที่ล้าสมัย (deprecated) และเปลี่ยนมาใช้ MySQLi กันดีกว่า
การใช้ MySQLi แทนการใช้ฟังก์ชั่น mysql_xxxxx() นั้น เขียนได้ 2 แบบคือ
แบบ OOP (เรียกใช้ method ของ object)
กับแบบ Procedural (เรียกใช้ฟังก์ชั่น)
OOP
$result = $mysqli->query("SELECT * FROM `table`");
Procedural
$result = mysqli_query($mysqli, "SELECT * FROM `table`");
ในบทความนี้จะกล่าวถึงแบบ OOP เท่านั้น เพราะการเขียนแบบ OOP นั้นมีโอกาสที่จะเกิดความผิดพลาดน้อยกว่า
เหตุผลคือ การเขียน MySQLi แบบ Procedural จะต้องมีการส่งตัวแปร mysqli object ทุกครั้ง
พูดง่ายๆ คือต้องเขียนยาวกว่า และอาจจะสับสนได้ง่ายกว่า
และอาจจะทำงานช้ากว่า เพราะแบบหลังอาจจะเป็นแค่การเรียก method ของ object ที่ส่งไปในแบบ OOP นั่นเอง
ซึ่งจริงๆ แล้ว mysqli_query() อาจจะทำงานในลักษณะนี้
function mysqli_query(MySQLi $link, $query, $resultmode = MYSQLI_STORE_RESULT)
{
return $link->query($query, $resultmode);
}
การเชื่อมต่อฐานข้อมูลด้วย MySQLi
ก่อนจะใช้ MySQLi นั้น ก็เหมือนกับการใช้ฟังก์ชั่น mysql_xxxxx()
คือต้องเชื่อมต่อกับ MySQL Server ก่อน ซึ่งในแบบเดิมเราใช้ mysql_connect()
แต่ในการเขียนด้วย MySQLi จะทำได้สองแบบ
1. ด้วย MySQLi::__construct() หรือการ new mysqli()
แบบนี้จะสามารถสร้าง MySQLi Object และทำการเชื่อมต่อไปได้ในคำสั่งเดียว
$mysqli = new mysqli('localhost', 'root', 'password', 'default_db_name');
$mysqli = mysqli_connect('localhost', 'root', 'password', 'default_db_name');
2. สร้าง MySQLi Object ด้วย mysqli_init() และเชื่อมต่อด้วย MySQLi::real_connect()
โดย mysqli_init() จะสร้าง MySQLi Object ให้เพื่อเอาไปใช้กับ MySQLi::options() และ MySQLi::real_connect()
ความแตกต่างกับแบบแรกคือ แบบแรกจะไม่สามารถกำหนด options ให้กับการเชื่อมต่อ
เช่น ระยะเวลา timeout, คำสั่ง SQL เริ่มต้น, ตั้งค่าการเชื่อมต่อแบบ SSL ฯลฯ
$mysqli = mysqli_init();
// การปรับแต่ง options ต้องทำก่อนการเชื่อมต่อ
$mysqli->options(MYSQLI_OPT_CONNECT_TIMEOUT, 3600);
// เชื่อมต่อ
$mysqli->real_connect('localhost', 'root', 'password', 'default_db_name');
ดังนั้นในส่วนของการเชื่อมต่อ เราใช้ method/function ดังนี้
MySQLi::__construct()
mysqli_connect()
สร้าง MySQLi Object และเชื่อมต่อฐานข้อมูลไปในคราวเดียว
MySQLi::__construct($host, $username, $password, $dbname)
mysqli_connect($host, $username, $password, $dbname)
$host ชื่อหรือ IP ของ MySQL Server เช่น 'localhost' หรือ '127.0.0.1'
$username ชื่อผู้ใช้
$password รหัสผ่าน
$dbname ชื่อฐานข้อมูล default ที่จะใช้กับทุกๆ query ที่อ่านค่าจากตารางโดยไม่เจาะจงฐานข้อมูล
ค่าที่คืนกลับมา
คืน MySQLi Object กลับมาเสมอ ไม่ว่าผลการเชื่อมต่อจะเป็นอย่างไร
ซึ่งหากจะตรวจสอบว่าเชื่อมต่อสำเร็จหรือไม่ ต้องตรวจด้วย MySQLi::$connect_errno หรือ MySQLi::$connect_error
ตัวอย่าง
$mysqli = new mysqli('localhost', 'root', 'password', 'default_db_name');
// หรือ $mysqli = mysqli_connect('localhost', 'root', 'password', 'default_db_name');
if ($mysqli->connect_errno) {
echo $mysqli->connect_error;
exit;
}
mysqli_init()
สร้าง MySQLi Object เพื่อใช้ในการตั้งค่าด้วย MySQLi::options() และเชื่อมต่อด้วย MySQLi::real_connect()
ค่าที่คืนกลับมา
MySQLi Object
ตัวอย่าง
$mysqli = mysqli_init();
MySQLi::real_connect()
เชื่อมต่อฐานข้อมูล โดยต้องเป็น MySQLi Object ที่สร้างขึ้นจาก mysqli_init() เท่านั้น
MySQLi::real_connect($host, $username, $password, $dbname)
$host ชื่อหรือ IP ของ MySQL Server เช่น 'localhost' หรือ '127.0.0.1'
$username ชื่อผู้ใช้
$password รหัสผ่าน
$dbname ชื่อฐานข้อมูล default ที่จะใช้กับทุกๆ query ที่อ่านค่าจากตารางโดยไม่เจาะจงฐานข้อมูล
ค่าที่คืนกลับมา
true หากเชื่อมต่อสำเร็จ
false หากการเชื่อมต่อเกิดความผิดพลาด โดยสามารถตรวจสอบสาเหตุของความผิดพลาดได้ด้วย MySQLi::$connect_error
ตัวอย่าง
$mysqli = mysqli_init();
$mysqli->real_connect('localhost', 'root', 'password', 'default_db_name');
MySQLi::$connect_errno
MySQLi::$connect_error
เป็น property ที่บอกว่า การเรียกใช้ MySQLi::__construct() หรือ MySQLi::real_connect() นั้นมีความผิดพลาดเกิดขึ้นหรือไม่
โดย MySQLi::$connect_errno เป็นตัวเลขของ error code
ส่วน MySQLi::$connect_error จะเป็น error message
หากไม่มี error เกิดขึ้น MySQLi::$connect_errno จะมีค่าเป็น 0
ส่วน MySQLi::$connect_error จะมีค่าเป็น ''
ตัวอย่าง
$mysqli = mysqli_init();
// ใช้ @ ปิดการแสดง warning ไว้ในกรณีที่เราอยากจะแสดง error message ในแบบของเราเอง
@$mysqli->real_connect('localhost', 'root', 'password', 'default_db_name');
if ($mysqli->connect_errno) {
echo $mysqli->connect_error;
exit;
}
ผลลัพธ์อาจจะเป็นเช่นนี้
Access denied for user 'root'@'localhost' (using password: YES)
การสร้าง MySQLi Object ด้วยการ new หรือการสร้า่งด้วย mysqli_init() นั้น
เปรียบเสมือนการจัดหา "โทรศัพท์"
การใช้คำสั่ง SQL
เมื่อเราได้ MySQLi Object จากการเชื่อมต่อฐานข้อมูลที่สำเร็จแล้ว
ก็มาถึงการใช้งาน MySQLi Object นั้นๆ ซึ่งโดยปกติก็คือการส่งคำสั่ง SQL ไปให้ MySQL Server ประมวลผล
ในการเขียนแบบเก่าเราใช้ฟังก์ชั่น mysql_query()
แต่ในแบบใหม่ เราจะใช้ method MySQLi::query() หรือ MySQLi::real_query() และอื่นๆ
MySQLi::query()
ส่งคำสั่ง SQL ไปให้ MySQL Server ประมวลผล
MySQLi::query($query)
$query คำสั่ง SQL ไปให้ MySQL Server ประมวลผล ซึ่งควรจะ escape ด้วย MySQLi::real_escape_string()
ค่าที่คืนกลับมา
false หากเกิดความผิดพลาด ซึ่งสามารถตรวจสอบสาเหตุของความผิดพลาดได้ด้วย MySQLi::$errno และ MySQLi::$error
true หากคำสั่ง SQL ที่ส่งไปเป็นคำสั่งที่ไม่คืนผลลัพธ์เป็นข้อมูลกลับมา เช่น INSERT, UPDATE, DELETE ฯลฯ
MySQLi_Result Object หากคำสั่ง SQL ที่ส่งไปเป็นคำสั่งที่อ่านข้อมูลกลับมา เ่ช่น SELECT, SHOW, EXPLAIN ฯลฯ
ตัวอย่าง การ INSERT
// escape ค่าที่มาจากผู้ใช้เพื่อป้องกัน SQL Injection
$_POST['username'] = $mysqli->real_escape_string($_POST['username']);
$_POST['password'] = $mysqli->real_escape_string($_POST['password']);
// $result จะเ็ป็น Boolean true หรือ false บอกว่าการ query นั้นๆ สำเร็จหรือไม่
$result = $mysqli->query(
"
INSERT INTO `users`
(`username`, `password`)
VALUES
('$_POST[username]', '$_POST[password]')
"
);
ตัวอย่างการ SELECT
// escape ค่าที่มาจากผู้ใช้เพื่อป้องกัน SQL Injection
$_POST['id'] = $mysqli->real_escape_string($_POST['id']);
// $result จะเป็น instance ของคลาส MySQLi_Result
$result = $mysqli->query("SELECT * FROM `users` WHERE `id` = '$_POST[id]'");
// ซึ่งจะมี method ที่เกี่ยวข้องกับการดึงข้อมูล เทียบเท่ากับฟังก์ชั่นพวก mysql_fetch_xxxxx()
$row = $result->fetch_assoc();
// แสดงข้อมูลแถวแรก
echo $row['username'];
MySQLi_Result Object
เป็น Object ที่จะได้จากการเรียกใช้ method MySQLi::query() (และอื่นที่คล้ายคลึงกัน)
ซึ่งเป็น Object ที่มี method ในการตรวจสอบและดึงข้อมูล
กระบวนการ query และรับ result นี้ ผมขอเปรียบกับการสั่งซื้อของกับยี่ปั๊วทางโทรศัพท์
ซึ่งการ query คือการโทรไปสั่งซื้อ บอกว่าต้องการอะไร
และ result ที่ได้รับกลับคืนมาจากการ query นั้น "ยังไม่ใช่สินค้า"
แต่เป็น "รถขนสินค้าและพนักงานยกของ"
// เฮียๆ สั่งเบียร์หน่อย เอาสิงห์ กับช้างนะ รวมๆ กันมา 50 ลังล่ะกัน
$result = $mysqli->query(
"
SELECT * FROM `stock`
WHERE `type` = 'beer' AND `brand` IN ('Singha', 'Chang')
LIMIT 50
"
);
// เปรียบให้ $result คือรถขนสินค้า ที่ตอนนี้บรรทุกเบียร์ที่สั่งไปอยู่เต็มคันรถ
// ถามขนขับรถดูซิว่าขนมากี่ลัง ครบมั้ย
echo $result->num_rows;
// ให้พนักงานขนของช่วยขนเบียร์ลงมาดูสักลังก่อนซิ
$item = $result->fetch_assoc();
// โอเค ถูกต้อง ยกมาหมดเลยล่ะกัน
$warehouse = array();
// ขนลงมาเรื่อยๆ จนกว่าจะไม่มีของให้คนแล้ว
while (($item = $result->fetch_assoc())) {
// ยกเบียร์เข้าไปเก็บในโกดังทีละลัง
$warehouse[] = $item;
}
// บอกให้คนขับรถกลับไปได้แล้ว
$result->free();
MySQLi_Result::fetch_xxxxx()
เป็น method ที่ใช้ดึงข้อมูลที่ได้มาจากการ query ซึ่งมีหลาย method ด้วยกัน แตกต่างตามชนิดข้อมูลที่คืนมา
MySQLi_Result::fetch_assoc() ข้อมูลที่ได้จะเป็น associative array
MySQLi_Result::fetch_row() ข้อมูลที่ได้จะเป็น indexed array
MySQLi_Result::fetch_array() ข้อมูลที่ได้จะเป็นทั้ง indexed array และ associative array
MySQLi_Result::fetch_object() ข้อมูลที่ได้จะเป็น object
โดยปกติเราจะใช้ MySQLi_Result::fetch_assoc() เป็นหลัก
และไม่ควรใช้ MySQLi_Result::fetch_array() เพราะจะทำให้เปลืองหน่วยความจำโดยใช่เหตุ
ตัวอย่าง MySQLi_Result::fetch_assoc()
$result = $mysqli->query("SELECT * FROM `games`");
$row = $result->fetch_assoc();
print_r($row);
// เข้าถึงด้วยชื่อคอลัมน์ในตาราง
echo $row['title'];
ผลลัพธ์
Array
(
[id] => 1
[released] => 2012-09-20
[title] => Pro Evolution Soccer 2013
[price] => 1090
)
Pro Evolution Soccer 2013
ตัวอย่าง MySQLi_Result::fetch_row()
$result = $mysqli->query("SELECT * FROM `games`");
$row = $result->fetch_row();
print_r($row);
// เข้าถึงข้อมูลด้วยลำดับของคอลัมน์ในตาราง
echo $row[2];
ผลลัพธ์
Array
(
[0] => 1
[1] => 2012-09-20
[2] => Pro Evolution Soccer 2013
[3] => 1090
)
Pro Evolution Soccer 2013
ตัวอย่าง MySQLi_Result::fetch_array()
$result = $mysqli->query("SELECT * FROM `games`");
$row = $result->fetch_array();
print_r($row);
// เข้าถึงข้อมูลได้ทั้งสองแบบ
echo $row['2'];
echo $row['title'];
ผลลัพธ์เป็น array ที่มี key ทั้งสองแบบ แต่ไม่ควรใช้เพราะเปลืองหน่วยความจำ (จะเห็นว่ามีข้อมูลที่ซ้ำกัน)
Array
(
[0] => 1
[id] => 1
[1] => 2012-09-20
[released] => 2012-09-20
[2] => Pro Evolution Soccer 2013
[title] => Pro Evolution Soccer 2013
[3] => 1090
[price] => 1090
)
Pro Evolution Soccer 2013Pro Evolution Soccer 2013
ตัวอย่าง MySQLi_Result::fetch_row()
$result = $mysqli->query("SELECT * FROM `games`");
$row = $result->fetch_object();
print_r($row);
// ต้องเข้าถึงข้อมูลในแบบ Object
echo $row->title;
ผลลัพธ์เป็นชนิด Object
stdClass Object
(
[id] => 1
[released] => 2012-09-20
[title] => Pro Evolution Soccer 2013
[price] => 1090
)
Pro Evolution Soccer 2013
MySQLi_Result::free()
ทำลายข้อมูลที่ query มาก่อนหน้า เพื่อคืนหน่วยความจำให้กับระบบ
ในการ query เพื่อให้ได้ MySQLi_Result Object นั้น ทุกๆ ครั้ง MySQL จะต้องเก็บผลลัพธ์ที่ได้ไว้ใน buffer ก่อน
ซึ่งอาจจะเป็นในหน่วยความจำ (RAM) หรือในฮาร์ดดิสก์
หลังจากนั้นจึงให้ผู้ใช้อ่านข้อมูลด้วย method fetch_xxxxx() ตามที่กล่าวมาข้างต้น
ซึ่ง buffer ที่ว่านี้จะยังคงอยู่ และยังคงกินทรัพยากรระบบ
จนกว่าจะมีการเรียก method นี้ หรือจนกว่าโปรแกรม PHP จะจบการทำงาน
ดังนั้นเราควรเรียกใช้ method นี้ทุกครั้งเมื่ออ่านข้อมูลออกมาจนครบ หรือไม่ต้องการอ่านข้อมูลจาก MySQLi_Result Object นั้นๆ แล้ว
ตัวอย่าง
$result = $mysqli->query("SELECT * FROM `games`");
while (($row = $result->fetch_assoc())) {
// do something
}
$result->free();
MySQLi_Result::$num_rows
จำนวนแถวที่อยู่ในผลลัพธ์
ตัวอย่าง MySQLi_Result::fetch_row()
$result = $mysqli->query("SELECT * FROM `games` LIMIT 10");
echo $result->num_rows;
ผลลัพธ์
10
การป้องกัน SQL Injection
มาถึงตรงนี้หากใครไม่เข้าใจคำว่า SQL Injection ลองอ่านบทความนี้ดูก่อนนะครับ
สิ่งที่ทุกคนต้องรู้ ในการเขียนโปรแกรมด้วย PHP กับ MySQL หากไม่อยากให้ระบบที่เขียนนั้นถูก HACK ได้ !!!
MySQLi ก็มี method ที่ทำหน้าเหมือนกับ mysql_escape_string() และ mysql_real_escape_string() นั่นก็คือ
MySQLi::real_escape_string()
ใช้ escape ค่าที่ต้องการให้สามารถใช้ใน query ได้อย่างปลอดภัย
MySQLi::real_escape_string($escapestr)
$escapestr ค่าที่ต้องการ escape
ค่าที่คืนกลับมา
string ที่ผ่านการ escape แล้ว
ตัวอย่าง
echo $mysqli->real_escape_string("Cookie's cat");
ผลลัพธ์
Cookie\'s cat
ตัวอย่างการ escape ค่าเืพื่อนำไปใช้ใน query
$_POST['username'] = $mysqli->real_escape_string($_POST['username']);
$_POST['password'] = $mysqli->real_escape_string($_POST['password']);
$result = $mysqli->query(
"
SELECT * FROM `users`
WHERE `username` = '$_POST[username]' AND `password` = '$_POST[password]'
"
);
ตัวอย่างการใช้ร่วมกับ sprintf()
$result = $mysqli->query(
sprintf(
"
SELECT * FROM `users`
WHERE `username` = '%s' AND `password` = '%s'
",
$mysqli->real_escape_string($_POST['username']), // แทนที่ %s ตัวที่ 1
$mysqli->real_escape_string($_POST['password']) // แทนที่ %s ตัวที่ 2
)
);
แต่จะเห็นว่าการเขียนแบบดังกล่าวอาจจะสร้างความรำคาญใจให้กับหลายๆ คน เพราะต้องพิมพ์โค้ดยาวมาก
ดังนั้นเราอาจจะสร้าง method ที่ลดการทำงานเหล่านี้ลงด้วยการ extend คลาส MySQLi
// สร้างคลาสขึ้นมาใหม่โดยขยายคลาส MySQLi
class ExtendedMySQLi extends MySQLi
{
public function __construct($host, $username, $password, $dbname)
{
// เราจำเป็นต้องเรียก constructor ของคลาสแม่ทุกครั้ง เพื่อให้คลาสลูกทำงานได้อย่างถูกต้อง
parent::__construct($host, $username, $password, $dbname);
}
// ประกาศ method ใหม่ที่เราต้องการ
// โดยในที่นี่เราจะสร้าง method ที่สามารถแทนที่ตัวแปรที่ผ่านการ escape แล้วลงใน query
// และส่ง query ให้ MySQL Server ในคราวเดียว
public function queryf($query)
{
// ตัวแปรพิเศษที่จะทำให้การทำงานเร็วขึืน ใช้เพื่อเติมเต็ม array ที่จะส่งไปให้ vsprintf
// จะได้ไม่เกิด error ในกรณีที่ผู้ใช้กำหนดตัวแปรน้อยกว่าจำนวนตัวแทนที่ใน $query
static $fill = array(
null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null,
);
$tmp = func_get_args();
// escape ตัวแปรทุกตัว
foreach (array_slice($tmp, 1) as $arg) {
$args[] = $this->real_escape_string($arg);
}
return isset($args)
// หากมีตัวแปรส่งมา ก็ให้เรียกใช้ vsprintf() เพื่อแทนที่ค่าตัวแปรลงใน query
? $this->query(vsprintf($query, $args + $fill))
// นอกนั้นก็ query ตามปกติ
: $this->query($query);
}
}
$mysqli = new ExtendedMySQLi('localhost', 'root', '', 'testdb');
$result = $mysqli->queryf("SELECT '%s'", "Cookie's cat");
$row = $result->fetch_row();
echo $row[0];
บทความที่เกี่ยวข้อง