|
PHP อัพเดท MySQL_Connection เป็น Version 2 ครับ |
อัพเดท MySQL_Connection เป็น Version 2 ครับ
สำหรับผู้ที่เคยใช้ class MySQL_Connection ของผม วันนี้มีเวอร์ชั่นใหม่มาแนะนำครับ แต่ไม่ backward compatibility นะครับ ซึ่งเวอร์ชั่นนี้มีการเปลี่ยนแปลงและปรับปรุงในหลายจุดครับ
1. เพิ่มความสามารถในการใช้ table prefix
ซึ่งจะมีประโยชน์สำหรับคนที่ใช้โฮสต์ที่ไม่ให้สร้างฐานข้อมูลมากกว่า 1 หรือจำกัดจำนวนฐานข้อมูล จึงต้องแบ่งฐานข้อมูลตาม prefix ของตาราง
รูปแบบการแทนที่คือ <ชื่อตาราง>
$mysql->tablePrefix = 'web001_';
$mysql->query("SELECT * FROM <user>");
query ที่ได้จะเป็น
SELECT * FROM `web001_user`
2. เพิ่มการ quote identifier ตามแบบ MS SQL Server
คือใช้ [ชื่อคอลัมน์หรือตารางหรือฐานข้อมูล] เพราะ MySQL ใช้ตัวอักษร ` ในการ quote ซึ่งยากต่อการพิมพ์ด้วยคีย์บอร์ดภาษาไทย คนส่วนมากจึงมักไม่ใช้กันและทำให้เกิดปัญหาเช่น ตั้งชื่อคอลัมน์หรือตารางไปตรงกับคำสงวน (พบบ่อยที่สุดคือคำว่า ORDER) และทำให้ error จนต้องไปแก้ไขโครงสร้างตาราง ซึ่งเป็นการแก้ปัญหาที่ปลายเหตุ
$mysql->query("SELECT * FROM [order] WHERE [from] = '[email protected]'");
query ที่ได้จะเป็น
SELECT * FROM `order` WHERE `from` = '[email protected]'
3. Auto Connect
แค่สร้าง instance ของ class ขึ้นมา แล้วมันจะ connect ให้เองเมื่อเรียกใช้ method ใดๆ ที่จำเป็นต้องใช้ connection ในครั้งแรก
$mysql = new MySQL_Connection('localhost', 'root', '', 'phpinfo');
// $mysql->connect();
...
...
...
// จะเริ่ม connect ตรงนี้
$value = $mysql->queryValue("SELECT 1");
และเพิ่ม arguments ในการเชื่อมต่อไปอีกสองตัว คือ port และ socket (ตาม mysqli_connect())
$mysql = new MySQL_Connection(
'localhost', // Host
'root', // Username
'', // Password
'phpinfo', // Default Database
3307, // Port
'/var/run/mysqld/mysql.sock' // Socket
);
4. ตัด class MySQL_Result และตัด method MySQL_Connection::queryResult() ออกไป
และเพิ่ม MySQL_Connection::fetch() และ MySQL_Connection::fetchAll() เข้ามาแทน
จากเดิมถ้าอยากจะค่อยๆ fetch result ออกมา
$result = $mysql->query("SELECT * FROM `user`");
while ($row = $result->fetch()) {
...
}
$result->free();
ในเวอร์ชั่นนี้ใช้ Class MySQL เองเป็นตัว fetch
$mysql->query("SELECT * FROM `user`");
while ($row = $mysql->fetch()) {
...
}
// run query ต่อไปได้เลยโดยไม่ต้องเรียก free() เพราะจะทำให้อัตโนมัติ
$mysql->query("SELECT * FROM `page`");
5. มี option debug ที่ถ้าเป็น false จะไม่จบการทำงานทันที
แต่จะโยน MySQL_Exception ออกมาให้ดักจับ และเลือกจบการทำงานได้ด้วย MySQL_Exception::debug()
$mysql->debug = false;
try {
$mysql->query("SELECT * * FROM `user`");
} catch (MySQL_Exception $exception) {
$exception->debug();
}
6. method MySQL_Connection::query() จะ return ค่าออกมาเป็นจำนวนแถวที่ SELECT ได้ หรือจำนวนแถวที่ UPDATE/DELETE ได้
if ($mysql->query("SELECT * FROM `user`")) {
while ($row = $mysql->fetch()) {
// ...
}
// กระทำการบางอย่างในกรณีที่มีข้อมูล ...
} else {
// ไม่มีข้อมูล
}
Methods/Properties
__construct([$host, [$username, [$password, [$db, [$port, [$socket]]]]])
สร้าง instance ของ MySQL_Connection
$host - ชื่อหรือ IP address ของ MySQL Server เช่น 'localhost' หรือ '127.0.0.1'
$username - ชื่อผู้ใช้ MySQL
$password - รหัสผ่าน
$db - default database สำหรับทุกๆ query สามารถเรียก selectDb() เพื่อเปลี่ยนในภายหลังได้
$port - หมายเลข TCP port ที่ใช้ในการเชื่อมต่อ
$socket - Unix Domain Socket file ที่ใช้ในการเชื่อมต่อ
$mysql = new MySQL_Connection('localhost', 'root', '');
connect([$host, [$username, [$password, [$db, [$port, [$socket]]]]])
ทำการเชื่อมต่อกับ MySQL Server
แต่ method นี้ไม่จำเป็นต้องเรียกใช้ในกรณีทั่วไป เพราะการเรียก method อื่นๆ ที่ต้องการ connection จะทำการเรียก method นี้ให้อัตโนมัติ
แต่จะมีประโยชน์ในกรณีที่ต้องการ connect ด้วย config ที่ต่างออกไปจากตอนสร้าง instance
$mysql = new MySQL_Connection('localhost', 'root', '');
$mysql->connect('localhost', 'admin', '1234');
close()
ตัดการเชื่อมต่อกับ MySQL Server เพื่อปล่อย connection ให้ process อื่นได้ใช้งาน
ควรใช้หลังจากแน่ใจว่าจะไม่มีการใช้งาน database อีกแล้ว และ script อาจจะต้องทำงานต่ออีกสักระยะ
เช่นการแสดงผลลัพธ์ด้วย template engine
เพราะถ้าหากไม่เรียก close() การเชื่อมต่อจะยังคงอยู่จนกว่า script จะจบการทำงาน
$mysql->close();
session_write_close();
$smarty->display('index.tpl');
selectDb($db)
กำหนด default database สำหรับทุกๆ query
$mysql->selectDb('phpinfo');
query($query [, $params])
ส่ง SQL Query ไปให้ MySQL Server ประมวลผล และคืนค่าแถวที่ SELECT/UPDATE/DELETE กลับมา
echo $mysql->query("SELECT * FROM `user`");
method นี้มักจะใช้ร่วมกับ fetch() เพื่อค่อยๆ อ่านข้อมูลที่ SELECT ได้ออกมาทีละแถว
if ($mysql->query("SELECT * FROM `user`") > 0) {
while ($user = $mysql->fetch()) {
// ...
}
}
fetch([$columnKey])
อ่านค่าแถวหลังเรียกใช้ query() ออกมาครั้งละ 1 แถว
ถ้าไม่เจอแถวใดใดเลยหรือยังไม่ได้เรียกใข้ query() หรือ query ที่เรียกไม่คืนค่าแถว เช่น UPDATE หรือ DELETE จะคืนค่ากลับมาเป็น null
$mysql->query("SELECT * FROM `user`");
$first = $mysql->fetch(); // แถวที่ 1
$second = $mysql->fetch(); // แถวที่ 2
$third = $mysql->fetch(); // แถวที่ 3
fetchAll([$columnKey [, $indexKey]])
อ่านค่าแถวหลังเรียกใช้ query() ออกมาทั้งหมด
method นี้คืนค่ากลับมาเป็น array เสมอ แม้จะไม่เจอแถวใดๆ เลยก็ตาม
ดังนั้นจึงปลอดภัยที่จะเอาไปใช้กับ foreach หรือ function อื่นๆ ที่ต้องการ argument เป็น array
$mysql->query("SELECT * FROM `user`");
$first = $mysql->fetch();
$second = $mysql->fetch();
$third = $mysql->fetch();
free()
ทำการคืนหน่วยความจำที่ใช้ไปกับ result ที่ได้จากการ query ก่อนหน้า
$mysql->query("SELECT * FROM `user`);
$user1 = $mysql->fetch();
$user2 = $mysql->fetch();
$mysql->free();
$user3 = $mysql->fetch(); // null
ซึ่งปกติเราไม่จำเป็นต้องเรียก method นี้ เพราะ method อื่นๆ เช่น queryAndFetch() หรือ queryAndFetchAll() จะทำการเรียกให้โดยอัตโนมัติ
method นี้มีประโยชน์เฉพาะเวลาเรา SELECT ข้อมูลออกมาจำนวนหนึ่ง และต้องการ fetch() ทีละแถว และทำตัดสินใจเองว่าจะหยุดเมื่อไหร่
$mysql->query("SELECT * FROM `user`");
while ($user = $mysql->fetch()) {
if ($user['type'] !== 'admin') {
break;
}
$users[] = $user;
}
$mysql->free();
queryAndFetch($query [, $params, [$columnKey]])
ส่ง SQL Query ไปให้ MySQL Server ประมวลผล และคืนค่าแถวแรกที่เจอกลับมา
ถ้าไม่เจอแถวใดใดเลยจะคืนค่ากลับมาเป็น null
$user = $mysql->queryAndFetch(
"SELECT * FROM `user` WHERE `username` = %s[username], `password` = MD5(%s[password]) LIMIT 1",
$_POST
);
if (!isset($user)) {
echo 'user not found or wrong password';
exit;
}
method จะเรียกใช้ free() ทุกครั้ง ดังนั้นแม้จะมีข้อมูลที่ SELECT เจอมากกว่า 1 จะไม่สามารถดึงข้อมูลมากกว่านั้นได้อีก
$user = $mysql->queryAndFetch("SELECT * FROM `user`");
$nextUser = $mysql->fetch(); // จะเป็น null เสมอ เพราะได้ free() ไปแล้ว
queryAndFetchAll($query [, $params, [$columnKey, [$indexKey]]])
ส่ง SQL Query ไปให้ MySQL Server ประมวลผล และคืนทุกแถวที่เจอกลับมา
method นี้คืนค่ากลับมาเป็น array เสมอ แม้จะไม่เจอแถวใดๆ เลยก็ตาม
ดังนั้นจึงปลอดภัยที่จะเอาไปใช้กับ foreach หรือ function อื่นๆ ที่ต้องการ argument เป็น array
foreach ($mysql->queryAndFetchAll("SELECT * FROM `user`") as $user) {
// ...
}
queryValue($query [, $params])
ส่ง SQL Query ไปให้ MySQL Server ประมวลผล และคืนฟิลด์แรกของแถวแรกที่เจอกลับมา
มีประโยชน์สำหรับในการอ่านค่า query สั้นๆ เช่น SELECT COUNT(*) หรือ SELECT ROW_COUNT()
$numUsers = $mysql->queryValue("SELECT COUNT(*) FROM `user`");
$insertId
เป็น property ที่คืนค่า auto_increment ของแถวที่ INSERT ไปล่าสุด
$mysql->query(
"INSERT INTO `user` SET `username` = %s, `password` = MD5(%s)",
array($_POST['username'], $_POST['password'])
);
echo $mysql->insertId;
หาก query ที่เรียกก่อนหน้าไม่ใช่ INSERT ก็จะ return 0
$affectedRows
เป็น property ที่คืนค่าแถวที่มีผลกระทบจากคำสั่งประเภท UPDATE/DELETE หรือจำนวนแถวที่ SELECT ได้
$mysql->query("DELETE FROM `user`");
echo $mysql->affectedRows;
ซึ่งปกติไม่จำเป็นต้องเรียกใช้ก็ได้ เพราะ query() คืนค่านี้กลับมาให้อยู่แล้ว
echo $mysql->query("DELETE FROM `user`");
$numRows
เป็น property ที่คืนจำนวนแถวที่ SELECT ได้
$mysql->query("SELECT * FROM `user`");
echo $mysql->numRows;
ซึ่งปกติไม่จำเป็นต้องเรียกใช้ก็ได้ เพราะ query() คืนค่านี้กลับมาให้อยู่แล้ว
echo $mysql->query("SELECT * FROM `user`");
จะมีประโยชน์ก็ต่อเมื่อ query ด้วย method อื่น
// แม้จะดึงออกมาแค่แถวแรก แต่จริงๆ MySQL อาจจะ SELECT เจอมากกว่า 1 แถว
$user = $mysql->queryAndFetch("SELECT * FROM `user`");
echo $mysql->query->numRows;
$tablePrefix
prefix ของตารางที่จะเอาไปต่อหน้าการแทนที่ในรูปแบบ <ชื่อตาราง>
$mysql->tablePrefix = 'web001_';
$mysql->query("SELECT * FROM <user>");
// SELECT * FROM `web001_user`
$charset
default charset สำหรับการเชื่อมต่อ
default คือ utf8
$mysql->charset = 'tis620';
$debug
หากเป็น true จะจบการทำงานทุกครั้งที่มี error เกิดขึ้นจากการ connect หรือ query
แต่ถ้าเป็น false จะโยน MySQL_Exception ออกมาให้ดักจับ
default เป็น true
แต่ควรกำหนดเป็น false หากต้องการตรวจจับ error
เช่นในกรณีที่ต้องการตรวจสอบว่ามีข้อมูลซ้ำหรือไม่ก่อน INSERT
เราอาจจะไม่ต้อง SELECT เพื่อตรวจสอบ แต่แค่ INSERT ข้อมูลเข้าไป หากข้อมูลที่ INSERT มันมี UNIQUE KEY ซ้ำ ก็จะมี Exception โยนออกมา
// ทำให้โยน Exception ออกมาหากมี error
$mysql->debug = false;
try {
// หาก `username` มี index เป็น UNIQUE จะ INSERT ไม่ได้ถ้าข้อมูลที่เข้าไปใหม่ซ้ำกับของเดิม
$mysql->query(
"INSERT INTO `user` SET `username` = %s, `password` = MD5(%s)",
array($_POST['username'], $_POST['password'])
);
} catch (MySQL_Exception $exception) {
// ... จัดการกับ error
}
$error
$errno
error message และ error code ของ error ที่เกิดขึ้นล่าสุด
ซึ่ง property ทั้งสองนี้จะไม่มีประโยชน์หาก $debug เป็น true เพราะเมื่อเกิด error จะจบการทำงานก่อนได้อ่านค่าจาก property เหล่านี้
$mysql->debug = false;
try {
// หาก `username` มี index เป็น UNIQUE จะ INSERT ไม่ได้ถ้าข้อมูลที่เข้าไปใหม่ซ้ำกับของเดิม
$mysql->query(
"INSERT INTO `user` SET `username` = %s, `password` = MD5(%s)",
array($_POST['username'], $_POST['password'])
);
} catch (MySQL_Exception $exception) {
// ตรวจสอบ error code ว่าเป็นกรณี KEY ซ้ำหรือเปล่า
// http://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html#error_er_dup_entry
if ($mysql->errno === 1062 // ER_DUP_ENTRY
|| $mysql->errno === 1586 // ER_DUP_ENTRY_WITH_KEY_NAME
) {
// ... แจ้งว่าข้อมูลซ้ำ
} else {
// error อื่นๆ
}
}
อธิบาย Arguments ที่ใช้ใน query(), fetch(), fetchAll(), queryAndFetch(), queryAndFetchAll(), queryValue()
$params - ค่าหรือ array ของค่าที่ต้องการแทนที่ใน query
โดยการแทนที่นั้นจะใช้เครื่องหมาย % ตามด้วยอักษร i, s, b, d, f, %
สามารถมี n ต่อท้าย (ยกเว้น %i และ %%) เพื่อกำหนดว่า ค่านี้สามารถเป็น NULL ใน SQL
$mysql->query(
"SELECT %s, %sn",
array(null, null)
);
// SELECT '', NULL
และสามารถมี [ชื่อ key] ต่อท้ายเพื่อกำหนดว่าจะใช้ key ไหนใน array $params
หากไม่กำหนด จะเรียงลำดับตั้งแต่ 0 ไปเรื่อยๆ
$mysql->query(
"SELECT %s, %s[abc], %sn[1000], %s, %s",
array(
'Test',
'PHP',
'MySQL',
'abc' => 555,
1000 => null,
)
);
// SELECT 'Test', '555', NULL, 'PHP', 'MySQL'
สามารถลดการเขียนโค้ดลงได้ โดยส่ง array ที่มี key แน่ชัดอยู่แล้วเข้าไปเป็น $params โดยตรง
// หาก $_POST มี key $_POST['username'] และ $_POST['password']
// ก็สามารถส่งไปเป็น $params ได้โดยตรง
$mysql->query(
"
INSERT INTO `user`
(`username`, `password`)
VALUES
(%s[username], %s[password])
"
$_POST
);
หากค่าที่นำมาแทนที่เป็น array ก็จะแปลงให้เป็น list ของค่า (คั่นด้วย ,)
เหมาะสำหรับเอาไปใช้ใน IN (...) หรือ INSERT INTO (...) VALUES (...)
$superUsers = $mysql->queryAndFetchAll(
"SELECT * FROM `user` WHERE `type` IN (%s)",
// ต้องเป็น array ซ้อน array มิเช่นนั้นจะถูกมองว่าเป็นค่าเดี่ยวๆ
// เช่น หากใช้ array('admin', 'vip') แบบนี้ %s จะถูกแทนที่ด้วย 'admin'
array(
array('admin', 'vip')
)
);
// SELECT * FROM `user` WHERE `type` IN ('admin','vip')
$mysql->query(
"INSERT INTO `user` (%i) VALUES (%s)",
array(
array('username', 'password'), // %i list ของ identifier
array('phpinfo', '1234'), // %s list ของ string
)
);
// INSERT INTO `user` (`username`, `password`) VALUES ('phpinfo', '1234')
i - แปลงค่าให้เป็น identifier คือครอบด้วย `
$mysql->query("SELECT * FROM %i", 'user');
// SELECT * FROM `user`
ถ้าในค่าที่เอาไปแทนที่มีเครื่องหมาย . ก็จะทำการแยกส่วนให้
$mysql->query("SELECT * FROM %i", 'phpinfo.user');
// SELECT * FROM `phpinfo`.`user`
s - แปลงค่าให้เป็น SQL string ซึ่งจะทำการ escape ค่าให้โดยอัตโนมัติ เพื่อป้องกัน SQL Injection
$mysql->query("SELECT %s", 123);
// SELECT '123'
$mysql->query("SELECT %s", "That's it!!!");
// SELECT 'That\'s it!!!'
d - แปลงค่าให้เป็นเลขจำนวนเต็ม (int)
$mysql->query("SELECT %d", '123.123');
// SELECT 123
f - แปลงค่าให้เป็นเลขจำนวนจริง (float)
$mysql->query("SELECT %f", '123.123');
// SELECT 123.123
b - แปลงค่าให้เป็น SQL boolean คือ TRUE หรือ FALSE
$mysql->query("SELECT %b", 123);
// SELECT TRUE
$mysql->query("SELECT %b", 0);
// SELECT FALSE
% - เอาค่าที่ส่งไปแทนที่ตรงๆ โดยไม่ escape ใดใดทั้งสิ้น ควรใช้อย่างระมัดระวัง
$mysql->query("SELECT %%", "COUNT(*)");
// SELECT COUNT(*)
หากในค่าที่ส่งไปมีสัญลักษณ์การแทนที่อื่นๆ ก็จะทำการแทนที่นั้นแบบ recursive
$mysql->query(
"SELECT %%",
array("%d[x] * %d[y]", 'x' => 2, 'y' => 4)
);
// SELECT 2 * 4
เหมาะสำหรับเพิ่มเงื่อนไขแบบ dynamic ให้กับ query
// ถ้ามี $_GET['username'] ส่งมา
if (isset($_GET['username'])) {
// เพิ่มเงื่อนไข
$where[] = `username` = %s[username]";
}
// ถ้ามี $_GET['email'] ส่งมา
if (isset($_GET['email'])) {
// เพิ่มเงื่อนไข
$where[] = "`email` = %s[email]";
}
$_GET[0] = isset($where)
? 'WHERE ' . implode(' OR ', $where)
: '';
$mysql->query("SELECT * FROM `user` %%", $_GET);
/*
สมมติว่าถ้า $_GET['username'] = 'phpinfo' ก็จะได้ query
SELECT * FROM `user` WHERE `username` = 'phpinfo'
แต่ถ้าไม่มีตามเงื่อนไขที่กำหนดก็จะได้ query
SELECT * FROM `user`
*/
$columnKey - ดึงเฉพาะค่าของฟิลด์ที่ต้องการออกมา
โดยปกติการ fetch จะดึงข้อมูลออกมาเป็น array ข้อมูลของแถว แม้ในแถวนั้นๆ จะมีแค่ฟิลด์เดียวก็ตาม
$user = $mysql->queryAndFetchAll("SELECT `username` FROM `user`");
/*
array(
array('username' => 'phpinfo'),
array('username' => 'test'),
)
*/
แต่ถ้าเราอยากได้ array เฉพาะค่าของฟิลด์ที่ต้องการ
$users = $mysql->queryAndFetchAll("SELECT `username` FROM `user`", null, 'username');
/*
array(
'phpinfo',
'test',
)
*/
$indexKey - กำหนดให้ค่าของฟิลด์ที่ต้องการเป็น key ของ array
โดยปกติการ fetchAll จะดึงข้อมูลออกมาเป็น array ข้อมูลของแถวโดยเรียงลำดับจาก 0 ไปเรื่อยๆ
แต่ถ้าเราอยากให้ array ที่ได้ มี key จากค่าในแถว เราสามาถใช้ $indexKey ช่วยได้
$users = $mysql->queryAndFetchAll("SELECT `username`, `email` FROM `user`", null, null, 'username');
/*
array(
'phpinfo' => array('email' => '[email protected]'),
'test' => array('email' => '[email protected]'),
)
*/
ใช้ร่วมกับ $columnKey
$users = $mysql->queryAndFetchAll("SELECT `username`, `email` FROM `user`", null, 'email', 'username');
/*
array(
'phpinfo' => '[email protected]',
'test' => '[email protected]',
)
*/
Source Code
<?php
/**
* MySQL_Connection V2
* คลาสที่จะช่วยให้คุณเขียนโปรแกรมเชื่อมต่อกับฐาน MySQL ได้สะดวกและปลอดภัยขึ้น
* Copyright (c) 2014, phpinfo.in.th (http://www.phpinfo.in.th)
*/
class MySQL_Connection
{
protected static $options = array(
'mysqli.default_host',
'mysqli.default_user',
'mysqli.default_pw',
null,
'mysqli.default_port',
'mysqli.default_socket',
);
private $_debug = true;
private $_tablePrefix = '';
private $_defaultCharset = 'utf8';
private $_affectedRows;
private $_numRows;
private $args;
private $c;
private $r;
private $s = array();
private $d;
private $i;
public function __construct(
$host = null,
$username = null,
$password = null,
$db = null,
$port = null,
$socket = null
) {
$args = array($host, $username, $password, $db, $port, $socket);
foreach (self::$options as $i => $default) {
if (!isset($args[$i]) && isset($default)) {
$args[$i] = ini_get($default);
}
}
$this->args = $args;
}
protected function replaceStringCallback($matches)
{
if (isset($matches[3])) {
$nullable = isset($matches[3][1]);
if (isset($matches[4])) {
$value = isset($this->d[$matches[4]])
? $this->d[$matches[4]]
: null;
} else {
$value = isset($this->d[$this->i])
? $this->d[$this->i++]
: null;
}
if (is_array($value)) {
switch ($matches[3][0]) {
case '%':
return $this->replaceString(implode(',', $value));
case 'i':
foreach ($value as &$ref) {
$ref = implode(
'`.`',
explode('.', preg_replace('/`/u', '``', $ref), 3)
);
}
return '`' . implode('`,`', $value) . '`';
case 'b':
foreach ($value as &$ref) {
$ref = $nullable && $ref === null
? 'NULL'
: ((bool) $ref ? 'TRUE' : 'FALSE');
}
return implode(',', $value);
case 'd':
foreach ($value as &$ref) {
$ref = $nullable && $ref === null
? 'NULL'
: (int)$ref;
}
return implode(', ', $value);
case 'f':
foreach ($value as &$ref) {
$ref = $nullable && $ref === null
? 'NULL'
: (float)$ref;
}
return implode(', ', $value);
case 's':
foreach ($value as &$ref) {
$ref = $nullable && $ref === null
? 'NULL'
: $this->c->real_escape_string($ref);
}
return "'" . implode("','", $value) . "'";
}
} else {
switch ($matches[3][0]) {
case '%':
return $this->replaceString($value);
case 'i':
return '`'
. implode(
'`.`',
explode('.', preg_replace('/`/u', '``', $value), 3)
)
. '`';
case 'b':
return $nullable && $value === null
? 'NULL'
: ((bool) $value ? 'TRUE' : 'FALSE');
case 'd':
return $nullable && $value === null
? 'NULL'
: (int)$value;
case 'f':
return $nullable && $value === null
? 'NULL'
: (float)$value;
case 's':
return $nullable && $value === null
? 'NULL'
: "'{$this->c->real_escape_string($value)}'";
}
}
} elseif (isset($matches[2])) {
return "`{$this->_tablePrefix}{$matches[2]}`";
} elseif (isset($matches[1])) {
return "`{$matches[1]}`";
}
return $matches[0];
}
public function connect()
{
if (isset($this->c)) {
return;
}
$this->c = mysqli_init();
$args = func_get_args();
@call_user_func_array(array($this->c, 'connect'), $args + $this->args);
if ($this->c->connect_error) {
$exception = new MySQL_Exception(
$this->c->connect_error,
$this->c->connect_errno
);
$this->c = null;
if ($this->_debug) {
$exception->debug();
}
throw $exception;
}
$this->c->set_charset($this->_defaultCharset);
}
public function selectDb($db)
{
$this->connect();
$this->c->select_db($db);
if ($this->c->error) {
$exception = new MySQL_Exception($this->c->error, $this->c->errno);
if ($this->_debug) {
$exception->debug();
}
throw $exception;
}
}
public function ping()
{
$this->connect();
return $this->c->ping();
}
public function close()
{
if (!isset($this->c)) {
return;
}
$this->free();
$return = $this->c->close();
$this->c = null;
return $return;
}
public function escapeString($value)
{
if (!isset($this->c)) {
$this->connect();
}
return $this->c->real_escape_string($value);
}
public function replaceString()
{
if (!isset($this->c)) {
$this->connect();
}
$args = func_get_args();
$query = array_shift($args);
if ($args) {
if (!isset($args[1])) {
$args = is_array($args[0]) ? $args[0] : array($args[0]);
}
if (isset($this->d)) {
$this->s[] = array($this->d, $this->i);
}
$this->d = $args;
$this->i = 0;
}
$result = preg_replace_callback(
'/\'(?>\\\\\'|\'\'|[^\']+)*\'|"(?>\\\\"|""|[^"]+)*"|`(?>``|[^`]+)*`|\[([^\s\]]+)\]|\<([^\s\>]+)\>|%(%|i|(?>s|b|d|f)n?)?(?>\[([^\]]+)\])?|%/u',
array($this, 'replaceStringCallback'),
$query
);
if ($args) {
if (!empty($this->s)) {
list($this->d, $this->i) = array_pop($this->s);
} else {
$this->d = null;
}
}
return $result;
}
public function query($query, $params = null)
{
$this->connect();
$this->free();
$result = $this->c->query($actualQuery = $this->replaceString($query, $params));
if ($result === false) {
$exception = new MySQL_Exception(
$this->c->error,
$this->c->errno,
$query,
$actualQuery
);
if ($this->_debug) {
$exception->debug();
}
}
if ($result instanceof mysqli_result) {
$this->r = $result;
return $this->_numRows = $this->_affectedRows = $this->c->affected_rows;
}
$this->_numRows = 0;
return $this->_affectedRows = $this->c->affected_rows;
}
public function queryAndFetch($query, $params = null, $columnKey = null)
{
$this->query($query, $params);
$row = $this->fetch($columnKey);
$this->free();
return $row;
}
public function queryAndFetchAll($query, $params = null, $columnKey = null, $indexKey = null)
{
$this->query($query, $params);
return $this->fetchAll($columnKey, $indexKey);
}
public function queryValue($query, $params = null)
{
if ($this->query($query, $params)) {
$row = $this->fetch();
$this->free();
return $row[key($row)];
}
}
public function fetch($columnKey = null)
{
if (!isset($this->r)) {
return;
}
if (!($row = $this->r->fetch_assoc())) {
$this->free();
} else {
return isset($columnKey)
? (isset($row[$columnKey])
? $row[$columnKey]
: null)
: $row;
}
}
public function fetchAll($columnKey = null, $indexKey = null)
{
if (!isset($this->r)) {
return;
}
$rows = array();
if (($row = $this->r->fetch_assoc())) {
if (isset($columnKey)) {
if (array_key_exists($columnKey, $row)) {
if (isset($indexKey) && array_key_exists($indexKey, $row)) {
do {
$rows[$row[$indexKey]] = $row[$columnKey];
} while (($row = $this->r->fetch_assoc()));
} else {
do {
$rows[] = $row[$columnKey];
} while (($row = $this->r->fetch_assoc()));
}
}
} elseif (isset($indexKey) && array_key_exists($indexKey, $row)) {
do {
$rows[$row[$indexKey]] = $row;
} while (($row = $this->r->fetch_assoc()));
} else {
do {
$rows[] = $row;
} while (($row = $this->r->fetch_assoc()));
}
}
$this->free();
return $rows;
}
public function free()
{
if (!isset($this->r)) {
return;
}
$this->r->free();
$this->r = null;
}
public function __get($name)
{
if (isset($this->{$prop = '_' . $name})) {
return $this->$prop;
}
if ($name === 'charset') {
$this->connect();
return $this->c->character_set_name();
}
static $map = array(
'error' => 'error',
'errno' => 'errno',
'insertId' => 'insert_id',
);
if (isset($map[$name])) {
return $this->c->{$map[$name]};
}
}
public function __set($name, $value)
{
if (isset($this->{$prop = '_' . $name})) {
$this->$prop = $name === 'debug'
? (bool)$value
: (string)$value;
}
if ($name === 'charset') {
$this->connect();
$this->c->set_charset($value);
if ($this->c->error) {
$exception = new MySQL_Exception($this->c->error, $this->c->errno);
if ($this->_debug) {
$exception->debug();
}
throw $exception;
}
}
}
}
class MySQL_Exception extends Exception
{
public static function formatMessage($message, $width = 100)
{
$width = max(40, (int)$width);
if (!preg_match_all('/^\t+/mu', $message, $matches)) {
$matches[0][0] = '';
}
sort($matches[0]);
$message = trim(
preg_replace(
'/\t/u',
' ',
preg_replace("/^{$matches[0][0]}/mu", '', $message)
)
);
if (preg_match_all("/\\n?(.{0,{$width}})/u", $message, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
$lines[] = $match[1];
}
return implode("\n", $lines);
}
return $message;
}
private $sourceQuery;
private $actualQuery;
public function __construct($message, $code, $sourceQuery = null, $actualQuery = null)
{
parent::__construct((string)$message, (int)$code);
$this->sourceQuery = $sourceQuery;
$this->actualQuery = $actualQuery;
}
public function getDebugMessage($width = 100)
{
$hr = str_repeat('-', $width = max(40, (int)$width));
return self::formatMessage(
isset($this->sourceQuery)
? sprintf(
"{$hr}\nMySQL error:\n{$hr}\n\n#%s - %s\n\n{$hr}\nSource query:\n{$hr}\n\n%s\n\n{$hr}\nActual query:\n{$hr}\n\n%s\n\n{$hr}\nTrace:\n{$hr}\n\n%s",
$this->getCode(),
$this->getMessage(),
$this->sourceQuery,
$this->actualQuery,
$this->getTraceAsString()
)
: sprintf(
"{$hr}\nMySQL error:\n{$hr}\n\n#%s - %s\n\n{$hr}\nTrace:\n{$hr}\n\n%s",
$this->getCode(),
$this->getMessage(),
$this->getTraceAsString()
),
$width
);
}
public function debug($width = 100)
{
if (!headers_sent()) {
header('Content-Type: text/plain; charset=utf-8', true);
while (ob_get_level()) {
ob_end_clean();
}
}
exit($this->getDebugMessage($width));
}
}
|
|
|
|
|
|