แก้ปัญหา PHP-ExcelReader ใน 64Bit system

ตัว PHP-ExcelReader เป็น PHP Class ที่นำมาใช้อ่านไฟล์ Excel 97-2003 ได้ดีมากตัวนึง และแม้จะไม่ได้ update มาตั้งแต่ปี 2007 ก็ยังทำงานได้ดีบน PHP 5.2.8 ซึ่งบนระบบ Development Environment นั้นไม่มีปัญหา ซึ่งบนเครื่องผมเป็น Core 2 Duo ที่ทำงานบน Windows XP Pro 32bit ซึ่งทำงานได้ปรกติดี แต่แล้วพอเอาขึ้น Production Environment มันก็เกิดปัญหาขึ้นที่ function GetInt4d ซึ่งผมควานหาปัญหาและไล่ว่ามันเกิดจากตัว code เองหรือเปล่า หรือว่ามีปัญหากับ function พื้นฐานต่าง ๆ จาก Extension ของ PHP เองหรือเปล่า ซึ่งผมก็ว่าไม่ใช่ และพอเอาไปทำงานบน Production ตัวนึงที่เป็น 32bit CPU กลับไม่มีปัญหา ซึ่งลง Extension เหมือน ๆ กันด้วย –_-‘

แต่สุดท้ายผมก็ไล่ไปเจอสิ่งที่ผมคิดว่ามันเป็นเรื่องที่ทำให้ผม งง แต่มันก็เกิดขึ้นก็คือ [ 1487371 ] AMD64 dead loop fix สรุปง่าย ๆ ว่าเป็นปัญหาของ bitshift ใน OS 64bit ที่ bit ตัวแรกจะได้ค่าไม่ตรงกับใน OS 32bit ทำให้เกิดความผิดพลาด

ตัว error ที่พบคือ

Notice: Uninitialized string offset: -138 in /home/www/public_html/excel/oleread.inc on line 27
Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to allocate 71 bytes) in /home/www/public_html/excel/oleread.inc on line 133

ซึ่งแก้ไขได้ง่าย ๆ ด้วยการเปลี่ยนแปลง function GetInt4d ใหม่ตามด้านล่าง ก็สามารถทำงานบน OS 64bit ได้แล้ว

แก้ไขในไฟล์ oleread.inc

function GetInt4d($data, $pos) {
    // Hacked by Andreas Rehm 2006 to ensure correct result of the 24 block on 32 and 64bit systems
    $_or_24 = ord($data[$pos+3]);
    if ($_or_24>=128) $_ord_24 = -abs((256-$_or_24) << 24);
    else $_ord_24 = ($_or_24&127) << 24;

    return ord($data[$pos]) | (ord($data[$pos+1]) << 8) | (ord($data[$pos+2]) << 16) | $_ord_24;
}

 

แก้ org.eclipse.php.ui.PHPCompletionProposalComputer มีปัญหาใน PDT 2.0

จาก Re: org.eclipse.php.ui.PHPCompletionProposalComputer crash ซึ่งผมก็เจออยู่ และใน [[Tools] PDT-] [performance]Code Assist takes too long ก็มีคำตอบเรื่องนี้แล้ว อยู่ท้าย ๆ เลย

I’ve prepared project that constists of 5 huge PHP frameworks (4130 files), and
I can’t re-create this issue while
working on this project. Please verify the following:

1. You are using the latest nightly build of PDT.
2. "PHP -> Editor -> Code Assist" preferences are set to default.

ซึ่งดูแล้วน่าจะเกิดจาก Options ตัว Show variables from other files ที่มันจะ scan ตัวแปรทั้ง project แบบ JIT ทำให้ถ้า project ที่มีไฟล์มาก ๆ รวมถึง PHP Include Path ด้วย อย่างของผมมี Zend Framework อยู่ใน PHP Include Path อยู่ซึ่งมีไฟล์ 1,300 กว่าไฟล์ มันเลยค้างไปสักพักก่อนจะ completion ตัวแปรออกมาได้ เอาตัวเลือกนี้ออกก็น่าจะหายแหละ

image

 

 

การใช้ Object ใน PHP 4 กับ 5 ต่างกันยังไง ? (ภาค 1)

การ Initialize Object เพื่อให้ได้ Object ใน PHP 4 นั้นจะเป็นการ Pass-by-Value แต่ใน PHP 5 จะเป็นการ Pass-by-Reference แทน จากโค้ดตัวอย่างด้านล่างนี้

class userClass {
    var $data = 'Hello';
    function talk() {
        echo $this->getData();
    }
    function setData($val){
        $this->data = $val;
    }
    function getData(){
        return $this->data;
    }
}

