Skip to content


Method Chaining ใน PHP ทำเองก็ได้ง่ายจัง

บทความนี้ได้แรงบันดาลใจมาจาก 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 ขึ้นไปนะครับ

ขอให้สนุกกับการเขียนโปรแกรมครับ

Share

Posted in jQuery, Object Oriented, PHP, Uncategorized.

Tagged with , , , .


15 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

  1. bAnk` says

    โอ้ จ๊อดดด
    ผมกำลัง นั่ง เขียน class database อยู่เลย

    ผมเพิ่งหัดเขียน oop ได้ไม่นานมานี้เอง
    ได้เห็นอะไรแบบนี้แล้ว ได้ ไอเดียเยอะแยะเลย

    แต่….
    เท่าที่ผมอ่านหนังสือดู เค้าบอกว่า ชื่อ constructor ต้องเป็นชื่อเดียวกับ Class ไม่ใช่เหรอครับ

    อธิบายทีครับ อยากได้ความรู้มากระแทกสมองอ่ะ

  2. bAnk` says

    ลอง test ดูแล้วล่ะครับ
    มานไม่ยอม query ข้อมูลออกมาอ่ะ งง

    ออกมาแต่

    array()

    แต่รูปแบบ แล้ะวิธีการใช้ สุดยอดเลยครับ

    ขอบคุณคับ

  3. chonla says

    ส่วนตัวแล้วผมเคยเห็น constructor มา 2 รูปแบบ คือ ชื่อเดียวกับคลาส และแบบที่ผมเขียน ลองดูใน php reference ก็ยังเห็นว่าเขียนได้ทั้ง 2 แบบ ไม่มีแบบไหนที่ยกเลิก ที่สำคัญ ผมจะเขียน destructor ด้วย ถ้าเขียนแบบชื่อคลาส ผมยังหาวิธีเขียน destructor ไม่เจอ ผมเลยเขียนแบบนี้ครับ

  4. chonla says

    เหรอครับ พอดี code ที่ผมแปะไว้ เป็นเวอร์ชั่นตัดทอนจากที่ผมเอามาใช้งานน่ะครับ เลยไม่แน่ใจว่าได้ตัดอะไรออกไปเกินหรือเปล่า อันที่ผมใช้ จะมี update, delete ด้วย พอดีอันนี้เห็นว่าเป็นตัวอย่าง เลยตัดออกละกัน เดี๋ยวจะยาวเกิน เดี๋ยวยังไงผมจะเช็คให้อีกทีละกันครับ

  5. Dr.Yes says

    การ connect น่าจะเอาไปไว้ตั้งแต่ตอน construct ตัวออปเจกเลยดีกว่ามั้ง

    เพราะถ้าเอามาเข้าเชนด้วย เกิดลืมเรียกไปล่ะก็จะงง

  6. chonla says

    เป็นแค่ตัวอย่างให้ดูแนวความคิดครับ ตอนผมเอามาใช้จริง ก็ใส่ตั้งแต่ตอน connect แล้วล่ะครับ

  7. chonla says

    ท่าน bank ครับ ผมลองทดสอบแล้วนะครับ ใช้งานได้ไม่มีปัญหาอะไรครับ

    ปล. ผมเอา version ที่เอาเรียก connect ตั้งแต่ constructor มาใช้แทนแล้วนะครับ ขอบคุณท่านรุ่งที่ทักมาครับ

  8. bAnk` says

    อ่อ มันเป็นแบนี้ นี่เองอ่ะ

    เดี๋ยวผมจาไปลอง เขียนของผม ดูมั่ง แต่ คงกำหนด username password ตั้งแต่ ตอนประกาศ New Object อ่ะ อิอิ

    ขอบคุณมากครับ

  9. เต้ says

    # private $link;
    #
    # private $table;
    # private $field;
    # private $condition;
    # private $order;
    # private $result;

    คือกำหนด ให้ค่านี้ใช้ เป็นค่าที่จะส่งไปมาระหว่างฟังก์ชั่นอันนี้เข้าใจถูกหรือป่าวครับ แล้วมันจำเป็นต้องกำหมดหรือป่าวครับ ผมเห็นบางทีเขาใส่ใน __construct อันนี้เหมือนหรือต่างกันอย่างไรครับ ลบกวนพี่ๆช่วยอธิบายทีครับ

  10. chonla says

    ^
    ^
    ผมกำหนดให้เป็น private คือ จะไม่มีอะไรมาเรียกใช้งานมันได้ นอกจาก object มันเองครับ ผมประกาศไว้แบบนี้ เพื่อให้มันอ้างถึงได้จากทุก ๆ method ใน class นี้ครับ และปกติแล้ว จะกำหนดค่าเริ่มต้นให้มันใน __constructor ครับ ผมไม่แน่ใจว่า เราสามารถกำหนดค่าเริ่มต้นให้ตอนที่ประกาศตัวแปรแบบ

    private $order = “”;

    ได้เลยหรือเปล่า ไม่เคยลองครับ แต่ผมทำแบบนี้ เพราะติดมาตอนสมัยเขียน c++ ครับ

  11. golfer007 says

    constructor ต้องเป็นชื่อเดียวกับ Class ไม่ใช่เหรอครับ
    ^
    ^
    ^
    ใน PHP5 จะใช้ __construct() เป็น constructor แต่หากไม่มี จะหาฟังก์ชันที่มีชื่อเดียวกับคลาสแทนครับ (PHP4 ต้องใช้ฟังก์ชันชื่อเดียวกับคลาสเป็น constructor เท่านั้น)

  12. golfer007 says

    อ้อ อีกอย่าง

    __destructor() มีเฉพาะใน PHP5 นะครับ

    ถ้า PHP4 ต้องพลิกแพลงใช้ฟังก์ชัน register_shutdown_function() เอาครับ

  13. chonla says

    ขอบคุณสำหรับความคิดเห็นครับ ผมเลยได้ความรู้ไปด้วยครับ

  14. dixon says

    ผมขอ class Database ตัวเต็มได้มั้ยครับ อยากได้ไปลองศึกษาอะครับ เพิ่งลองเขียนครับ

  15. Genetic says

    ขอบคุณมาก ๆ เลยนะครับ

    ได้แนวความคิดใหม่ ๆ อีกเยอะเลย แล้วจะมาขอเก็บความรู้บ่อย ๆ นะครับ



Some HTML is OK

or, reply to this post via trackback.

*