PDO กับฐานข้อมูล

ในการใช้ 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 จะคอยจัดการเชื่อมต่อ และเตรียมข้อมูลที่คิวรี่ได้มาให้โดยอัตโนมัติ ซึ่งง่าย สะดวก และปลอดภัยกว่า

blog comments powered by Disqus