ทำการ Initialize Object โดยกำหนดชื่อ Reference-Name ว่า $obj1

// PHP 5
// Assigned-by-Value Format
// Override operation with Assigned-by-Reference
$obj1 = new userClass();

// PHP 4
// Assigned-by-Value Format
// Used more memory
$obj1 = new userClass();

// PHP 4 same way with PHP 5
// Assigned-by-Reference Format
// E_STRIC Error in PHP 5 and Removed in PHP 6
$obj1 =& new userClass();
$obj1 --------> [OBJECT DATA]

ในการ Initialize Object ใน PHP 4 นั้น ถ้าใช้แบบทั่วไปใช้ใช้กันคือแบบ Assigned-by-Value นั้น Object ที่ถูกสร้างขึ้นมาตอน Initialize แล้วถูก copy (clone) ไปยังตัวแปรที่เราสร้างขึ้นมา ซึ่งการทำแบบนี้จะเสีย memory เพิ่มขึ้น แต่ถ้าใช้การ Initialize Object แบบ Reference กับตัวแปร object แทนการ copy (clone) ข้อมูลใส่ตัวแปรนั้น โดยต้องใช้การทำ Assigned-by-Reference แทนแบบ Assigned-by-Value โดยที่ Initialize Object เพื่อสร้าง Object จะมีการสร้างบนหน่วยความจำ แล้วมีตัวแปรไป Reference มันเพื่ออ้างอิงค่าแทน

แต่ใน PHP 5 นั้นการ Initialize Object ก็ใช้รูปแบบ Assigned-by-Value ไปเลย แบบเดียวกับ Java ซะ แต่ผลก็จะเป็นการอ้างอิงแบบ Assigned-by-Reference ไปเลยในตัว ซึ่งการทำแบบนี้นั้นรวมไปถึงการทำ Assign-Value, Return-Value และการ Pass-Parameter ด้วยเมื่อเรานำไปใช้ตามตัวอย่างด้านล่างนี้

$obj1->talk();

$obj2 = $obj1;

$obj1->talk();

$obj2->setData("Hi");

$obj1->talk();

ก็จะได้ผลของ PHP 5 คือ

Hello
Hi

โดยการทำงานนั้น $obj1 นั้นจะเรียก Method talk มา แล้วทำการ Assigned-by-Reference ให้กับ $obj2 ทำให้ต่อไปเมื่อใช้งาน $obj2 ก็จะเหมือนกับใช้ $obj1 นั้นเอง (ทั้งการแก้ไข และเรียกใช้) เพราะตัวแปรทั้งสองตัว มันอ้างอิง Memory Address เดียวกัน

$obj1 --------> [OBJECT DATA]
          |
$obj2 ----|

ส่วน PHP 4 คือ

Hello
Hello

โดยการทำงานนั้น $obj1 นั้นจะเรียก method talk มา แล้วทำการ Assigned-by-Value หรือเรียกว่า clone ให้กับ $obj2 ทำให้ $obj2 นั้นมีค่าเหมือนกับ $obj1 เพียงแต่ถ้ามีการแก้ไขค่าที่ $obj2 จะไม่มีผลกระทบกับ $obj1 เลย

$obj1 --------> [OBJECT DATA]

$obj2 --------> [OBJECT DATA]

แต่ถ้าเราต้องการ copy (clone) ค่า ใน PHP 5 ได้มี keyword เพิ่มขึ้นมาชื่อ clone ครับ

โดยเปลี่ยนการ Assigned-by-Value เพียงเล็กน้อยโดยเพิ่ม keyword ไปด้านหลังเครื่องหมาย Assigned (=) ได้ตามนี้

$obj2 = clone $obj1;

เมื่อทำแบบนี้ด้านบนก็จะเป็นการ copy (clone) ค่าให้กับอีกตัวแปรนึงให้มันไป Reference กับ Objet ที่ถูก clone ไปอีกก้อนนึงแบบเดียวกับ Assigned-by-Value แบบ PHP 4 นั้นเอง

$obj1 --------> [OBJECT DATA]

$obj2 --------> [OBJECT DATA]

จากการทำงานด้านบนจะเห็นว่าใน PHP 5 นั้นการ Pass-Parameter, Initialize Object, Assign-Value และ Return-Value นั้นจะส่งแค่ค่าของ Reference-Address ออกไปเท่านั้น ซึ่งเป็นผลทำให้ใช้ Method Chaining ไปได้เลยในตัว

ต่อมาผมทำการเพิ่ม Method เข้าไปอีก 1 ตัวเพื่อทำสอบการทำ Method Chaining

