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 ก็ใกล้ความจริงเข้ามาแล้วครับ

function fgetcsv with Thai Language

ฟังก์ชั่น fgetcsv ใช้สำหรับอ่านไฟล์ csv โดยมันจะทำการ parse ออกมาเป็น Array ให้เราเลยครับ. แต่ก่อนตอนที่ผมยังไม่รู้จักคำสั่งนี้ นั่งเขียนตัว Parse เองตั้งนานครับ แถมยังมีบั๊กอีกต่างหาก (- -‘)

เข้าเรื่องครับ… ผมยังไม่ได้ศึกษาอย่างจริงจังเหมือนกัน ว่าสาเหตุมันคืออะไรกันแน่ (เพราะขี้เกียจครับ) เอาเป็นบอกปัญหาและวิธีแก้เลยละกันนะครับ

ปัญหา คือ มันไม่สามารถ parse ภาษาไทยได้ครับ  (เฉพาะบาง server ที่มี setting อะไรบางไม่ปรกติ)

วิธีแก้ ก็ง่ายๆ ครับ.. ใช้คำสั่ง  setlocale  ครับ.  ประมาณนี้

<?php
    setlocale ( LC_ALL, 'en_US.UTF-8' );
    ...
    ...
    $fields = fgetcsv ( $fp , $maxlength , $separator );
?>

ผมก็ยังไม่ได้ค้นคว้าเหมือนกันว่า มันมีตัวเลือกอะไรบ้าง และมีผลอย่างไร  แต่เอาเป็นว่าให้ใช้งานได้พอครับ