Sub-Domain or Many File in Page Caching with CodeIgniter

คราวก่อนเขียนเรื่อง Page Caching with CodeIgniter and Cache Clearing code เป็นการเกริ่นถึงวิธีใช้งาน Page Caching ของ Codeigniter รวมไปถึงผมได้แนบคลาสสำหรับการ ลบแคช (Cache Clearing) เข้าไปด้วย เพื่อความสะดวกในการใช้งาน

บทความนี้เลยคล้ายๆ กับบทต่อจากเรื่องข้างต้นนะครับ เป็นกรณีศึกษาที่ผมเจอและจำเป็นต้องใช้ เลยมาขอแชร์ความรู้นิดหนึ่ง เผื่อมีใครมีแนวทางที่ดีกว่าของผมนะครับ

Mini Introduction Page Caching with CodeIgniter

เกริ่นพื้นฐานเล็กน้อยครับ ปกติการทำ Page Caching ใน Codeigniter เราจะเก็บลงโฟลเดอร์หนึ่ง สมมุติว่าชื่อ "system/cache/" นะครับ ส่วนชื่อไฟล์ที่ทำการ Cache ก็จะนำเอา URI(หรือ URL) มาเข้ารหัสด้วย md5 เช่น "http://lab.tosdn.com" จะถูกเข้ารหัสและนำไปตั้งชื่อไฟล์ คือ "53db6ab6193a34a4ebac45ebd7592da6"

ถ้าไม่เข้าใจ ไปอ่านเต็มๆ ที่บทความเก่าครับ - Page Caching with CodeIgniter and Cache Clearing code

Problem of Page Caching with CodeIgniter

คราวนี้ ปัญหาที่ผมเจอมี 2 ข้อครับ

1. หากการทำ Caching มีไฟล์มากมายมหาศาลจะทำอย่างไร? ซึ่งปํญหานี้อาจจะไม่ค่อยสำคัญ หากเว็บเราไม่ได้คิดการใหญ่ และหน้าเพจไม่มากนัก

2. ถ้าเราเปิดให้สมาชิกมี Sub-Domain ได้เอง แปลว่า ในเว็บเราจะเสมือนมีเว็บสมาชิกย่อยลงไปอีก คราวนี้ ปัญหาที่หนีไม่พ้นก็คือ ปัญหาในข้อ 1 และ ปัญหาที่สำคัญตามมาคือ การบริหารจัดการไฟล์ Caching ที่ยากขึ้น เช่น สมาชิกคนนี้เปลี่ยนแปลงชื่อใน profile ตัวเอง ดังนั้น หน้าใน Sub-Domin ต้องมีการเปลี่ยนแปลงด้วย เช่นนั้นแล้วเราต้องมาทำการค้นหาว่า Cacgin Page ของสมาชิกคนนี้ มีไฟล์ไหนบ้าง แค่คิดก็สนุกแล้วครับ T-T

หรือบางคนไม่ได้ใช้เป็น Sub-Domain แต่ใช้เก็บค่าสมาชิกใน Session หรือ Cookie หรือ อื่นๆ แต่ URI ยังใช้คงเดิม เช่น สมาชิก A,B,C ก็เข้าแก้ไขชื่อตนเองใน http://www.web.com/profile เหมือนกัน ดังนั้น Caching ที่เก็บ มันก็จะมีชื่อเหมือนกัน อยู่ที่ว่า Codeigniter ทำการ Generate Page Caching ให้คนไหนก่อน พอคนต่อมาเข้ามาใช้ มันก็จะเจอข้อมูลของคนแรกที่ถู Generate ไว้ มันช่างวุ่นวายได้อีก T-T

Solution of Page Caching with CodeIgniter

เมื่อเจอปัญหาแบบนั้น ผมเลยลองแก้ไขด้วยการจัดระเบียบโฟลเดอร์ของ Page Caching ให้ลึกลงไปอีก 2 ชั้นโดยชั้นแรกเก็บเป็นอักษรหน้า 3 ตัว และชั้นสองเก็บ อักษรเต็มของชื่อ Domain หรือ Sub-Domain ไว้ และไฟล์ข้างในโฟลเดอร์นี้ ก็จะเก็บเฉพาะ หน้านั้นๆของ Domain หรือ Sub-Domain ที่ระบุไว้เท่านั้น

เอาหละครับ เมื่อทราบที่มา ปัญหา และวิธีแก้ไข ก็ลงมือกันเลย :D

How to use Sub-Domain or Many File in Page Caching with CodeIgniter

1. สร้างไฟล์ Library ใน Application มาหนึ่งตัวครับ เช่น ปล. หากในบทความก่อน ใครสร้างไว้แล้วก็ข้ามไปครับ

/system/application/libraries/My_Output.php (ใครตั้ง Suffix ว่าอะไรก็ใส่แทน My ไปเลยครับ)

2. ทำการ Extend Output ของ CodeIgniter และใส่โค้ด ดังนี้ครับ ปล. หากในบทความก่อน ใครสร้างไว้แล้วก็ทับได้เลยครับ เพราะผมพ่วง clear_cache() มาในโค้ดนี้แล้ว

 if ( ! defined('BASEPATH')) exit('No direct script access allowed');