class userClass {
    var $data = 'Hello';
    function talk() {
        echo $this->getData();
    }
    function setData($val){
        $this->data = $val;
    }
    function getData(){
        return $this->data;
    }
    function chaining() {
        return $this;
    }
}

โดยการ Return ที่ตัว keyword $this นี้ออกไป การ Return-Value นี้เป็นการ Return ที่เอา Reference Address ออกมาไปส่วนต่อให้อ้างอิงกับ Address ของ Object นั้น ๆ ทำให้ใช้งาน Method ของ Object นี้ต่อไปได้เรื่อย ๆ ครับ ดังตัวอย่างด้านล่างครับ

$obj1->chaining()->talk();

ก็จะได้ผลดังนี้ครับ

Hello
Hi
Hi

ต่อมาเรามาทดสอบเรื่องการ Pass-Parameter โดยทดสอบว่ามันจะ Pass-by-Reference ถึงแม้ว่าจะ กำหนด Parameter ที่ Pass-by-Value ไว้ก็ตามที โดยในโค้ดด้านล่างนี้ ได้เพิ่ม Method prototypeMod เข้าไป โดยส่ง Object และ Value ลงไปเพื่อ Assign ค่าให้กับ Object ตัวที่ Pass-Parameter ลงไป

class userClass {
    var $data = 'Hello';
    function talk() {
        echo $this->getData();
    }
    function setData($val){
        $this->data = $val;
    }
    function getData(){
        return $this->data;
    }
    function chaining() {
        return $this;
    }
    function prototypeMod($obj, $val) {
        $obj->setData($val);
        return $obj;
    }
}

แล้วเมื่อผมนำไปใช้ก็ทำการส่ง $obj2 ลงไปแล้วตั้งค่า Hello ลงไป แล้วทำ Chaining เรียก talk ของ $obj2 ออกมาใช้งาน แล้วก็เรียก talk ของ $obj1 ด้วยเช่นกั

$obj1->prototypeMod($obj2, 'Hello')->talk();

$obj1->talk();

ผลที่ได้คือ

Hello
Hello

จะเห็นว่าถ้าเราส่ง Object ด้วยการ Pass-Parameter เข้าไปใน function หรือ method จะเป็นการ Pass-by-Reference ครับ ซึ่งต้องระวังอย่างมาถ้า upgrade จาก PHP 4 มา 5 ครับ ที่บางครั้งค่าที่ได้อาจจะแตกต่างในช่วงการทำงานครับ ต้องระวังเรื่องของ logic-error ให้ดีครับ ซึ่งถ้าคนที่ไม่เคยเขียน Programming Language พวก Java, C# หรือ VB.NET มาก่อนอาจจะงง เพราะ PHP 4 นั้น Object จะถูกเรียกว่า “objects are not any longer just ‘improved arrays’.” ซึ่งใน PHP 5 นั้นได้ลบแนวคิดนี้ออกไป ให้เป็นแบบเดียวกับภาษา Object Oriented Programming อื่น ๆ ครับ

ดังนั้นต่อไปถ้าใช้ใน PHP 5 ไม่ต้องทำการ Initialize Object แบบ Assigned-by-Reference อีกต่อไปครับ และการทำ Assigned-by-Reference ในการ Initialize Object จะใช้งานไม่ได้แล้วใน PHP 6 ด้วย โดยใน PHP 5 จะขึ้น E_STRIC Error Message ด้วยเช่นกันครับ

ปล. entry นี้เล่นซะเหนื่อยเพราะว่านั่งแก้ format ของ code ซะเมามันกับนั่งไล่ keyword ต่าง ๆ ให้มันตรงกับที่เค้าใช้กัน เพราะส่วนตัวเองก็ไม่แม่นพวก technical term บางตัวว่าใช้คำว่าอะไรเท่าไหร่ เพราะคืนอาจารย์ไปบ้างแล้ว -_-‘ (แต่รู้ว่ามันคืออะไรนะ เพียงแต่ใช้คำไม่ถูก) ถ้าส่วนไหนไม่เข้าใจก็โพสความคิดเห็นถามเพิ่มเติมกันมาได้นะครับ

เพิ่มเติม (2/6/2551 18:00)

In PHP 5 there is a new Object Model. PHP’s handling of objects has been completely rewritten, allowing for better performance and more features. In previous versions of PHP, objects were handled like primitive types (for instance integers and strings). The drawback of this method was that semantically the whole object was copied when a variable was assigned, or pass as a parameter to a method. In the new approach, objects are referenced by handle, and not by value (one can think of a handle as an object’s identifier).

