บทความนี้ได้แรงบันดาลใจมาจาก chainability ของ jQuery และ query builder ของ kohana (จริง ๆ ผมเข้าใจว่า query builder มีอยู่ใน php framework ตัวอื่นด้วย แต่พอดีท่านรุ่งจากบ้าน PHP.DeeServer.net ซึ่งแนะนำให้ผมรู้จักกับ kohana เค้าแนะนำตัวที่อยู่ใน kohana มาครับ)
ไอเดียหลัก ๆ ของการทำ chaining ก็คือ การทำให้มันสามารถเรียกต่อกันได้เป็นทอด ๆ นั่นเอง ยกตัวอย่างใน jQuery ซึ่งมีคุณสมบัติที่เรียกว่า chainability อยู่ ทำให้เราสามารถเรียกคำสั่งได้แบบนี้
$("#submenu").addClass("whitemenu").show("slow");
จะเห็นได้ว่า เราสามารถเรียก .show ต่อเนื่องได้เลย นี่เองที่ทำให้เกิดการเรียกอย่างต่อเนื่อง หรือ การทำ Method chaining นั่นเอง
ใน PHP เราสามารถทำให้ object มีคุณสมบัติเดียวกันนี้ได้เช่นกัน เพียงแต่น่าเสียดายที่คุณสมบัตินี้ มีอยู่ใน PHP ตั้งแต่เวอร์ชั่น 5 ขึ้นไปเท่านั้น สำหรับคนที่ยังใช้ PHP4 อยู่ ผมต้องขอแสดงความเสียใจด้วยครับ ฮ่า ๆ และคาดว่าเหตุนี้เองที่ทำให้ kohana สามารถใช้งานได้กับ PHP5 ขึ้นไปเท่านั้น
หลักการง่าย ๆ ที่ทำให้เราสามารถทำ Method chaining ได้ก็คือ การเขียน class และทำการคืนค่าของ object มันเองกลับออกมาหลังจากที่จบ method ทำนองเดียวกับใน jQuery ตอนที่เขียน plugin
ตอนจบ เราก็จะทำการ return jQuery object กลับออกมาให้เช่นเดียวกัน
ลองมาดูตัวอย่างใน PHP กันดีกว่าครับ
// php5 only
class Database
{
private $link;
private $table;
private $field;
private $condition;
private $order;
private $result;
// connection functions
function __construct($config)
{
$this->flush();
$this->link = null;
$this->connect($config["hostname"],
$config["username"],
$config["password"],
$config["database"]);
}
function __destruct()
{
$this->disconnect();
}
function connect($host = "",
$user = "",
$password = "",
$db = "")
{
if ($this->link !== null)
$this->disconnect();
$this->link = mysql_connect($host, $user, $password);
if ($this->link === FALSE)
{
echo $this->err();
}
else
{
if ($db !== "")
{
return $this->db($db);
}
}
return $this;
}
function disconnect()
{ // break chain
if ($this->link !== null)
mysql_close($this->link);
$this->link = null;
}
function db($db)
{
if (mysql_select_db($db, $this->link) === FALSE)
echo $this->err();
return $this;
}
function errno()
{
return mysql_errno($this->link);
}
function ok()
{
return ($this->errno() == 0);
}
function err()
{ // break chain
return mysql_error($this->link);
}
function execute($sql)
{
$this->result = mysql_query($sql, $this->link);
return $this;
}
function records()
{
$result = array();
if ($this->result !== FALSE)
{
while (($row = mysql_fetch_assoc($this->result)) !== FALSE)
{
$result[] = $row;
}
}
return $result;
}
function flush()
{
$this->table = "";
$this->field = "";
$this->condition = "";
$this->order = "";
$this->result = null;
return $this;
}
function safe($value)
{
if (!get_magic_quotes_gpc())
{
$value = addslashes($value);
}
return "'$value'";
}
function table($table)
{
$this->table = $table;
return $this;
}
function field($field)
{
$this->field = $field;
return $this;
}
function condition($condition)
{
if (is_array($condition))
{
$cond = array();
foreach ($condition as $f => $v)
{
$cond[] = "($f = " . $this->safe($v) . ")";
}
$condition = implode(" AND ", $cond);
}
$this->condition = $condition;
return $this;
}
function top($num = 1)
{
$field = $this->field;
if ($field == "") $field = "*";
$sql = "SELECT {$field} FROM {$this->table}";
if ($this->condition !== "")
$sql .= " WHERE {$this->condition}";
if ($this->order !== "")
$sql .= " ORDER BY {$this->order}";
$sql .= " LIMIT 0, {$num}";
return $this->execute($sql)->records();
}
function all()
{
$field = $this->field;
if ($field == "") $field = "*";
$sql = "SELECT {$field} FROM {$this->table}";
if ($this->condition !== "")
$sql .= " WHERE {$this->condition}";
if ($this->order !== "")
$sql .= " ORDER BY {$this->order}";
return $this->execute($sql)->records();
}
function order($order)
{
$this->order = $order;
return $this;
}
}
code ด้านบนนี้ เป็น code ที่ผมทดสอบ โดยทำเป็นไอเดียเริ่มต้นง่าย ๆ สำหรับการทำ query builder จะเห็นได้ว่า หลาย ๆ method จะมีการ return ตัว object มันเอง ($this) ไอเดียหลักในการกำหนดว่า method ไหนที่ผม return หรือไม่ return $this ก็คือ มีความจำเป็นในการเรียก method ต่อจากนี้หรือไม่นั่นเอง
การใช้งานก็ไม่ได้ยุ่งยากอะไร
$dbconf = array(
"hostname" => "localhost",
"username" => "root",
"password" => "123456",
"database" => "test"
);
$db = new Database($dbconf);
$rows = $db->table("products")
->field("product_id, product_name")
->condition(array(
"product_group"=>"tyre",
"product_model"=>"Eagle"))
->top();
print_r($rows);
จากตัวอย่างด้านบน เป็นการ select หา product จาก table products โดยกำหนด criteria ในการค้นหาเป็น product_group = ‘tyre’ และ product_model = ‘Eagle’ โดยจะเลือกเอาเฉพาะ record แรกสุดเท่านั้น
เห็นมั๊ยครับ หลักการง่าย ๆ แค่ return $this เองครับ ที่สำคัญ อย่าลืมนะครับ PHP5 ขึ้นไปนะครับ
ขอให้สนุกกับการเขียนโปรแกรมครับ
โอ้ จ๊อดดด
ผมกำลัง นั่ง เขียน class database อยู่เลย
ผมเพิ่งหัดเขียน oop ได้ไม่นานมานี้เอง
ได้เห็นอะไรแบบนี้แล้ว ได้ ไอเดียเยอะแยะเลย
แต่….
เท่าที่ผมอ่านหนังสือดู เค้าบอกว่า ชื่อ constructor ต้องเป็นชื่อเดียวกับ Class ไม่ใช่เหรอครับ
อธิบายทีครับ อยากได้ความรู้มากระแทกสมองอ่ะ
ลอง test ดูแล้วล่ะครับ
มานไม่ยอม query ข้อมูลออกมาอ่ะ งง
ออกมาแต่
array()
แต่รูปแบบ แล้ะวิธีการใช้ สุดยอดเลยครับ
ขอบคุณคับ
ส่วนตัวแล้วผมเคยเห็น constructor มา 2 รูปแบบ คือ ชื่อเดียวกับคลาส และแบบที่ผมเขียน ลองดูใน php reference ก็ยังเห็นว่าเขียนได้ทั้ง 2 แบบ ไม่มีแบบไหนที่ยกเลิก ที่สำคัญ ผมจะเขียน destructor ด้วย ถ้าเขียนแบบชื่อคลาส ผมยังหาวิธีเขียน destructor ไม่เจอ ผมเลยเขียนแบบนี้ครับ
เหรอครับ พอดี code ที่ผมแปะไว้ เป็นเวอร์ชั่นตัดทอนจากที่ผมเอามาใช้งานน่ะครับ เลยไม่แน่ใจว่าได้ตัดอะไรออกไปเกินหรือเปล่า อันที่ผมใช้ จะมี update, delete ด้วย พอดีอันนี้เห็นว่าเป็นตัวอย่าง เลยตัดออกละกัน เดี๋ยวจะยาวเกิน เดี๋ยวยังไงผมจะเช็คให้อีกทีละกันครับ
การ connect น่าจะเอาไปไว้ตั้งแต่ตอน construct ตัวออปเจกเลยดีกว่ามั้ง
เพราะถ้าเอามาเข้าเชนด้วย เกิดลืมเรียกไปล่ะก็จะงง
เป็นแค่ตัวอย่างให้ดูแนวความคิดครับ ตอนผมเอามาใช้จริง ก็ใส่ตั้งแต่ตอน connect แล้วล่ะครับ
ท่าน bank ครับ ผมลองทดสอบแล้วนะครับ ใช้งานได้ไม่มีปัญหาอะไรครับ
ปล. ผมเอา version ที่เอาเรียก connect ตั้งแต่ constructor มาใช้แทนแล้วนะครับ ขอบคุณท่านรุ่งที่ทักมาครับ
อ่อ มันเป็นแบนี้ นี่เองอ่ะ
เดี๋ยวผมจาไปลอง เขียนของผม ดูมั่ง แต่ คงกำหนด username password ตั้งแต่ ตอนประกาศ New Object อ่ะ อิอิ
ขอบคุณมากครับ
# private $link;
#
# private $table;
# private $field;
# private $condition;
# private $order;
# private $result;
คือกำหนด ให้ค่านี้ใช้ เป็นค่าที่จะส่งไปมาระหว่างฟังก์ชั่นอันนี้เข้าใจถูกหรือป่าวครับ แล้วมันจำเป็นต้องกำหมดหรือป่าวครับ ผมเห็นบางทีเขาใส่ใน __construct อันนี้เหมือนหรือต่างกันอย่างไรครับ ลบกวนพี่ๆช่วยอธิบายทีครับ
^
^
ผมกำหนดให้เป็น private คือ จะไม่มีอะไรมาเรียกใช้งานมันได้ นอกจาก object มันเองครับ ผมประกาศไว้แบบนี้ เพื่อให้มันอ้างถึงได้จากทุก ๆ method ใน class นี้ครับ และปกติแล้ว จะกำหนดค่าเริ่มต้นให้มันใน __constructor ครับ ผมไม่แน่ใจว่า เราสามารถกำหนดค่าเริ่มต้นให้ตอนที่ประกาศตัวแปรแบบ
private $order = “”;
ได้เลยหรือเปล่า ไม่เคยลองครับ แต่ผมทำแบบนี้ เพราะติดมาตอนสมัยเขียน c++ ครับ
constructor ต้องเป็นชื่อเดียวกับ Class ไม่ใช่เหรอครับ
^
^
^
ใน PHP5 จะใช้ __construct() เป็น constructor แต่หากไม่มี จะหาฟังก์ชันที่มีชื่อเดียวกับคลาสแทนครับ (PHP4 ต้องใช้ฟังก์ชันชื่อเดียวกับคลาสเป็น constructor เท่านั้น)
อ้อ อีกอย่าง
__destructor() มีเฉพาะใน PHP5 นะครับ
ถ้า PHP4 ต้องพลิกแพลงใช้ฟังก์ชัน register_shutdown_function() เอาครับ
ขอบคุณสำหรับความคิดเห็นครับ ผมเลยได้ความรู้ไปด้วยครับ
ผมขอ class Database ตัวเต็มได้มั้ยครับ อยากได้ไปลองศึกษาอะครับ เพิ่งลองเขียนครับ
ขอบคุณมาก ๆ เลยนะครับ
ได้แนวความคิดใหม่ ๆ อีกเยอะเลย แล้วจะมาขอเก็บความรู้บ่อย ๆ นะครับ