<br> /********<br> / TOSDN - URI from CI URI Library<br> / Update 12/12/2551 2:26<br> ********/</p> <p>/**<br> * Page Caching management class (extends from CI_Output)<br> *<br> *<br> * @package        CodeIgniter<br> * @subpackage    Libraries<br> * @hacked-by    Chitpong Wuttanan<br> */<br> class My_Output extends CI_Output {
    var $cache_name;
    <br> var $cache_expiration;
    /**<br> * Set Cache<br> *<br> * @access    public<br> * @param    integer<br> * @return    void<br> *//**<br> * Update/serve a cached file<br> *<br> * @access    public<br> * @return    void<br> */<br> function _display_cache(&$CFG, &$URI)<br> {
        <br> $cache_path = ($CFG->
        item('cache_path') == '') ? BASEPATH.'cache/' : $CFG->
        item('cache_path');
        if ( ! is_dir($cache_path) OR ! is_really_writable($cache_path))<br> {
            <br> return FALSE;
            <br>
        }
        $cache_topdomain_uri = md5($CFG->
        item('base_url'));
        <br> $cache_path_folder_uri = $cache_path."/".substr($cache_topdomain_uri, 0, 3)."/".$cache_topdomain_uri."/";
        // Build the file path.  The file name is an MD5 hash of the full URI<br> $uri =    $CFG->
        item('base_url').<br> $CFG->
        item('index_page').<br> $URI->
        uri_string;
        $filepath = $cache_path_folder_uri.md5($uri);
        if ( ! @file_exists($filepath))<br> {
            <br> return FALSE;
            <br>
        }
        if ( ! $fp = @fopen($filepath, FOPEN_READ))<br> {
            <br> return FALSE;
            <br>
        }
        flock($fp, LOCK_SH);
        $cache = '';
        <br> if (filesize($filepath) >
        0)<br> {
            <br> $cache = fread($fp, filesize($filepath));
            <br>
        }
        flock($fp, LOCK_UN);
        <br> fclose($fp);
        // Strip out the embedded timestamp<br> if ( ! preg_match("/(\d+TS--->
        )/", $cache, $match))<br> {
            <br> return FALSE;
            <br>
        }
        // Has the file expired? If so we'll delete it.<br> if (time() >
        = trim(str_replace('TS--->
        ', '', $match['1'])))<br> {
            <br> @unlink($filepath);
            <br> log_message('debug', "Cache file has expired. File deleted");
            <br> return FALSE;
            <br>
        }
        // Display the cache<br> $this->
        _display(str_replace($match['0'], '', $cache));
        <br> log_message('debug', "Cache file is current. Sending it to browser.");
        <br> return TRUE;
        <br>
    }
    /**<br> * Write a Cache File<br> *<br> * @access    public<br> * @return    void<br> */<br> function _write_cache($output)<br> {
        <br> $CI =& get_instance();
        <br> $path = $CI->
        config->
        item('cache_path');
        $cache_path = ($path == '') ? BASEPATH.'cache/' : $path;
        if ( ! is_dir($cache_path) OR ! is_really_writable($cache_path))<br> {
            <br> return;
            <br>
        }
        $cache_topdomain_uri = md5($CI->
        config->
        item('base_url'));
        <br> $cache_path_folder = $cache_path."/".substr($cache_topdomain_uri, 0, 3);
        <br> $cache_path_folder_uri = $cache_path_folder."/".$cache_topdomain_uri."/";
        if ( ! is_dir($cache_path_folder) OR ! is_really_writable($cache_path_folder))<br> {
            <br> @mkdir($cache_path_folder, 0755);
            <br> @mkdir($cache_path_folder_uri, 0755);
            <br>
        }
        if(empty($this->
        cache_name)) {
            <br> $uri = $CI->
            config->
            item('base_url').<br> $CI->
            config->
            item('index_page').<br> $CI->
            uri->
            uri_string();
            <br> $cache_path_folder_uri .= md5($uri);
            <br>
        }
        else {
            <br> $cache_path_folder_uri .= md5($this->
            cache_name);
            <br>
        }
        if ( ! $fp = @fopen($cache_path_folder_uri, FOPEN_WRITE_CREATE_DESTRUCTIVE))<br> {
            <br> log_message('error', "Unable to write cache file: ".$cache_path_folder_uri);
            <br> return;
            <br>
        }
        $expire = time() + ($this->
        cache_expiration * 60);
        if (flock($fp, LOCK_EX))<br> {
            <br> fwrite($fp, $expire.'TS--->
            '.$output);
            <br> flock($fp, LOCK_UN);
            <br>
        }
        <br> else<br> {
            <br> log_message('error', "Unable to secure a file lock for file at: ".$cache_path_folder_uri);
            <br> return;
            <br>
        }
        <br> fclose($fp);
        <br> @chmod($cache_path_folder_uri, DIR_WRITE_MODE);
        log_message('debug', "Cache file written: ".$cache_path_folder_uri);
        <br>
    }
    function clear_cache($set_uri = NULL, $all=FALSE){
        <br> $CFG =& load_class('Config');
        <br> $cache_topdomain_uri = md5($CFG->
        item('base_url'));
        <br> $cache_path_folder_uri = $CFG->
        item('cache_path')."/".substr($cache_topdomain_uri, 0, 3)."/".$cache_topdomain_uri;
        if($all == FALSE) {
            <br> $filepath = ($CFG->
            item('cache_path') == '') ? BASEPATH.'cache/' : $cache_path_folder_uri."/".md5($set_uri);
            <br> if(file_exists($filepath))<br> {
                <br> @unlink($filepath);
                <br> log_message('debug', "Cache deleted for: ".md5($set_uri)." (".$set_uri.")");
                <br>
            }
            else {
                <br> return FALSE;
                <br>
            }
            <br>
        }
        else {
            <br> $filepath = ($CFG->
            item('cache_path') == '') ? BASEPATH.'cache/' : $cache_path_folder_uri;
            <br> if ($handle = opendir($filepath)) {
                <br> while (($file = readdir($handle))  !== false) {
                    <br> if ($file != "." && $file != "..") {
                        <br> @unlink($filepath."/".$file);
                        <br>
                    }
                    <br>
                }
                <br> closedir($handle);
                <br> log_message('debug', "Cache deleted for: ".md5($set_uri)." (".$set_uri.")");
                <br>
            }
            else {
                <br> return FALSE;
                <br>
            }
            <br>
        }
        <br>
    }
    <br>
}

