2008/12/29

一年離職的魔咒

我上班從未堅持過一年。


最近,我又要離職了,這份工作將於2008-12-31日劃下一個句點。
這半年來,尤其是這兩個月,許多不利於上班族的訊息不斷在媒體中放送,例如:不景氣、無薪假、裁員等等。
我常常在想,家庭、社會、公司給每個人的歸屬到底是什麼?為甚麼大多數人害怕失業?


我在上一份工作上瞭解到家庭的牽絆,當時是在一個討論勞健保自付額度到底要選擇扣多少%的問題。
個人是傾向扣多一些,因為當時我拿了薪水也不知道要幹麻。
不過當時有家庭(累)的同事們都傾向扣少一點,讓他們能夠有多一點錢去付房屋貸款、小孩的學費等等。
我不知道是幸運還是不幸,目前沒有對象,也沒想過交一個,父母對此也無期待。
雖然已過而立之年,但心中還有一股衝動,想要對世界有些貢獻。


有點離題了,讓我們回到失業的恐懼這個話題上。
我認為,不論是主動還是被動,失業這件事情其實不應該是會讓人恐懼的。
從客觀的角度看,公司並不屬於全體員工,而是屬於公司的經營階層的那一小群人所有。
這些人決定了公司的一切,所以,公司會有「負責人」這個角色,這個角色明明白白告訴我們,公司並不屬於全體員工的。
因此,失業變成了一個平常的事件:人總會來來去去,因為員工不能掌握自身的被僱用權力。(注意,我用的是權力而非權利)


平常的事情像什麼呢?像吃飯、喝水、睡覺一樣。這些東西太平常了,所以讓我們日用而不知。
失業對我來說是稀鬆平常之事,沒有「上班」並不會對我有所損害,重要的是,我有沒有「工作」。
工作是什麼?工作是實現人生唯一的途徑,無論你做的是什麼樣的工作。
我失業、不用上班,但我仍然有工作,只是這工作沒有人會發給我薪水,也不會有人給我鼓勵與支持。


我不知道其他人的選擇是什麼,但我知道我父母的選擇是要那份薪水。
我感謝我父母過去的努力和選擇,讓我可以有選擇的權利(與任性),現在,輪到我了~


 

2008/10/29

瓦斯跟洨人難得有一致的看法,我也要跟進~

我也不知道出處,但是從瓦斯跟洨人CV過來的~





進入公司前,從上往下讀



老闆:萬分歡迎,沒有你我們的公司肯定大不一樣!

職員:如果工作太累,搞不好我會辭職的

老闆:放心,我不會讓這樣的事情發生的!

職員:我週休二日可以休息嗎?

老闆:當然了!這是底線!

職員:平時會天天加班到淩晨嗎?

老闆:不可能,誰告訴你的?

職員:有餐費補貼嗎?

老闆:還用說嗎,絕對比同行都高!

職員:有沒有工作猝死的風險?

老闆:不會!你怎麼會有這種念頭?

職員:公司會定期組織旅遊嗎?

老闆:這是我們的明文規定!

職員:那我需要準時上班嗎?

老闆:不,看情況吧

職員:工資呢?會準時發嗎?

老闆:一向如此!

職員:事情全是新員工做嗎?

老闆:怎麼可能,你上頭還有很多資深同事!

職員:如果管理職位有空缺,我可以參與競爭嗎?

老闆:毫無疑問,這是我們公司賴以生存的機制!


職員:你不會是在騙我吧?



進入公司後,從後往前讀




順便預告一下,目前這份工作我預計做到明年二月底。

連續工作了兩年,我需要休個假,在休假期間好好陪家人,順便作一些研究。

2008/10/27

快速升級 Drupal

先說好,這個方法只適用在主要版號相同的情況(例如:5.0 -> 5.1) ,並不適用在主要版號不同的狀況(例如:5.x -> 6.x)。



  1. 備份檔案與資料庫。

  2. 下載新版的 Drupal 主程式。

  3. 解壓到某個目錄。

  4. 刪除 .htaccess 跟 sites/default/setting.php 這兩個檔案。

  5. 用系統管理員的權限登入系統。

  6. 複製解開的檔案到舊的系統目錄中。

  7. 跑 update.php。

2008/10/20

超大字集~

我目前使用的輸入法是「新酷音輸入法」,之前在使用的時候,常常有時候選字翻到最後幾排時,都有缺字,即便我裝了號稱有Ext-B 字面的「新細明體更新套件」也是一樣。


直到某個很無聊的下午,我到很久沒更新的「Unicode 補完計畫」的討論區去翻文章來看,發現了一個維基百科的連結:「Unicode 擴展漢字」。


在該連結裡面,我發現了一個有超大字集,具有 Ext-A/B/C 字面的「海峰五筆超大字符集」,下回來裝上去後,本來缺字的部份都顯示出來了~

2008/08/27

在 Drupal 中配合 Views 和 Localizer 做多國語言頁面

由於 Localizer 沒辦法對 frontpage 做個別指定,而且我需要的只是把屬於各國語言不同的資料撈出來,畫面的結構不變。
因此,我在 Views 中設定 frontpage,並在 Argument Handling Code 中輸入:




global $locale;
$view->filter[] = array(
'id' => 'localizernode.language',
'field' => 'localizernode.language',
'operator' => 'is',
'value' => $locale,
);
return $arg;


即可

2008/08/15

CakePHP 的 Model 中 id 屬性被修改的問題

我在使用 Auth 這個 Component 時,由於很多地方都會使用到 User 這個 Model 來實現一些功能,所以我在 app_controller.php 裡面於 __construct 時將一個 User 物件 new 出來,並將其設為 AppController 的屬性來共用。