และใน Zend Engine 2 ที่เป็นแกนหลักของ PHP 5 ได้ให้คำจำกัดความไว้ว่า “In the new approach, objects are referenced by handle, and not by value”

อ้างอิงจาก

 

ตั้งค่า Zend Optimizer กับ Zend Debugger ให้ทำงานพร้อมกันได้

หลายคนที่ทำงานด้าน Web Dev ที่พัฒนาบน PHP มักจะต้องใช้ Zend Debugger ไม่มากก็น้อย โดยเอาไว้ใช้ในการ Debug แบบ Realtime ในระหว่างที่ PHP กำลังทำงานอยู่ เพื่อเก็บรายละเอียดด้าน Performance ต่าง ๆ ที่ เช่น

  • php ไฟล์ที่เราเขียนอยู่มีการ access พวก include หรือ require ไฟล์ต่าง ๆ กี่ไฟล์
  • มีการเรียกใช้ object, class, method หรือ function อะไรบ้าง ไล่เรียงตาม flow ต่าง ๆ
  • ตรวจสอบว่า method หรือ function ในแต่ละตัวนั้นทำงานนานแค่ไหน เพื่อเช็ด และตรวจสอบด้านความเร็วในการทำงาน
  • เช็คพวก warning, strict, notice message ระหว่างการทำงานโดยไม่ต้องเขียน error_reporting ให้เขียนไฟล์ให้เสียเวลาทำงาน

ส่วน Zend Optimizer นี่ผมใส่ลงไปเพื่อเพิ่มความเร็วในการทำงานของ PHP เวลาทำงาน แต่มัน debug ไม่ได้ครับ แต่ทำให้ script ของเราทำงานเร็วขึ้น 10 – 30% ตามที่ Zend ได้บอกไว้ในเว็บ (แต่มันก็เร็วขึ้นจริง ๆ แหละ)

โดยทั้งสองตัวนี้มีวิธีการลงที่คล้าย ๆ กัน (เรื่องวิธีการลงหาได้ทั่วไปตาม internet ทั้ง Debugger กับ Optimizer ไม่ขออธิบายแล้วกัน) ถ้าไปสืบหาตามปกติ การตั้งค่าจะทับซ้อนกันครับ ทำให้ต้องเลือกอย่างใดอย่างหนึ่งตอนใช้งาน แต่ว่าก็มีหนทางทำให้มันทำงานได้เหมือนกันครับ

โดยเราตั้งค่าพื้นฐานโดยใช้ Optimizer ยืนพื้นไปก่อนเลย โดยชื่อไฟล์ต่าง ๆ ที่บอกมาเป็นตัว library ที่ทำงานบน Windows ครับ

[Zend]
zend_extension_ts="c:/path_to_zend_optimizer/lib/ZendExtensionManager.dll"
zend_extension_manager.optimizer_ts="c:/path_to_zend_optimizer/lib/Optimizer"

โดยใน c:/path_to_zend_optimizer/lib/Optimizer นั้น จะมี directory เรียงรายตาม version ต่าง ๆ ของ php ครับ โดยผมใช้ PHP 5.2.6 ครับ ก็จะใช้ directory php-5.2.x ครับ ในนั้นก็จะมีไฟล์ชื่อ ZendOptimizer.dll (ไฟล์ที่อ้างอิงคือ Windows นะครับ)

ให้เอาไฟล์ ZendDebugger.dll ที่โหลดมาจากเว็บ zend เอามาใส่ โดยเอาไฟล์จาก directory ใน .zip ไฟล์ใส่ให้ตรงกับ version ใน directory ปลายทางด้วย

แล้วตั้งค่า ต่อไปด้วยคำสั่ง

[Zend]
zend_extension_ts="c:/path_to_zend_optimizer/lib/ZendExtensionManager.dll"
zend_extension_manager.optimizer_ts="c:/path_to_zend_optimizer/lib/Optimizer"

[Debugger]
zend_extension_manager.debug_server_ts="c:/path_to_zend_optimizer/lib/Optimizer"
zend_debugger.allow_hosts=127.0.0.1/32
zend_debugger.allow_tunnel=127.0.0.1/32
zend_debugger.expose_remotely=allowed_hosts
zend_debugger.connector_port=10001

แล้ว restart ตัว Web Server ก็จะสามารถทำให้ Zend Optimizer กับ Zend Debugger ทำงานได้พร้อม ๆ กันแล้วหล่ะครับ

โดย phpinfo แล้วจะได้ตามวงสี่เหลี่ยมสีแดงนี้ครับ

2008-05-19_211436

อ้างอิงจาก How to allow Zend Optimizer and Zend Debugger coexist