3. วิธีการนำไปใช้ (Usage) ใช้เหมือนเดิมทุกประการครับ :D

$this->output->cache(60); //1 Hour $this->load->view(’profile.php’);

ปิดท้ายด้วยการอธิบายโค้ดนิดหนึ่งนะครับ

โค้ดตัวนี้ผมได้ทำการ extend ไว้กับคลาส Output เดิม และทำการสร้าง Function ในชื่อเดิม เพื่อเรียกใช้แทน Function เดิม โดย

_display_cache() - จะทำหน้าที่เรียกการแสดงผล โดยตรวจสอบว่ามีการทำ Caching ไว้ไหม ถ้ามี และยังไม่หมดอายุ ตามเวลาที่กำหนด ก็จะเรียก Cache โดยตรวจสอบก่อนว่ามาจาก URI ไหน ให้เข้ารหัสด้วย md5และตัด 3 ตัวแรกออกมา เพื่อเข้าโฟลเดอร์ชั้นแรก จากนั้นเข้าโฟลเดอร์ชั้นสอง ด้วยรหัส md5 ตัวเต็ม จากนั้นจึงจะเจอไฟล์ที่มันจะตรวจสอบ แต่ถ้าไม่เจอ มันจะไปเรียก _write_cache() เพื่อสร้างไฟล์ใหม่ทันที รวมไปถึงสร้างโฟลเดอร์ให้อัตโนมัติด้วยนะครับ

_write_cache() - อันนี้ทำหน้าที่สร้างไฟล์ครับ โดยมีขั้นตอนเหมือน _display_cache() ครับ แต่ทำหน้าที่สร้างไฟล์ในโฟลเดอร์ที่เรากำหนดไว้

clear_cache() - เอาไว้ Clear Cache ครับ ตามที่เคยเขียนไว้ใน Page Caching with CodeIgniter and Cache Clearing code

ยังไงก็ลองเล่นดูนะครับ หากมีวิธีที่ดีกว่านี้ ก็เสนอได้ครับ :D

 

อ้างอิง : Sub-Domain or Many File in Page Caching with CodeIgniter

Comments

ความรู้ๆ

ขอบคุณครับสำหรับบทความดี

สำหรับผมเขียน PHP มาสองปี แต่เหมือนเวลาที่ผ่านมามันไม่มีความรู้อะไรเลย

เรื่องของการทำ caching หรือ orm ฯลฯ ก็เพิ่งรู้ว่ามันคืออะไรเอาตอนเริ่มหัดใช้ codeigniter Cry เพราะต้องพยายามหาความรู้เพิ่มเติมมากโครตๆ

หวังว่าความพยายามของผมกับ เพื่อนGoogle จะช่วยให้ผมสามารถเข้าใจอะไรได้มากกว่านี้

ปล.ใครมีโปรแกรมแปลอังกฤษเป็นไทยดีๆมั้งครับ แบบไม่ต้องสลับหน้าจอไปมาอะครับมันมึนหัว ภาษาอังกฤษผมก็โคตะระแข็งแรงเลย

ดีคร้าบ

ตัวผมเองก็เขียน php มาหลายปีเมหือนกัน แล้วก็ยังยังไม่เคยใช้ orm ซะด้วย อิอิ

และเจ้า page caching หรือ database caching เพิ่งมีโอกาสได้เล่นมาปีเดียวเองครับ

แต่รู้สึกว่ามันช่วยเราได้เยอะมากๆ เลยทีเดียว แต่ต้องวางโครงสร้างของการทำงานให้ดีๆ ถึงจะออกมาสมบูรณ์ครับ