問題發生在 user/add 的 action 上面,當執行 $this->User->save($this->data) 時,總是會改寫到登入 User 的資料,而非新增一筆 User 的資料。
後來我發現,$this->User 的 id 屬性變成登入 User 的 id(正常的狀況是,這個 id 屬性應為 false)。
所以我檢查 app_controller.php 使用到 User 的地方,發現只要在 beforeFilter 這個方法內,另外 new 一個 User 物件出來代替使用 AppController 的 User 屬性即可解決這個問題。

不過為了保險起見,在 user/add 這個 action 當中,我還是會加入以下兩行敘述在 $this->User->save($this->data) 之前:

unset($this->data['User']['id']);
$this->User->id = false;

2008/08/12

CakePHP 將預設值填入表單的方法

方法其實很簡單,在 Controller 裡面的 action function 裡面設定 $this->data 這個值即可。

add:
$this->data = $this->Model->create();

edit:
$this->data = $this->Model->read(null, $id);

2008/07/15

啟用狐仙大人第三版

話說這一版出來有一段時間了,不過之前因為在趕案子,沒空測試,所以拖到上禮拜比較閒的時候才換上新版~
我是有參加那個當日下載的金氏世界紀錄啦,不過當天我就發現可攜版也同時放出來了~

有些附加元件並沒有這一版,所以我重新做了一個目前我在用的列表:


 


2008/07/03

Node Import 的匯入技巧

目前做的這個案子,需要把舊的文章匯入到新的系統中,匯入是沒什麼問題的,文章數目沒少,可是有些文章就是有幾個欄位匯不進來。
研究之後發現,是因為文章本文的欄位 (一般來說是 Body) 的內容引起的問題,實際上因為內容很長,我也無法判斷問題出在哪?

只記得為了避免雙引號跟換行字元,我在匯入之前已經先處理裡過了:我用 OpenOffice.org 來置換字元,因為他支援正規表達式的搜尋與取代,我把雙引號換成單引號、換行字元換成 <br />。

後來我把這個本文欄位移到最後一欄,並在最後一欄後面再加一欄防護欄,內容是兩個雙引號。
我發現用這樣的方式匯入,文章都沒有問題,所有的欄位也都可以匯入。

另外要注意的事情是,有些模組會對文章作直接或間接的前後處理(例如:Pathauto 、Taxonomy Menu 模組),匯入前要先停用。

2008/06/22

讓 Drupal 的 Node Import 模組支援 option widget

Node Import 模組是個非常好用的模組,可以把各種類型的 Node 資料先用打好,存成 csv 檔然後匯入。
不過對於自設的 CCK 欄位有個缺憾,就是在匯入之前必須先把 widget 的型態,由 option (select list / checkbox / radio) 改成 text,這樣才能匯入,實在是不太方便。
我修改了一點程式,讓它可以不用轉換 widget 的型態也能匯入,不過有個限制,目前修改的方式,只支援 Allowed values list 為下列形式:
value1
value2
vaule3

不支援 Allowed values list 為下列形式:
key1|value1
key2|value2
key3|value3

這是要注意的地方。

修改方式:
找出 node_import/supported/cck/content.inc 中的 function content_node_import_prepare
在該函數接近結尾的地方找到:

        // Unset the dummy column value.
        unset($node->$dummy_name);


改成:

        $option_type = array('options_select' => true, 'options_onoff' => true, 'options_buttons' => true);
        if (isset($option_type[$field['widget']['type']])) {
            $keys = array();
            foreach($node->$field_name as $value_list) {
               $keys[] = $value_list['value'];
            }
            if ($field['multiple'] || $field['widget']['type'] == 'options_onoff') {
               $node->$field_name += array('keys' => $keys);
            } else {
               $node->$field_name += array('key' => reset($keys));
            }
        }

        // Unset the dummy column value.
        unset($node->$dummy_name);


這樣就可以了~
報錯在這裡

2008/06/14

Drupal 的 Date 模組的一個小問題

目前首上有一個案子,要作一些統計的處理,其中有一向是關於日期欄位的。
我用 Drupal 的 Views 模組搭配 Date 模組來作,於 Argument 指定該日期欄位,這樣就可以顯示出我要的結果。
不過 Date 模組中有個小問題,那就是在生成 SQL 命令時,會產生錯誤,其錯誤的理由是,日期欄位的名稱被設定成 range ,而剛好在 MySQL 中 range 是保留字,必須用 `range` 才能使用該名稱。
所以我修改了 date_views.inc 中的 _date_views_argument_range_handler 函數

找到:
$fieldinfo['fieldname'] = 'range';

改成:
$fieldinfo['fieldname'] = '`range`';

這個問題於 Date 模組 V1 跟 V2 的版本都會發生,報錯在這裡

2008/06/03

擴充 DateTime 類別

今天在處理一個日期比較的問題,發現 DateTime 類別可以進行 1970/01/01 以前的日期比較,不過由於該類別建構式比較嚴格,所以我將他繼承後擴充改寫了一番。
程式碼在下面:

class Zyme_DateTime extends DateTime{
    function __construct($時間 = null, $時區 = null){
        if ($時間 instanceof DateTime) {
            $_時間 = $時間->format('c');
        } else if (is_int($時間)) {
            $_時間 = '@' . $時間;
        } else {
            $_時間 = (string)$時間;
        }
           
        if (is_null($時區)) {
            parent::__construct($_時間);
        } else {
            $_時區 = self::時區($時區);
            parent::__construct($_時間, $_時區);
            $this->setTimezone($_時區);
        }
    }
   
    function parse(){
        return date_parse($this->format('c'));
    }

    function 時戳(){
        return (int)$this->format('U');
    }
   
    function 凌晨(){
        $this->setTime(0,0,0);
        return $this;
    }

