How to check array isset and is_null in PHP

ถ้า value ของ Array ช่องนึงเป็น NULL เวลาที่เช็คด้วยคำสั่ง isset มันจะได้ false ดังตัวอย่างนี้

<?php
    $arr = array();
    $arr['foo'] = NULL;
    var_dump( isset( $arr['foo'] ); // ให้ผลลัพท์เป็น bool(false)
?>

วิธีที่จะเช็คว่า Array มีการกำหนด key นั้นอยู่ และค่าของมันเป็น NULL ด้วย ให้ใช้คำสั่ง array_key_exists ดังตัวอย่างนี้นี้
<?php
    if ( array_key_exists( 'foo', $arr ) && is_null( $arr['foo'] ) )
        echo "OK";
?>

Modifying timthumb.php in wordpress’s theme for optimize performance

ปรกติแล้วใน timthumb.php จะมีความสามารถในการ cache รูปที่ถูกย่ออยู่แล้ว เพื่อไม่ให้มันต้อง crop รูปสำหรับทุกๆ request. แต่ว่าปัญหา คือ ไฟล์ cache ของ timthumb นั้น มันไม่ใช่ไฟล์รูปที่สามารถเปิดดูได้โดยตรง เพราะว่าที่ส่วนหัวของ cache จะมี filePrependSecurityBlock แทรกอยู่เพื่อป้องกันการส่งสคริปท์ php เข้าไปรัน. จากลักษณะการทำงานที่เป็นชี้นี้ ทำให้เราไม่สามารถใช้พวก Reverse proxy หรือ CDN มาช่วยในการลดโหลดได้ของ WordPress ที่มีจำนวน Traffic สูงๆ ได้ (ไม่งั้นมันเรียก php ขึ้นมาทำงานตลอด). Continue reading “Modifying timthumb.php in wordpress’s theme for optimize performance”

ปัญหา Query หนักใน plugin post-views ของ WordPress

วันก่อนเซิร์ฟเวอร์เครื่องหนึ่งเกิดปัญหาโหลดขึ้นสูงอย่างหนัก จึงได้เข้าไปตรวจสอบ

พบว่าสาเหตุเกิดจาก เว็บไซต์ลูกค้ารายหนึ่ง ใช้ WordPress และมีคนเข้าจำนวนมาก เค้าได้ติดตั้ง plugin ที่มีชื่อ post-views เข้าใจว่ามันจะทำการนับ stats และแสดงรายการ posts ที่มีคนเข้าชมเยอะ ไรประมาณนั้น

ใน Query ที่ plugin มันเขียน มันใช้ SELECT ใหม่ทุก โดยใช้ WHERE ระบุเป็นเงื่อนไขเวลาที่ละเอียดเป็นวินาทีเลย.  ผมเลยลองเข้าไปแก้ให้ Query มัน WHERE โดยใช้เวลาเป็น floor ที่ interval=20 minutes  เพราะตอนแรกเดาว่ามันน่าจะได้ประโยชน์จาก MySQL query cache  แต่ปรากฎว่าไม่ช่วยแฮะ ไม่รู้ว่า result มันขนาดเกิน cache limit หรือเปล่า

ผมเลยแก้ให้มันเขียน result ลงไฟล์แทน โดยใช้ serialize

ต้องสร้างโฟลเดอร์ /wp-content/plugins/post-views/cache/ และเปิดสิทธิ์ให้ php เขียนไฟล์ในโฟลเดอร์นี้ได้ด้วย

อันนี้เป็นไฟล์ที่ผมแก้แล้ว  แก้ 2 จุดครับ ประมาณบรรทัดที่ 2243 และ 2412 ครับ (ดาวน์โหลด post-views.php)

How to enable using the $_GET variable in CodeIgniter

คงจะทราบดีว่า ถ้าเราตั้ง $config[‘enable_query_strings’] = FALSE ตัว CodeIgniter มันจะ unset ตัวแปร $_GET ของเราซะงั้น  ครั้งแรกที่ผมแก้ ผมเข้าไปแก้ที่ core ของ CodeIgniter เลยครับ  แต่วันนี้ผมคิดเทคนิคดีๆ ได้แล้ว เลยอยากบอกต่อครับ

ให้เข้าไปตั้งค่าในไฟล์ config.php ดังนี้ครับ

Continue reading “How to enable using the $_GET variable in CodeIgniter”

Reset value of _fail_gracefully (avoid recursively call controller)

Wick Ref: http://codeigniter.com/wiki/Wick/

ผมเจอปัญหาว่าเวลามีการเรียก  $this->load->uri ( … ); ที่ไม่มีอยู่จริง ปรกติแล้วควรจะขึ้น Error ว่าไม่พบ Controller หรืออะไรประมาณนั้น แต่ปัญหาคือเมื่อเรียก controller ที่ไม่มีอยู่จริงแล้ว จะทำให้มันพยายาม load controller ตัวเดิมไปเรื่อยๆ แบบ Recusive.  คิดว่าน่าจะเกิดเฉพาะกรณีที่ คุณเข้าถึง controller หลักโดยไม่ได้ระบุ controller  (หมายถึง มันเข้า default controller) เพราะจะไปมีผลกับตัวแปร  _fail_gracefully ครับ ซึ่งจะกลายเป็น true เมื่อมีการดึง config ตรง default controller มาใช้. วิธีแก้ก็ไม่ยากครับ ตามข้างล่างเลย!

/system/application/libraries/Wick.php

        $router = &load_class('Router');
        $uricla = &load_class('URI');
        $router->_fail_gracefully = false; // <-- Insert this Line
        $router->_set_request($uri);

Wick 0.8 Bug with Model & non-core Library

Wick Ref: http://codeigniter.com/wiki/Wick/

หลังจากที่สู้รบปรบมือกับปัญหานี้อยู่นาน ผมก็เจอสาเหตุที่แท้จริงจนได้ครับ  และสามารถแก้ไขได้ด้วยครับ งานนี้ถึงเหนื่อยก็คุ้มครับ

ปัญหา

เมื่อมีการเรียก $this->load->uri( …. ); จะทำให้พวก Model และ Non-core Library หายไปครับ (อาจรวมไปถึง plugin ด้วย). บางคนอาจจะเข้าใจว่ามันไม่ถูกโหลดเข้าไปใน Controller ตัวที่ถูกเรียกเท่านั้น, แต่จริงๆ แล้วนอกจากจะไม่ถูกโหลดเข้าไปแล้ว ยังทำให้สิ่งเหล่านี้ที่อยู่ที่ Controller หลักหายไปด้วยครับ. ทำให้ไม่สามารถใช้งาน Model, Non-core Library และ Plugin ได้ตามปรกติ ถ้ามีการใช้งาน Load URI ของ Wick

การหาสาเหตุ

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

ตอนแรกผมพยายามไล่ดูใน Wick ครับ เพราะเข้าใจว่า Wick คงโหลดบางอย่างไม่ครบ แต่ก็ไม่เจออะไรครับ เพราะพอหลังจากอ่านโค๊ดอยู่สักพัก ก็เข้าใจว่า Wick เป็นเพียง Library ที่มาเสริมโดยเพิ่ม method uri ใน Loader เฉยๆ ครับ. อย่างที่บอกครับว่าผมพบว่า Model และ Non-core Library มันหายไปจาก Controller หลักด้วย, เคสนี้เป็นเคสที่สำคัญเลยครับ เพราะใน Wick ไม่มีโค๊ดส่วนไหนที่ไปกระทบกับตัว Controller หลักที่อยู่ข้างนอกเลยครับ  (จริงๆ แล้วมันกระทบครับ กระทบครั้งเดียวตอนโหลดครั้งแรก ซึ่งไม่มีผลอยู่แล้ว)

หลังจากนั้นผมก็ลองเช็คไปเรื่อยๆ ก็ไม่เจออะไรที่น่าสนใจเลยครับ จนเกือบจะถอดใจแล้ว..  แต่เปลี่ยนใจลองกลับไปไล่อ่าน Wick ใหม่.. คราวนี้ผมมาหยุดตรงบรรทัดที่มีการสร้าง controller ครับ ประมาณ $controller = new $class(); อะไรประมาณนี้. และก็อย่างที่คาดการครับ บรรทัดนี้คือตัวปัญหาจริงๆ  ก็เลยต้องไล่ต่อเข้าไปข้างในครับ ว่าทำอะไรบ้าง และสุดท้ายก็เจอจนได้ครับ

สาเหตุ

เนื่องจาก CodeIgniter ถูกออกแบบมาสำหรับการใช้งานเพียง 1 Controller ต่อการเรียกครั้งนึงครับ. โดยเอาทุกอย่างมาเชื่อมเข้ากับ Controller ตามหลักของ Singleton ครับ ประเด็นคือใน code ของ Base4 และ Base5 จะทำการนำเอา Controller ที่สืบทอดมาจากมันทับเข้าไปที่ตัว Object กลางเลยครับ ซึ่งตรงนี้เลยเกิดปัญหาครับ เพราะว่า Controller ที่ถูกเรียกจาก Wick จะมีเพียง Core Library ที่เกิดจากขั้นตอนการ initialize เท่านั้นครับ  แล้วก็ถูกจับไปวางทับ Object กลางซึ่งมีการโหลด Model หรือ Non-core Library เอาไว้. Model กับ Non-core Library เลยหายไปครับ

วิธีแก้ไข

ผมใช้แนวคิดเดียวกับของ Wick ครับ  นั้นก็คือการ merge! ครับ  ผมเข้าไปแก้ไขใน Base4 และ Base5 โดยให้ทำการ merge Controller เข้ากับ Object กลางครับ แทนที่จะไปวางทับเอาดื้อๆ. การ merge ตรงนี้ผมไม่แน่ใจว่าจะส่งผลกระทบอย่างไร ต่อการทำงานในระบบใหญ่บ้าง เพราะไม่แน่ใจว่า จะมีกรณีที่บาง Module ใช้ชื่อ Properties เดียวกันหรือไม่ และอาจทำให้เกิดปัญหาได้ แต่จาการคาดคะเนโอกาสที่จะเกิดปัญหานั้นมันก็น้อยครับ. แต่ถึงอย่างนั้นก็ตาม ถ้าหลีกเลี่ยงเรื่องการใช้ชื่อซ้ำซ้อนก็จะดีครับ.

/system/codeigniter/Base4.php

if (class_exists( config_item('subclass_prefix')."Loader" ))
{
    eval('
    class CI_Base extends '.config_item('subclass_prefix')."Loader".' {
        function CI_Base()
        {
            $CI =& get_instance();
            if (is_object($CI)) {
                foreach (array_keys(get_object_vars($CI)) as $attribute) {
                    $this->$attribute = &$CI->$attribute;
                }
            }

            // This allows syntax like $this->load->foo() to work
            parent::'.config_item('subclass_prefix')."Loader".'();
            $this->load =& $this;

            // This allows resources used within controller constructors to work
            global $OBJ;
            $OBJ = $this->load; // Do NOT use a reference.
        }
    }
    ');
}
else
{
    class CI_Base extends CI_Loader {
        function CI_Base()
        {
            $CI =& get_instance();
            if (is_object($CI)) {
                foreach (array_keys(get_object_vars($CI)) as $attribute) {
                    $this->$attribute = &$CI->$attribute;
                }
            }

            // This allows syntax like $this->load->foo() to work
            parent::CI_Loader();
            $this->load =& $this;

            // This allows resources used within controller constructors to work
            global $OBJ;
            $OBJ = $this->load; // Do NOT use a reference.
        }
    }
}

/system/codeigniter/Base5.php

class CI_Base {

    private static $instance;

    public function CI_Base()
    {
        if (isset(self::$instance)) {
            foreach (array_keys(get_object_vars(self::$instance)) as $attribute) {
                $this->$attribute = &self::$instance->$attribute;
            }
        }
        self::$instance =& $this;
    }

    public static function &get_instance()
    {
        return self::$instance;
    }
}

หมายเหตุ: ไฟล์ Base4.php ผมใส่สีเฉพาะความแตกต่างจากการแก้ไขเดิมนะครับ ตามลิงค์นี้ (http://blog.kentreez.com/2008/07/22/codeigniter-my_loader-in-php4/)

พอแก้ไขบั๊กจุดนี้ได้แล้ว Framework Bug Free ก็ใกล้ความจริงเข้ามาแล้วครับ

Wick 0.8 in PHP4

Wick Ref: http://codeigniter.com/wiki/Wick/
Matchbox Ref: http://code.google.com/p/matchbox/

ผมพบว่าใน php4 จะมีการพยายามโหลดคลาส Wick สองครั้งครับ
(ผมตั้ง config ให้ autoload libraries Wick)

จริงๆ แล้ว Zacharias Knudsen เค้าบอกว่า Wick มัน php4 compatible นะครับ. ทำให้ผมไม่แน่ใจว่าที่ผมเจอ Error Redeclare นี่เพราะผมแก้ Framework ไปหลายจุดหรือป่าว?

แต่ไม่มีปัญหาครับ Error Redeclare เรื่องเล็กนิดเดียวครับ ก็แค่ดัก If เข้าไปนิดหน่อยก็เรียบร้อย แต่ไม่รู้ว่าจะมีผลต่อ Performance หรือป่าว และ Reliable ?. ในส่วนของ Performance ก็ไม่รู้จะทดสอบอย่างไรเหมือนกัน แต่ผมได้ทำการทดลองใช้งาน $this->load->uri ทั้งใน App และใน Module ซึ่งยังสามารถทำงานได้ปรกติ ก็ถือว่าโอเคครับ (มั้งนะ)

ถ้ามีเวลาเหลือ ผมคงจะหาวิธีแก้ที่สาเหตุ มันจริงๆ อีกทีครับ 🙂

แก้ไขแบบง่ายๆ ครับ  (เติมโค๊ด ๓ ตำแหน่ง)

จุดแรก

if (class_exists('Wick')===FALSE) { // <-- Insert this line (at line 36)

จุดที่สอง

function Wick ()
{
    <span style="color: #0000ff;">static $php4_run_once; // <-- Insert this line</span>
    <span style="color: #0000ff;">if (isset($php4_run_once)) // <-- Insert this line
        return; // <-- Insert this line
    else // <-- Insert this line
        $php4_run_once = true; // <-- Insert this line</span>
    $ci = &get_instance();

จุดที่สาม

} // <-- Insert this line</span>
/* End of file Wick.php */
/* Location: ./system/application/libraries/Wick.php */

รักสามเศร้า (BASE)Loader, (APP)Loader และ MY_Loader

รักสามเศร้าในไฟล์ /system/codeigniter/Common.php

ในฟังก์ชัน load_class ตรงส่วนที่เป็นการตัดสินใจว่าจะดึง library จากใน BASEPATH หรือ APPPATH มีโค๊ดตามนี้

if (file_exists(APPPATH.'libraries/'.config_item('subclass_prefix').$class.EXT))
{
    require(BASEPATH.'libraries/'.$class.EXT);
    require(APPPATH.'libraries/'.config_item('subclass_prefix').$class.EXT);
    $is_subclass = TRUE;
}
else
{
    if (file_exists(APPPATH.'libraries/'.$class.EXT))
    {
        require(APPPATH.'libraries/'.$class.EXT);
        $is_subclass = FALSE;
    }
    else
    {
        require(BASEPATH.'libraries/'.$class.EXT);
        $is_subclass = FALSE;
    }
}

จะเห็นว่าถ้ามี MY_Loader.php อยู่ใน /system/application/libraries/ ก็จะเข้าที่เงื่อนไขแรกเลย. ซึ่งมันจะโหลด Loader.php ซึ่งอยู่ใน /system/libraries/ โดยไม่สนว่าจะมี Loader.php อยู่ใน /system/application/libraries/ หรือไม่ ทั้งๆ ที่จริงๆ แล้วควรให้ความสำคัญกับ Loader ที่อยู่ใน APPPATH ก่อน BASEPATH. ตรงนี้ผมก็ไม่แน่ใจเหมือนกันว่าจุดประสงค์ที่แท้จริงของเงื่อนไขนี้คืออะไร แต่มันไม่ตรงกับความต้องการของผม ก็เลยต้องจัดการปรับเปลี่ยนเล็กน้อย เป็นแบบนี้แทนครับ

if (file_exists(APPPATH.'libraries/'.config_item('subclass_prefix').$class.EXT))
{
    if (file_exists(APPPATH.'libraries/'.$class.EXT))
        require(APPPATH.'libraries/'.$class.EXT);
    else
        require(BASEPATH.'libraries/'.$class.EXT);
    require(BASEPATH.'libraries/'.$class.EXT);
    require(APPPATH.'libraries/'.config_item('subclass_prefix').$class.EXT);
    $is_subclass = TRUE;
}
else
{
    if (file_exists(APPPATH.'libraries/'.$class.EXT))
    {
        require(APPPATH.'libraries/'.$class.EXT);
        $is_subclass = FALSE;
    }
    else
    {
        require(BASEPATH.'libraries/'.$class.EXT);
        $is_subclass = FALSE;
    }
}

Codeigniter MY_Loader in PHP4

ใน /system/codeigniter/CodeIgniter.php จะมีทางแยกระหว่าง php4 กับ php5 อยู่ ซึ่ง php4 จะโหลดคลาส Loader ขึ้นมาก่อนและโหลดคลาส CI_Base (ใน /system/codeigniter/Base4.php) ซึ่งจะสืบทอด (extends) จาก CI_Loader ตรงนี้จึงเป็นจุดบอดเวลาที่เราต้องการจะเขียนคลาส MY_Loader เพื่อใช้งาน เพราะ Base4 มันเขียนไว้ว่ายังไงก็จะสืบถอดจาก CI_Loader ให้ได้ซะอย่างงั้น

ผมพยายามหาทางออก ที่จะไม่ต้องแก้ไขตัว Core ของ CodeIgniter แต่ก็จนปัญหาครับ. จริงๆ แล้วมันมีอยู่อีกแบบนึงคือ เขียน CI_Loader ขึ้นมาใหม่เองทั้งหมด (แบบที่ Matchbox ทำ) แล้ววางไว้ที่ /system/application/libraries/Loader.php แต่ก็ดูจะผิดจุดประสงค์ไปหน่อยครับ เพราะว่า MY_Loader ของผมต้องการจะต่อเติม หรือแก้ไขเฉพาะบาง method ที่อยู่ใน CI_Loaderเท่านั้น.

สุดท้ายก็ต้องตัดสินใจแก้ที่ Core ของมันครับ ที่จะแก้คือไฟล์ /system/codeigniter/Base4.php ครับ

จากเดิม

class CI_Base extends CI_Loader {
    function CI_Base()
    {
        // This allows syntax like $this->load->foo() to work
        parent::CI_Loader();
        $this->load =& $this; // This allows resources used within controller constructors to work
        global $OBJ;
        $OBJ = $this->load; // Do NOT use a reference.
    }
}

เปลี่ยนเป็น

if (class_exists( config_item('subclass_prefix')."Loader" ))
{
    eval('
    class CI_Base extends '.config_item('subclass_prefix')."Loader".' {
        function CI_Base()
        {
            // This allows syntax like $this->load->foo() to work
            parent::'.config_item('subclass_prefix')."Loader".'();
            $this->load =& $this; // This allows resources used within controller constructors to work
            global $OBJ;
            $OBJ = $this->load; // Do NOT use a reference.
        }
    }
    ');
}
else
{
    class CI_Base extends CI_Loader {
        function CI_Base()
        {
            // This allows syntax like $this->load->foo() to work
            parent::CI_Loader();
            $this->load =& $this; // This allows resources used within controller constructors to work
            global $OBJ;
            $OBJ = $this->load; // Do NOT use a reference.
        }
    }
}

แค่นี้ก็จะสามารถใช้งาน MY_Loader ได้แล้วล่ะครับ