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的檔案系統編碼的問題。

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