    function 午夜(){
        $this->setTime(23,59,59);
        return $this;
    }
   
    static function 時區($時區 = null){
        if ($時區 instanceof DateTimeZone) {
            return $時區;
        } else if (is_null($時區)) {
            $_時區 = date_default_timezone_get();
        } else if (is_numeric($時區)) {
            $_時區 = self::數字時區($時區);
        } else {
            $_時區 = self::字串時區($時區);
        }
        return new DateTimeZone($_時區);
    }

    static protected function 數字時區($時區){
        $_時區 = (Zyme_Filter::數字範圍($時區, -12, 12))
            ? (int)((float)$時區 * 3600)
            : (int)$時區;
        $_表格 = self::時區位移表格();
        $_輸出 = &$_表格[$_時區];
        return (isset($_輸出))
            ? $_輸出
            : 'UTC';
    }
   
    static protected function 字串時區($時區){
        $_表格 = self::時區識別表格();
        $_時區 = (string)$時區;
        return (isset($_表格[$_時區]))
            ? $_時區
            : 'UTC';
    }
   
    static function 時區識別表格(){
        static $_時區 = array();
        if (0 === count($_時區)) {
            $_時區 = array_flip(DateTimeZone::listIdentifiers());
        }
        return $_時區;
    }
         
    static function 時區位移表格(){
        static $_時區 = array();
        if (0 !== count($_時區)){
            return $_時區;
        }
        $_表格 = DateTimeZone::listAbbreviations();
        foreach ($_表格 as $_項目){
            foreach ($_項目 as $_內容) {
                if (false === isset($_時區[$_內容['offset']])) {
                    $_時區[$_內容['offset']] = $_內容['timezone_id'];
                }
            }
        }
        ksort($_時區);
        return $_時區;
    }   

}

2008/05/11

替代 xdebug 的報錯程式


xdebug 的報錯功能非常好用,但由於沒有 nts 的版本,一直以來是我心中的缺憾。
這兩天花了一點時間研究,寫出一個替代用的報錯物件,除了時間和記憶用量無法顯示外,其他都可以做到接近 xdebug 的報錯功能。
我還加上顯示出現錯誤的源碼片段、傳入參數的功能,這個物件還可以繼續改加以完善。

用法:
Zyme_Error::除錯(true);
Zyme_Error::錯誤處理(true);

原始碼在下面:
<?php
final class Zyme_Error {
    static private $除錯 = false;
    static private $詳細 = false;
    static private $間距 = 2;
   
    static function 除錯($除錯 = null){
        $_除錯 = self::$除錯;
        if (false == is_null($除錯)) {
            self::$除錯 = (bool)$除錯;
        }
        return $_除錯;
    }
   
    static function 錯誤處理($設定 = true, $詳細 = false, $間距 = 2){
        static $處理 = false;
        if ($設定) {
            if (false == $處理) {
                set_error_handler(array(__CLASS__, '報錯'));
                $處理 = true;
            }
        } else {
            restore_error_handler();
            $處理 = false;
        }
        self::$詳細 = (bool)$詳細;
        $_間距 = (int)$間距;
        self::$間距 = (0 > $_間距) ? 0 : $_間距;
    }

    static function 報錯($代碼, $訊息, $檔案 = '', $行號 = '', $內容 = array()){
        static $預設 = array('function' => '', 'line' => '', 'file' => '', 'class' => '', 'object' => '', 'type' => '', 'args' => array());
        if (false == self::$除錯 || false == (error_reporting() & $代碼)) {
            return true;
        }
        $除錯 = array_reverse(debug_backtrace());
        array_pop($除錯);

        $輸出 = array();
        $輸出[] = '<table bgcolor="#eeeeec" border="1px" cellspacing="0px" cellpadding="0px">';
        $輸出[] = '<tr><th colspan="3" style="background:#f57900; text-align:left; padding:1ex">' . self::錯誤代碼($代碼) . ' : ' . $訊息 . '</th></tr>';
        $輸出[] = '<tr><td colspan="3" style="padding:1ex">' . $檔案 . '<strong> : </strong>' . $行號 . self::源碼($檔案, $行號) . '</td></tr>';
        $輸出[] = (empty($除錯)) ? '' : '<tr style="background:#e9b96e"><th>#</th><th>Function</th><th>Location</th></tr>';
        foreach ($除錯 as $_索引 => $_內容){
            $輸出[] = self::項目繪製($_索引, $_內容);
        }
        $輸出[] = '</table>';
        echo implode("\n", $輸出), '<br />';
        return true;
    }

    static private function 引用($內容) {
        $_內容 = trim($內容);
        return (empty($_內容)) ? '&nbsp;' : htmlspecialchars($內容, ENT_COMPAT, 'utf-8');
    }

    static private function 參數($參數) {
        return (empty($參數) || false == self::$詳細) ? '' : '<pre style="background:#ffffff; margin:0px; padding:1ex; overflow:auto">' . self::引用(print_r($參數, true)) . '</pre>';
    }

    static private function 源碼($檔案, $行號) {
        static $斷行 = "<br />";
        if (false == is_readable($檔案) || false == self::$詳細) {
            return '';
        }
        $源碼 = explode($斷行, substr(highlight_file($檔案, true), strlen('<code>'), -1 * strlen('</code>')));
        $行數 = count($源碼);
        $_行號 = $行號 - 1;
        $開始 = ($_行號 - self::$間距);
        $結束 = ($_行號 + self::$間距);
        $_開始 = (0 > $開始) ? 0 : $開始;
        $_結束 = ($行數 < $結束) ? $行數 : $結束;
        $格式 = '<div style="color:#000000; display:inline">%0' . ceil(log10($行數)) . 'd<strong> : </strong></div>%s';
        $輸出 = array();
        for ($索引 = $_開始; $索引 <= $_結束; $索引++){
            $_格式 = ($_行號 == $索引) ? '<div style="background:#00ff00; display:inline">' . $格式 . '</div>' : $格式;
            $輸出[] = sprintf($_格式, $索引 + 1, $源碼[$索引]);
        }
        return '<div style="background:#ffffff; padding:1ex; overflow:auto">' . implode($斷行, $輸出) . '</div>';
    }

    static private function 項目繪製($索引, $項目){
        static $預設 = array('function' => '', 'line' => '', 'file' => '', 'class' => '', 'object' => '', 'type' => '', 'args' => array());
        $輸出 = array();
        $_項目 = $項目 + $預設;
        if (empty($_項目['file'])) {
            return '';
        }
        $調用 = (empty($_項目['type'])) ? $_項目['function'] : $_項目['class'] . $_項目['type'] . $_項目['function'];
        $位置 = (empty($_項目['file'])) ? '' : self::引用($_項目['file']) . '<strong> : </strong>' . self::引用($_項目['line']);
       
        $輸出[] = '<tr>';
        $輸出[] = '<td style="padding:1ex; vertical-align:top; text-align:center">' . $索引 . '</td>';
        $輸出[] = '<td style="padding:1ex; vertical-align:top">' . $調用 . self::參數($_項目['args']) . '</td>';
        $輸出[] = '<td style="padding:1ex; vertical-align:top">' . $位置 . self::源碼($_項目['file'], $_項目['line']) . '</td>';
        $輸出[] = '</tr>';
       
        return implode("\n", $輸出);
    }
       
    static private function 錯誤代碼($代碼){
        static $錯誤 = array(
            E_ERROR                            =>    'Error',
            E_WARNING                        =>    'Warning',
            E_PARSE                            =>    'Parse',
            E_NOTICE                        =>    'Notice',
            E_CORE_ERROR                =>    'Core Error',
            E_CORE_WARNING            =>    'Core Warning',
            E_COMPILE_ERROR            =>    'Compile Error',
            E_COMPILE_WARNING        =>    'Compile Warning',
            E_USER_ERROR                =>    'User Error',
            E_USER_WARNING            =>    'User Warning',
            E_USER_NOTICE                =>    'User Notice',
            E_STRICT                        =>    'Strict',
            E_RECOVERABLE_ERROR    =>    'Recoverable Error',
        );
        $輸出 = &$錯誤[$代碼];
        return (isset($輸出)) ? $輸出 : 'Unknown Error';
    }   

}
?>

2008/04/25

SugarCRM 的 射、拔、追

今天我的老闆交代我在 Server 上裝一份新的 SugarCRM ,原以為是個很簡單的事情,結果花了一整天~
原因出在 SugarCRM 在安裝過程中,檢查 MySQL 連線設定那部份一直過不了,可是在同一台機器上,另外一份之前安裝過的 SugarCRM 卻跑的好好的~

我以為是 PHP 版本問題,從 PHP4 升級、換成 PHP5 都不行~
後來以為是 MySQL 的問題,重裝、換了 5.1 的版本也不行~
最後只好使出最終奧義:在 Windows 上裝好後再移過去...

這下終於可以動了,不過我還是不曉得 SugarCRM 的安裝程式出了什麼問題。

另外,我在 Windows 下安裝程序最後會發生 500 錯誤,只要把 SugarCRM 目錄下的 .htaccess 這個檔案刪除即可正常執行~

2008/04/15

重構插件類別

算是在實做過程中發現一些問題,然後重構這個強力工具!

我知道我目前碰到一個危險的境界,由於這個插件的類別非常強力,我經常拿他來替代繼承,我知道有點過頭了,但它真的很好用!
~我是說你可以維持一個物件變數,然後把需要功能一直掛入(只要你繼承這個抽象的插件類別,寫出你需要的功能)~

這幾天一直為了unset前後記憶用量沒有改變的問題無法釋懷,後來問了 kiang,他表示那是 Zend 引擎還沒到記憶體回收的週期,所以不會釋放~
這個類別的 __更新() 方法有一個地方我一直無法瞭解為何會有這個效果,知道的網友請開示吧~

另外一提,ADODB Lite 目前仍然是記憶用量最小的 DB Layer ,我自己寫的這個 DB Layer 跟它比較,兩邊都不掛任何多餘的模組,硬是比它大了約 50 K...
我該檢討了~


<?php
abstract class Zyme_Class_Plugin{
    private $_插件 = array();
    private $_方法 = array();
    private $_反映 = array();

    function __construct($宿主 = null){
        static $類別 = __CLASS__;
        if ($宿主 instanceof $類別) {
            $this->__插件($宿主);
        }       
    }
   
    final function __call($方法, $參數){
        return call_user_func_array($this->_方法[$方法], $參數);
    }

//    清除插件方法
    final protected function __清除(){
        $this->_插件 = array();
        $this->_方法 = array();
        $this->_反映 = array();
    }   

//    測試某個插件是否已安裝
    final protected function __已裝($類別){
        return isset($this->_插件[$類別]);
    }
   
//    將插件的方法加入$this的方法中
    final protected function __插件(Zyme_Class_Plugin $插件){
        $類別 = get_class($插件);
        if (false == $this->__已裝($類別)){
            $this->_插件[$類別] = $插件;
            $this->_方法 = $插件->__反映() + $this->_方法;
        }
    }

//    取得插件的可呼叫方法列表
    final function __方法(){
        return $this->_方法;
    }

//    取得插件本身的方法列表(除了方法名稱開頭是'__'的方法,例如一些魔術方法,或是方法本身屬於私有方法)
    final private function __反映(){
        $輸出 = &$this->_反映;
        if (false == empty($輸出)) {
            return $輸出;
        }
        $反映 = new ReflectionObject($this);
        $方法 = $反映->getMethods();
        foreach ($方法 as $項目){
            $名稱 = $項目->getName();
            if (0 !== strpos($名稱, '__') && false == $項目->isPrivate()) {
                $輸出[$名稱] = array($this, $名稱);
            }
        }
        return $輸出; 
    }

//    匯入插件的可呼叫方法表
    final function __匯入(Zyme_Class_Plugin $宿主){
        $this->_方法 = $宿主->__方法() + $this->_方法;
    }

//    更新所有插件的方法,通常在載入所有插件後調用
    final protected function __更新(){
        foreach ($this->_插件 as $插件){           
            $插件->_方法 = $this->_方法 + $插件->_方法;    //    照理說,這行應該會出現錯誤(存取私有成員),但不知為何可以執行
//            $插件->__匯入($this);    //    萬一未來版本上面那行無法執行時,可以改用這行
        }
    }
   
}

?>


2008/04/07

開發平台暫時維持在 PHP 5.2.5

由於名稱空間的諸多限制,以及 PHP 5.3 的 call_user_func_array 傳遞參照參數陣列 與 &new 的 Deprecated 警告問題,個人決定還是先將開發平台維持在 PHP 5.2.5 上面。
名稱空間的限制還好,但後面那兩個會造成許多現有的函式庫噴出一堆警告訊息,我沒時間一個一個去修,所以只好暫時放棄,等年底 PHP 5.3 出來一陣子後再看看。

2008/04/03

名稱空間的注意事項

在使用上有幾個要注意的事情:

  • 使用 use 敘述必須至少有一項用::串接
use test; // 這是錯的
use test::test1; // OK

  • 上述的的敘述中,若用::來分隔,最後一項不可相同,若相同則必須用 as 來設定別名
use test::test1::test;
use test::test2::test; // 這是錯的
use test::test2::test as test0; // OK

  • 命名空間不可使用關鍵字或保留字
use test::class; // 這是錯的
use test::_class; // OK

  • 透過字串變數來 new 一個物件時,字串變數必須為完整的類別
use test::test1;
$class1 = 'test1::_class';
$class2 = 'test::test1::_class';
$obj1 = new $class1();   //   這是錯的
$obj2 = new $class2();   //   OK
這當中我個人覺得最嚴重的問題是,命名空間不可以使用關鍵字或保留字這項限制,如此一來將可用的詞彙縮減掉了,實在是很不方便~
最明顯的影響是 interface 一詞,這在 ZF 的命名中是非常常見的,目前的解法是加底線、使用縮寫,或是改用其他的詞彙。
不過 interface 這個詞彙是電腦的專有名詞,目前在物件導向中,並無其他的詞彙可替代,實在是很傷腦筋。

PHP 5.3 dev

根據 PHP 5.3 todo 的資訊,PHP 5.3 會在今年第二或是第三季推出,目前我正在考慮改用 5.3 的 namespace 功能加上類似ZF和PEAR的目錄結構來發展與佈署程式。
我放了一份修改過的 PortableAPM 在這裡,需要的人請自行取用。

使用時要注意 mysql 的部份,由於新引進 mysqlnd 的關係,所以即便是可以用 dl 來載入,對於 mysql 相關的 extension 最好在 php.ini 裡先載入。

剛剛找到這個網頁,裡面的資訊表示了PEAR2要採用名稱空間的作法。

2008/03/28

單例模式

看了網路上幾個用PHP 實現單例模式的作法,我寫了一個比較簡單且通用的方式。
程式碼如下:

class _單例{
    final protected function __construct(&$類別, &$參數){
        $函數 = array($this, '_' . $類別);
        if (is_callable($函數)) {
            call_user_func_array($函數, $參數);
        }
    }

    static function &實例($類別, $參數 = array()){
        static $實例 = array();
        $類別 = (string)$類別;
        $輸出 = &$實例[$類別];
        if(false == isset($輸出) || false == is_object($輸出)){
            $輸出 = new $類別($類別, $參數);
        }
        return $輸出;
    }
}


用法範例:

class 測試 extends _單例 implements __單例{
        protected function _測試($數字, $字串){
            echo (string)$數字, $字串, '<br>';
        }
       
    static function &實例(){
        $參數 = func_get_args();
            return parent::實例(get_class(), $參數);
    }
}

$結果 = 測試::實例(5, 'years');

2008/03/24

DB Layer 目前的進度

這次 DB Layer 的研發要告一段落,目前對於 MySQL 的資料庫已經可以做到匯入匯出的功能了,所以明後天程式重整完以後,接下來要先去作案子來把進度趕一下。

2008/03/20

插件用的類別

話說 adodb lite 這套資料庫用的類別庫已經很久沒更新了,這套小巧的類別庫有一個很棒的功能,就是可以寫插件。
不過他寫插件的方法我不是很喜歡,正好我最近開始復工在寫的DB Layer也碰到這樣的難題:我想把功能分割出來,寫成可以共同合作的插件,需要什麼功能可以隨時加進去。
另外還有一點,我希望使用插件的方法時,其調用的方式等同於使用繼承的方式。

因此,我利用了PHP5的反映的功能,寫了一個插件用的抽象類別,只要繼承它,就可以簡單的寫出插件以及加入插件的功能。
底下是程式碼:



abstract class _插件{
    private $_插件 = array();
    private $_方法 = array();

    function __construct(&$插件 = null){
        $this->__插件($插件);
    }

    final function &__call($方法, $參數){
        $輸出 = call_user_func_array($this->_方法[$方法], $參數);
        return $輸出;    
    }

//    將插件的方法加入$this的方法中
    final protected function __插件(&$插件, $更新 = false){
        static $類別 = __CLASS__;
        if (false == ($插件 instanceof $類別)) {
            return false;
        }

        $名稱 = get_class($插件);
        if (false == array_key_exists($名稱, $this->_插件)){
            $this->_插件[$名稱] = $插件;
            $this->_方法 = $插件->__方法() + $this->_方法;
        }
        if ($更新) {
            $this->__更新();
        }
        return true;
    }

//    取得插件的方法(除了方法名稱開頭是'__'的方法,例如一些魔術方法,或是方法本身屬於私有方法)
    final protected function &__方法(){
        static $_方法 = array();
        if (false == empty($_方法)) {
            return $_方法;
        }
        $反映 = new ReflectionObject($this);
        $方法 = $反映->getMethods();
        unset($反映);
        foreach ($方法 as &$項目){
            $名稱 = &$項目->getName();
            if (0 !== strpos($名稱, '__') && false == $項目->isPrivate()) {
                $_方法[$名稱] = array($this, $名稱);
            }
        }
        return $_方法; 
    }
   
//    更新所有插件的方法,通常在載入所有插件後調用
    final protected function __更新(){
        static $類別 = __CLASS__;
        foreach ($this->_插件 as &$插件){
            if($插件 instanceof $類別) {
                $插件->_方法 = $this->_方法 + $插件->_方法;
            }
        }
    }   
}



這裡有個簡單的範例。
plugin.php => 插件的抽象類別檔案 (內容就是上述的抽象類別)
test01.php => 主要的範例檔案
test02.php => 插件的檔案

test01.php:

require_once dirname(__FILE__) . '/plugin.php';
class 測試 extends _插件 {
    function 載入插件($插件) {
        require_once dirname(__FILE__) . "/$插件.php";
        $類別 = __CLASS__ . ":$插件";
        $插件 = new $類別($this);
        $this->__插件($插件);
    }
   
    protected function 測試01(){
        echo __METHOD__ , '<br>';
    }
}

$測試 = new 測試();
$測試->載入插件('test02');
$測試->測試02();


test02.php:

require_once dirname(__FILE__) . '/test01.php';
class 測試:test02 extends _插件 {
    function 測試02(){
        echo __METHOD__ , '<br>';
        $this->測試01();
    }
}


str_replace 和 strtr不同的地方

一直以來以為這兩個函數是等價的,今天心血來潮想知道這兩者在效率上有何差異,所以在網路上找了一些文章來看。
找到了CSDN的這篇討論,裡面說的很清楚,這兩個函數還是存在微妙的差異:在strtr中的替換字串長度必須等長,輸出結果才會與str_replace等價。

2008/03/03

PHP 檔案管理記要

今天早上我老闆問了我一個問題:為何他在IE裡面下載SugarCRM裡面有中文檔名的檔案,存檔時帶出的檔名變成亂碼?

我直覺的覺得是IE和FF對於header的解釋不同的緣故,因為上禮拜我把SugarCRM裝好後有試過,在FF中是正常的~
為了證實我的想法,我把SugarCRM的download.php翻出來看了一下,果然發現一段if else的結構證明我的想法沒錯,兩種瀏覽器對於檔名解碼的方式有不同的作法。
現在重點來了,怎麼做才是正確的?

開了MSN問了一下Kiang的意見,他給了一個提示:urlencode。
我試了幾種方式,發現在檔名編碼的部份,IE應該用urlencode,FF應該用mb_encode_mimeheader的QP編碼方式來處理檔名的部份。

因此,既然可以完成檔名的處理,我接著觀察SugarCRM處理檔案的方式。
很顯然的,他把上傳後的檔案「暗號化」,然後以此「暗號」查詢資料庫,取得原始的檔名,而上述的download.php則是統一的取檔窗口,這樣的方式便可以避免去處理Windows的檔案系統編碼的問題。

我認為這是個很好的方式,記起來以免忘記~

2008/02/28

SugarCRM的翻譯工作

這幾天在做SugarCRM 5.0.0b的翻譯工作,昨天開始正式動手,先從install的部份開始做起。
由於官方的wiki文件語焉不詳(好啦~我承認我的英文爛),所以去翻源碼找出手動掛載的方式。

先將SugarCRM 5.0.0b未翻譯的中文檔下載回來解開,並將未翻譯的中文檔的內容複製到SugarCRM的目錄中。
編輯SugarCRM目錄下的install.php,尋找以下幾行:
$supportedLanguages = array(
    'en_us'    => 'English (US)',
    'ja'    => 'Japanese - 日本語',
    'fr_fr'    => 'French - Français',

在其下增加:
    'zh_tw'    => 'Traditional Chinese - 正體中文',

這樣就能在安裝時選擇正體中文的翻譯了。(當然要等翻譯完成後才有正式的語言包可以用啦~)

而安裝完後,還要修改SugarCRM目錄下的config.php
找到:
  'languages' =>
  array (
    'en_us' => 'US English',

在其下增加:
    'zh_tw'    => 'Traditional Chinese - 正體中文',

這樣就可在使用時選擇正體中文了。(當然要等翻譯完成後才有正式的語言包可以用啦~)

步驟是相似的,所以有回音~
而我使用的編輯器是PDT,雖然有點大,但是功能完整,讓我不需要離開這個IDE就可以完成翻譯的工作,很棒!
下載回來解開後,可以在桌面上做一個捷徑方便工作。首先要做的設定就是改文字編輯器的Code Page,從功能列:
Windows -> Preference -> General -> Workspace 中修改 Text file encoding,
選擇 Other: UTF-8 然後按OK離開即可。

開始翻譯之前,我要先寫個批次檔,把翻譯完的檔案複製到SugarCRM的目錄中,這裡假設翻譯中的檔案放在sugar底下,而SugarCRM的目錄在SugarCRM。
底下是批次檔的內容:
@echo off
xcopy sugar\*.* SugarCRM\ /S /Y

然後在PDT裡面把這個批次檔掛到External Tools裡面,這樣以後翻譯時就改sugar底下的檔案,改好後執行這個工具,然後就可以在內建的瀏覽器看結果。

2008/02/24

支援一些特殊功能的NAS

話說這禮拜絕大部分的時間在搞那台Dell的伺服器,測試了數種OS安裝跟使用的情況,雖說花時間也是有些許心得。
週末與友人一邊吃涮涮鍋,一邊公幹某編輯的不當行為時,我突然靈光一閃,想到一個簡單解決檔案分享的問題。

原本我是計畫用那台Dell的機器當做檔案分享、開發測試環境以及給客戶看的展示網站之用,不過經歷了一些不太愉快的過程後,目前我思考將部份功能拆出來,用可以連接網路的外接設備來解決。
我找了一下相關的NAS產品,功能性方面較完整的有QNAP的TS系列、Synology的DS系列以及之前炒得很熱的PCI的NAS-01G的產品。
NAS-01G由於內建記憶體較少,所以應該是不會建議這台。
而QNAP和Synology的產品目前看來在PHP和MySQL的版本上沒有差異,不過QNAP官網的連線速度較慢,也沒設置中文的討論區,而且韌體更新速度也不如Synology。
所以如果在兩者擇一的話,我應該會選擇Synology的產品。

若只針對檔案分享的部份,還有另外一個比較省錢的選擇,那就是ViPower的VPA-3532Net,不過缺點就是只能接IDE的硬碟,該公司並沒有對應SATA硬碟的同類型產品。

2008/02/23

AMD 的 Turion 64 X2 相關資料

因為某些緣故,我在調查AMD 的 Turion 64 X2 相關資料,主要有三個方面:價格、功耗、時脈
價格可以在這裡查到。
功耗跟時脈可以在比較表中查到。

2008/02/21

Dell 的伺服器安裝雜記

話說前兩天我們公司訂購的Dell伺服器送到了,我跟我的老闆都驚訝於Dell的送貨效率,因為我們上個禮拜五下訂單,星期二就收到了,只有短短的三個工作天。
這可是我人生中第一次摸到真正的伺服器呀~

不過接下來就令人有點小挫折,翻開手冊看看支援的作業系統:2K3、RH、SuSE,就這樣。
後來我才搞清楚,那些是導引光碟所支援的,其實只要找的到驅動程式,裝其他的作業系統是不成問題的。

另外一個問題是,這台機器有一張LSI SAS 5/i 的RAID卡,裝它的驅動程式很麻煩,在開機階段我插上USB的隨身碟又常常讀不到。
後來就放棄了,去買一條SATA的線,把這張卡拔掉,將硬碟直接接到主機板上,接下來一切都很愉快~

由於我對XP比較熟,所以就裝XP囉,裝好後有三個Device需要自己裝驅動程式,分別是:Base System、顯示卡、網路卡。
除了Base System以外,其他都可以找到驅動程式,我在Dell的網站上也找不到這個東西的驅動程式,後來問了某學長,他說那個東西要先用導引光碟裝才有。
我想了一下,反正不影響伺服器的運作,所以我就沒裝這個東西啦~

2008/02/20

瀏覽器升級到 Firefox 2.0

追記:過了一年,有些東西有些出入,更新一下
瀏覽器: Firefox Portable 2.0.0.11

附加元件我裝了以下的東西:


另外我已經把 Live 書籤的部份換成靜態的書籤,因為每次一起動,火狐老是會去逛一圈,都不會作 cache 的說。

2008/02/15

部份無法掛入的extension

下列這些extensions有些是因為缺少其他dll而無法掛入,有些則是掛入後PHP執行不正常。
;extension=php_blenc.dll
;extension=php_ibm_db2.dll
;extension=php_ifx.dll
;extension=php_ingres2.dll
;extension=php_maxdb.dll
;extension=php_mcve.dll
;extension=php_netools.dll
;extension=php_oci8.dll
;extension=php_oracle.dll
;extension=php_pdo_ibm.dll
;extension=php_pdo_informix.dll
;extension=php_pdo_oci.dll
;extension=php_pdo_oci8.dll
;extension=php_pdo_sqlite_external.dll
;extension=php_pspell.dll
;extension=php_sam.dll
;extension=php_svn.dll
;extension=php_sybase_ct.dll
;extension=php_threads.dll

另外,有某些extension需要mbstring先掛入,因此我將mbstring放在第一順位來掛入。
Xdebug因為沒有提供給nts的編譯好的dll,所以在FCGI模式下要使用的話,要自行編譯。
XCache有提供給ts/nts的編譯好的dll,所以可以正常掛入使用。
Apache Module模式和CGI模式下可用的extension有些出入,但兩種模式都有100個左右的extensions可用!

2008/02/08

Windows 下 Apache 2.2.x + mod_fcgid + PHP 設定配置

話說日前在比對某個Hosting的環境是否符合某系統的需求時,發現該系統需要ImageMagick的extension才能執行,然後我就想在我的Windows XP的環境下把該extension掛起來,可惜的是,預設的 Thread Safety 版本的PECL中並沒有編好這個extension。
不過天無絕人之路,我發現在Non-Thread Safety (NTS) 的PECL中有發現,不過問題來了,我該如何設定 NTS 版本的 PHP 環境呢?
準備:
  1. 下載 Apache 與 mod_fcgid :可以在 Apache Lounge 中找到下載點,請注意不要拿官網的 Apache 去配合該 mod_fcgid,一定要用 Apache Lounge 中的 Apache 來裝,否則會掛不起來。
  2. 下載 Runtime:可以選擇 Visual C++ 2008 Redistributable Package 或是 .Net Framework 3.5,個人建議用 .Net Framework。
  3. 下載 NTS 版本的 PHP 與 PECL :在官網就有提供下載點,請注意要下載的是 Non-Thread Safety 的那兩個 zip 檔。
安裝:
  1. 先安裝Runtime,然後把 Apache、mod_fcgid、NTS 版的 PHP 和 PECL 解開。
  2. 假設 Apache 是放在 C:\Apache2 中,PHP 是放在 C:\PHP5 中。
  3. 把 mod_fcgid 中的 mod_fcgid.so 複製到 Apache 下的 modules 目錄裡 (C:\Apache2\modules)。
  4. 把 PECL 裡的所有檔案複製到 PHP 下的 ext 目錄裡 (C:\PHP5\ext)。
  5. 將 C:\PHP5 加入 PATH 環境變數中
修改設定:
假設網站放在 C:\WebSite 中,修改 C:\Apache2\conf\httpd.conf,加入以下設定

<IfModule userdir_module>
    UserDir C:/WebSite
</IfModule>

LoadModule fcgid_module modules/mod_fcgid.so
<IfModule fcgid_module>
AddHandler fcgid-script .php
FCGIWrapper "C:/PHP5/php-cgi.exe -c C:/PHP5/" .php
</IfModule>

<Directory "C:/WebSite">
    DefaultInitEnv SystemRoot "C:/Windows"
    Options ExecCGI
    allow from all
</Directory>

修改完後開啟 DOS 視窗,進入 C:\Apache2\bin,執行

httpd -k install

註冊 Apache 的 Service即可

2008/01/25

離職的時間確定~

今天發生了一些意外,造成離職申請意外的順利,而且還可以提早走人。
沒有意外的話,我會在2008/01/31離開目前的公司~

感謝這近一年來一起走過的同事們,希望你們能夠越來越好!

2008/01/24

準備要換工作了

目前這份工作堅持了近一年,不過還是決定換工作,回到我喜歡的軟體業界。
我將於2008/02/15正式離開我目前的工作,也就是俗稱的年後離職潮~
新的公司跟工作已經決定好了,由於新工作是屬於程式設計性質,所以往後這裡的文章比較常更新。

今天回了幾篇留言,也去留言版那邊看看,發現 PHPChina 那邊有意思要合作,由於我還沒確認對方身份,所以不便回信。
再者過了快一個月才回覆,也不太好意思。不過 TWPUG 的站長是 Kiang,所以我會把那個訊息告知他。

另外,我也申請了Google 的 AdSense,純粹好玩,弄起來看看~

Visual Studio 2008 Express Edition All-in-One DVD

這次微軟蠻上道的,已經自己整理好了,在這裡可以下載。

2008/01/22

1 w = 683 lm 正式確定是錯誤的

在 LED inside 出現了「日本北海道大學研發出發光強度為目前最亮LED之20倍的超導LED」這篇文章後,之前被引用的 1 w = 683 lm 這個公式正式被確認是不成立的!

根據文中指出:對比發現,新型超導技術LED的發光強度是目前最亮LED的20倍。
這句話來看,我們可以知道這款LED的每瓦流明數為何。

目前我所知道的最亮的LED是CREE的實驗品(請見:「Cree大功率LEDs可達129 lm/W,創新紀錄」),其每瓦流明數為129 lm/W,20倍就是2580 lm/W。
即便我誤解他的意思,假設他指的是用目前市面上可買到的量產品來比較的話,以目前來說,大約是50 ~ 60 lm/W,其20倍也在1000 lm/W以上,對於結論一樣是正確的。

P.S. 發光強度的單位是燭光,在相同的光照角的條件下,燭光和流明成正比,所以發光強度的20倍可視為光通量20倍同義。

2008/01/02

LED 光、電、熱轉換效率的探討

今天在 LED inside 的知識庫看到這篇「淺談LED的熱量產生原因」,其結論是:

LED 的產熱量與光效無關;不存在百分之幾的電功率產生光,其餘百分之幾的電功率產生熱的關係。

簡單的說,燈具的研發若以 1W = 683 lm 的理想值和 LED 晶片的發光效率來做比較,推估所產生熱量的方式,有很大的漏洞。
從巨觀而言,我們會假設光電轉換的極限效率為一定值,也就是 683 lm/w,就此文章的結論而言,似乎違反能量守恆定律,但是若我們的假設錯了呢?

查看 683 lm/w 這個比例的意義是:

國際約定波長為555nm的單色光發光效率定為1,此光每1W 輻射通量具有683流明的光通量。

在這段敘述裡面,沒有提到電的問題,而這個比例又被稱作最大光譜光視效能。
嚴格來說,這個國際約定的比例,其實是:

光源發出的輻射通量可產生多少能對目視引起刺激的光通量。

也就是說,這個比例值並非是光電轉換效率的最大比例值。而光電轉換的比例值,實際上或許有可能受其他物理條件的影響,造成這個值並非定值。

另外一提的是波長555nm的光的顏色是介於黃色與綠色之間的顏色。
底下是RGB三原色的波長範圍(單位:nm)
  • 紅:620.5~645
  • 綠:520~550
  • 藍:460~490
追記:剛剛透過水球介紹,請教了塔麵長輩(御宅族美食達人),他的意見是
  • 半導體轉換出來的光子並不會100%飛出半導體外
  • 這些沒有飛出半導體外的光子會轉換成熱能
  • 因為上述原因,同體積的LED,越高效的熱阻也越高
這當中當然還有些量測方面問題,不過已經具有很強的解釋力去解釋「越高效的熱阻也越高」的現象。

與君一席話,豁然開朗~