2007/01/30

xdebug 的編譯方式

這個東西的編譯方式也不太一樣,他需要先把 PHP 編完後,才能編這個,而且需要將 xdebug.dsp 轉成 xdebug.vcproj 並修改一些地方才能編譯,不過我已經幫你準備好轉好的檔案。
步驟如下:
  1. 源碼:下載
  2. 輔助編譯工具包需使用 2007013001 以後的版本。
  3. 解開後放到 D:\Work\PECL\xdebug 下。
  4. 編譯 PHP 。
  5. 執行 D:\Work\buildtools\PECL\xdebug.bat
  6. 編譯完成後安裝請執行:php_make.bat install
  7. 修改 php.ini 設定
  8. 靜態連結:no
  9. 動態連結:yes

敗部復活!編譯 PHP 成功

感謝石頭的鼓勵。前幾天可能是太累了,漏看了最後一個步驟...

2007.01.30 追記:
調整一些說明。換掉 Windows SDK 有兩個因素:
  1. Windows SDK 的檔案太新,編譯時常會出現一堆警告,有時還會編譯失敗,所以我把他的順序放到後面,以 Platform SDk 為主。
  2. 有些 Extensions 有提供 .dsp 的檔案,常會需要用 VCE 來匯入轉檔,所以我乾脆就把 VCE 拿來換掉 Windows SDK。

我的建構平台是 Windows XP SP2(x86),我寫了幾個批次檔來輔助編譯的過程。
編譯所需檔案:

安裝編譯工具時要注意的地方:
  • Platform SDK:建議用預設的選擇。
    • Platform SDK 我安裝到 C:\Program\Microsoft Platform SDK。
  • Windows SDK:如果不編 com-dotnet 的 Extenstion 可不裝,也可用 .NET Framework SDK 取代這個。
    • 若要安裝只需裝 Developer Tools 下的 Windows Vista Headers and Libraries,其他的不用選。
    • Windows SDK 我裝到 C:\Program\Microsoft SDKs。
  • Visual C++ 2005 Express Edition(VCE):只需要安裝 Graphics IDE 即可,其他的不用選。
    • 建議加裝 SP1。
  • 如果你裝到不同的目錄,記得修改輔助批次檔內的 setenv.bat

準備目錄:
  • 先將編譯工具安裝好,然後建立一個目錄作為編譯之用,底下以 D:\Work 作為範例說明。
  • 將 PHP 相關檔案(源碼、win32 buildtools、DNS name resolver、輔助編譯包)解壓縮至 D:\Work 下,目錄會長的像
+--D:\work
| |
| +--bindlib_w32
| | |
| | +--arpa
| | |
| | +--conf
| | |
| | +--...
| |
| +--php-5.x.x
| | |
| | +--build
| | |
| | +--...
| | |
| | +--win32
| | |
| | +--...
| |
| +--win32build
| | |
| | +--bin
| | |
| | +--include
| | |
| | +--lib
| | |
| | +--...
| |
| +--buildtools
| | |
| | +--bin
| | |
| | +--PECL
| |
| +--PECL
| | |
| | +--...
| |

  • 輔助編譯包的說明請看:這裡

編譯流程:
  1. 請切換至純英文環境,再進行編譯:有些源碼檔案內容有些中歐字元,在中文環境下會發生問題,必須切換至英文環境才不會出問題,另外使用 Apploc 來欺騙是行不通的,請切換成英文環境再編譯。
  2. 檢視並修改 env_*.bat 與 php_conf.bat 使其符合你的編譯需求,底下步驟若發生錯誤也請回來修改這兩個檔。
  3. 執行 env_set.bat
  4. 執行 php_conf.bat
  5. 執行 php_make.bat rebuild
  6. 執行 php_make.bat
  7. 執行 php_make.bat install

預設的安裝目錄是在 C:\php5,你可以從這裡拿到編譯好的檔案。
執行時需要 msvcr80.dll ,這個檔案可以在 %windir%\WinSxS\x86_Microsoft.VC80.CRT_xxx 開頭的目錄中找到。
註:%windir%是指你的Windows XP的安裝目錄,預設是 C:\Windows

其他有關 extension 編譯時需要的檔案、要去哪裡找等問題,等我研究清楚後再發表。
不過我發現,網路上有些現成已編好的 lib,有些在編譯過程中無法連結,原因是因為那些 lib 是用 VC6 以前的版本編的,或是非 VC 的編譯器編的,我個人不傾向自己編這些 extension ,不過要找到用 VC7 以後編出來的 lib 有點難度。

PHP 在 WIndows XP 下的輔助編譯包 (2007013001 版)

以後輔助編譯包(buildtools.zip)的文件就固定在這裡,有變更會隨時修改,編譯流程請看這裡

假設編譯的工作目錄在 D:\Work,那麼請把下列檔案解開到 D:\Work 下:

編譯時請開一個空白的 CMD,不要用SDK裡面的捷徑,因為相關的環境設定已經寫在輔助編譯包內的批次檔中了。
編譯包的內容說明:
  • env_set.bat:編譯環境建立,會設定相關所需的環境參數,若你的編譯環境不同於預設,請自行修改 env_*.bat。
  • env_clean.bat:清除各項環境變數。
  • env_wsdk.bat:建立 / 清除與 Windows SDK 相關的環境變數。
  • env_psdk.bat:建立 / 清除與 Platform SDK 相關的環境變數。
  • env_vce.bat:建立 / 清除與 VC++ 2005 Express Edition 相關的環境變數。
  • env_php.bat:建立 / 清除與 PHP 編譯相關的環境變數。
  • env_add_group.bat:將某個目錄下所有子目錄中的 bin 加入 PATH、include 加入 INCLUDE、lib 加入 LIB 環境變數中。
  • env_add_dir.bat:將某個目錄下的 bin 加入 PATH、include 加入 INCLUDE、lib 加入 LIB 環境變數中。
  • bindlib.vcproj:已經轉檔好的 DNS name resolver 的 VC++ Project 檔,用新版的 VC++ 必須使用這個檔才能編譯。
  • bindlib.bat: 編譯 DNS name resolver 的批次檔,會自動建立 resolv.lib 並將檔案複製到 D:\Work\win32build\lib 中。
  • php_conf.txt:編譯 PHP 時下的參數說明,請根據你所需要的參數修改 php_conf.bat 中 cscript 那一行。
  • php_conf.bat:可用 --help 來觀看參數說明,內容同 php_conf.txt,不下參數時表示要設定編譯 PHP 時的參數用的批次檔。
  • php_make.bat:編譯 PHP 用批次檔,不下參數時會進行編譯,且編譯完會自動 Embed Manifest 編譯好的 .exe .dll 檔案,同時可選用以下參數:
    • rebuild:包含 resolv.lib 都重新編譯。
    • clean:清除編譯過程中的所有檔案。
    • install:安裝編譯好的 PHP 檔案。
    • dist:將編譯好的 PHP 檔案打包成 zip 壓縮檔。
  • convert.js:從微軟的站上看到的,據說可以把 VC++ 6 的專案檔(.dsw)轉換成 VC++ 8 的專案檔(.vcproj),不過我沒執行成功過。
  • bin 目錄:編譯 PHP 及其 PECL 所需的各項檔案,大部分可以從 GnuWin32 裡找到。
  • PECL 目錄:編譯個別的 PECL 所需的各項檔案。

2007/01/29

IMAP 的編譯方式

IMAP 的編譯方式比較奇特,請先看一下解開後的 PHP 源碼目錄中的 ext\imap\IMAP_Win32_HOWTO.txt 這個檔案。
  1. IMAP 源碼:下載,請下載 imap.tar.Z 這個檔案。
  2. 解開後放到 D:\Work\test\imap 中,編輯 D:\Work\test\imap\src\osdep\nt\makefile.nt 這個檔案,找到 CFLAG 那行,把 /MT 改成 /MD。
  3. 切換目錄到 D:\Work\test\imap 中,執行 nmake -f makefile.nt,過程中最後會編譯失敗,但我們仍然可以得到需要的 *.h 及 cclient.lib。
  4. 將 D:\Work\test\imap\c-client\*.h 複製到 D:\Work\win32build\include 中。
  5. 將 D:\Work\test\imap\c-client\cclient.lib 複製並取代 D:\Work\win32build\lib\cclient.lib。
  6. 修改 php_conf.bat 加上參數 --with-imap=shared(IMAP 這個 Extension 只能以動態連結的方式編譯)
  7. 重新編譯 PHP,執行:php_make.bat
  8. 編譯完成後安裝請執行:php_make.bat install
  9. 修改 php.ini 加上 extension=php_openssl.dll
  10. 靜態連結:no
  11. 動態連結:yes

OpenSSL 的編譯方式

安裝步驟:
  1. 網路上我有找到預編好的 lib 可以使用,在這裡下載
  2. 下載後安裝起來,預設會裝到 C:\OpenSSL 下,請把 C:\OpenSSL\include\openssl 複製到 D:\win32build\include\openssl 下。
  3. C:\OpenSSL\lib\VC 下有許多 libeay*.lib 和 ssleay32*.lib 檔,請選擇相同字尾的 libeay*.lib 和 ssleay32*.lib 各一,複製到 D:\win32build\lib 下,並更名為 libeay.lib 和 ssleay32.lib。
  4. 主檔名最後是 d 的表示是 Debug 版,沒有 d 的是 Release 版
    • MT 表示 Multi-threaded, statically linked  - libcmt.lib,MD 表示 Multi-threaded, dynamically linked - msvcrt.lib
    • 個人建議使用 MT 系列的。
  5. 修改 php_conf.bat 加上 --enable-openssl=[shared] 參數。
  6. 重新編譯 PHP,執行:php_make.bat
  7. 編譯完成後安裝請執行:php_make.bat install
  8. 若使用動態連結,請修改 php.ini 加上 extension=php_openssl.dll
  9. 靜態連結:yes
  10. 動態連結:yes

2007/01/28

將 Suhosin Patch 加入 PHP 中編譯

Suhosin 是一個關於 PHP 安全方面的 Patch,FreeBSD 內建已經會將此 Patch 加入 PHP 編譯。
Suhosin有兩種方式可以增加 PHP 的安全性,一種是使用 Patch 的方式,另一種是使用 Extension 的方式,兩種方式也可以同時使用。

在 Windows 下也可以作這項 Patch,請看以下的步驟來進行:
  1. 下載 Patch ,請注意你所使用的 PHP 版本:下載,這裡假設 Patch 檔的檔名為 suhosin-patch-5.2.0-0.9.6.2.patch。
  2. 工具:patch,可以從 GnuWin32 上取得,或是下載最新的輔助編譯包,裡面也有這些工具。
  3. 將 Suhosin 的 Patch 解開放到 D:\Work 中。
  4. 請切換目錄至 php-x-x-x 中,執行:patch -binary -p 1 -i ../suhosin-patch-5.2.4-0.9.6.2.patch
    • 註:在 Windows 下作 Patch 一定要使用 -binary 參數,這是由於 Windows 與 *nix 中的換行符號不同所必須注意的地方
  5. Patch 完後請重新編譯 PHP,執行:php_make.bat rebuild
  6. 編譯完成後安裝請執行:php_make.bat install

如果你想用 Extension 的方式,請看以下的步驟來進行:
  1. 下載源碼,請注意你所使用的 PHP 版本:下載,這裡假設檔名為 suhosin-0.9.16.tgz。
  2. 將源碼解開,複製其中的 suhosin-0.9.16 到 D:\Work\suhosin 中。(記得目錄名稱中不可有減號 - ,否則 nmake 會無法編譯)
  3. 修改 php_conf.bat 加上 --enable-suhosin=[shared] 參數。
  4. 重新編譯 PHP,執行:php_make.bat
  5. 編譯完成後安裝請執行:php_make.bat install
  6. 若使用動態連結,修改 php.ini 加上 extension=php_suhosin.dll
  7. 靜態連結:yes
  8. 動態連結:yes

XCache 編譯方式

Xcache 屬於 PECL 的 Extension,所以請照下面的步驟來編譯:
  1. 源碼:下載
  2. 工具:m4、grep、sed、gawk、cat 都可以在 GnuWin32 中找到,記得要把 Binaries 和 Dependencies 都抓下來,或是下載最新的輔助編譯包,裡面也有這些工具。
  3. 建立 D:\Work\PECL 目錄,並將源碼解壓縮至 D:\Work\PECL\Xcache 下
  4. 修改 D:\Work\PECL\Xcache\Makefile.frag 的第 21 行:
    • 將 $(GREP) 'export: ' $(XCACHE_PROC_OUT) | $(SED) 's/.*export:\(.*\):export.*/\1/g' | $(XCACHE_INDENT) > $(XCACHE_PROC_H).tmp 改成:
    • $(GREP) "export: " $(XCACHE_PROC_OUT) | $(SED) "s/.*export:\(.*\):export.*/\1/g" | $(XCACHE_INDENT) > $(XCACHE_PROC_H).tmp
    • 也就是將單引號改為雙引號,因為 GunWin32 中的 grep 和 sed 不認得單引號。
  5. 執行 php_config.bat --help 看看有哪些可用的參數,根據你的需要修改 php_conf.bat
    • 目前經我的測試,可用的參數有:
      • --enable-xcache
      • --enable-xcache-optimizer
      • --enable-xcache-coverager
      • --enable-xcache-assembler
      • --enable-xcache-encoder
      • --enable-xcache-decoder
    • 無法使用的參數有:
      • --enable-xcache-disassembler
      • --enable-xcache-test
    • 設定要作動態連結就只需要修改 --enable-xcache=shared 即可,其他的參數無須加上 =shared,預設會使用 --enable-xcache=shared 來編譯,若要作靜態連結,請設定 --enable-xcache
  6. 修改完後請執行:php_make.bat
  7. 編譯完成後安裝請執行:php_make.bat install
  8. 修改 php.ini 加上 xcache 相關的設定,但有一個設定要注意:
    • xcache.shm_scheme = "malloc"
    • 這一項設定請使用 malloc ,預設是 mmap ,但經我測試若設定成 mmap 則 xcache 無法啟動。
  9. 靜態連結:yes
  10. 動態連結:yes

2007/01/26

bz2 編譯方式

我會慢慢將每個 extension 測試看看,盡量詳細的說明該如何編譯。
下載跟解壓縮我就不多作說明,編譯前請記得 setenv。

lib 編譯階段:
  • 源碼:下載
  • 說明檔:README
  • 編譯方法:nmake -f makefile.msc

PHP 編譯階段:
  • 必要 include:bzlib.h
  • 必要 lib:libbz2.lib
  • 參數:--with-bz2
  • 靜態連結:yes
  • 動態連結:yes

bz2 算是很簡單的,一下子就編譯成功了。

2007/01/24

放棄在 Windows 下編譯 PHP

兩個禮拜前,我就想為量測增加一個跟記憶體使用量的功能,但是這個功能需要使用到 memory_get_usage 這個函數,而這個函數需要在編譯時指定 --enable-memory-limit 參數才能用,故我自己試試看編譯 PHP。

我看了官方網站的文件,到微軟下載相關檔案,不過我沒有 VC++ 6.0,所以我拿 VC++ 2005 Express 來實驗。
網路上我找到這篇文章,文章很長但解釋的蠻清楚,可惜的是我做到最後一步,碰到 VC++ Runtime 的問題,就過不去了,執行後總會出問題。

這次實驗花了太多時間(有一半以上的時間我跑去搞Visual Studio 2005 Express SP1 DVD Pack (偽)),所以我決定停止,一方面是時間不允許,另一方面我實在覺得編譯的過程非常的繁瑣,我不曉得下次 PHP 新版出來時會不會支援其他的編譯方式,短時間內我是不會在進行實驗了。

至於 memory_get_usage 的議題,我會參考已有的替代方案,將期功能加入新版的量測中。

2007/01/23

Visual Studio 2005 Express SP1 DVD Pack (偽)

不要被標題嚇到了,這篇文章沒有教你如何用非法途徑拿到微軟的產品,這裡所提到的東西都是可以從微軟網站上合法且免費取得的,我只是教你如何把這些開發工具塞到一張DVD裡面。

準備下載:
  • .NET Framework 3.0(包含2.0)
  • Visual XXX 的光碟映像檔(下載
    • Visual Basic 2005 Express Edition
    • Visual C++ 2005 Express Edition
    • Visual C# 2005 Express Edition
    • Visual J# 2005 Express Edition
    • Visual Web Developer 2005 Express Edition
  • Visual XXX SP1(下載
    • Visual Basic 2005 Express Edition SP1 - VS80sp1-KB926747-X86-INTL.exe
    • Visual C++ 2005 Express Edition SP1 - VS80sp1-KB926748-X86-INTL.exe
    • Visual C# Express Edition SP1 - VS80sp1-KB926749-X86-INTL.exe
    • Visual J# Express Edition SP1 - VS80sp1-KB926750-X86-ENU.exe
    • Visual Web Dev Express Edition SP1 - VS80sp1-KB926751-X86-INTL.exe
  • Visual XXX Add-on
    • Reporting Add-In for Microsoft Visual Web Developer 2005 Express(下載
    • XNA Game Studio Express 1.0(需先安裝 Visual C# 2005,下載
  • SQL Server 2005 Express Edition(下載
    • SQL Server 2005 Express Edition with Advanced Services SP1
    • SQL Server Express Toolkit(如果不需要 Business Intelligence Developer Studio 可以不用下載,微軟站上有張比較表
    • SQL Server 2005 Samples
    • SQL Server 2005 Books Online
  • Windows SDK for Windows Vista ISO(下載
  • Windows Server 2003 R2 Platform SDK(下載
  • DirectX SDK(End-User Runtimes 在解開來的 Redist 目錄下有,下載
整合Visual XXX:
  1. 新增一個新的目錄(底下以 D:\Express 舉例說明)
  2. 將 Visual XXX 的光碟映像檔用虛擬光碟工具掛上,並將檔案複製到 D:\Express 中
    • Visual Web Developer 2005 Express Edition(D:\Express\VWD)
    • Visual Basic 2005 Express Edition(D:\Express\VB)
    • Visual C# 2005 Express Edition(D:\Express\VCS)
    • Visual C++ 2005 Express Edition(D:\Express\VC)
    • Visual J# 2005 Express Edition(D:\Express\VJS)
  3. 將 D:\Express\VJS\wcu 移到 D:\Express\wcu(一定要用這個,其他的少了Visual J# 2.0 Redistributable Package)
  4. 刪除其他目錄下的 wcu
  5. 修改 D:\Express\VWD、D:\Express\VB、D:\Express\VCS、D:\Express\VC、D:\Express\VJS 中的setup.sdb :
    • 將 BaselineFolder=.\WCU 改成 BaselineFolder=..\WCU
    • 將所有的 wcu\ 取代成 ..\wcu\
  6. 將 Visual XXX SP1 放到對應的目錄下的 SP 目錄中
    • VS80sp1-KB926747-X86-INTL.exe:D:\Express\VB\SP
    • VS80sp1-KB926748-X86-INTL.exe:D:\Express\VC\SP
    • VS80sp1-KB926749-X86-INTL.exe:D:\Express\VCS\SP
    • VS80sp1-KB926750-X86-ENU.exe:D:\Express\VJS\SP
    • VS80sp1-KB926751-X86-INTL.exe:D:\Express\VWD\SP
取代檔案(非必要):
  1. 底下幾個東西可以使用新版的安裝檔來取代舊的,不過我個人不建議這麼作,要修改起來很麻煩
    • .NET Framework
    • SQL Server 2005 Express Edition
  2. 取代的方式有兩種:一是更名取代,也就是把新版的檔案名稱更名為舊版的檔案名稱並取代之,另一種則是修改 Vistual XXX 的目錄中的 baseline.dat,我會以 .NET Framework 說明第一種作法,SQL Server 2005 Express Edition 說明第二種
  3. 把下載回來的 .NET Framework 3.0 放到 D:\Express\wcu\dotNetFramework(x86)與 D:\Express\wcu\dotNetFramework\x64(x64)下,並更名取代原有的 dotnetfx.exe(x86)與 NetFx64.exe(x64)
  4. 將 SQL Server 2005 Express Edition with Advanced Services SP1 的檔案放到 D:\Express\wcu\SSE 下,並修改 Vistual XXX 的目錄中的 baseline.dat ,搜尋 SQLEXPR32.EXE 與 SQLEXPR.EXE,以新版的檔名取代之。
其他檔案:
  1. 將 Windows SDK for Windows Vista ISO 的光碟映像檔用虛擬光碟工具掛上,並將檔案複製到 D:\Express\WinSDK 中
  2. 將 Windows Server 2003 R2 Platform SDK 的光碟映像檔用虛擬光碟工具掛上,並將檔案複製到 D:\Express\PSDK 中
  3. 將 .NET Framework 3.0 的檔案複製到 D:\Express\dotNetFramework 中
  4. 將 SQL Server 2005 Express Edition 的檔案,除了 SQL Server 2005 Samples 外,複製到 D:\Express\SQLEXPRESS 中,而 SQL Server 2005 Samples 則依平台不同分別放到
    • D:\Express\SQLEXPRESS\Sample\x86
    • D:\Express\SQLEXPRESS\Sample\x64
    • D:\Express\SQLEXPRESS\Sample\IA64
  5. 將 DirectX SDK 解到 D:\Express\DirectX 中
  6. 將 Visual XXX Add-on 複製到 D:\Express\Add-on 中
接下來就可以製作光碟選單,然後燒成一片DVD。

2007/01/19

2007年的規劃

底下是有優先順序的:
  1. 找個好工作
  2. 養成良好的作息與習慣
  3. 完成DB Layer的寫作
  4. 學好Ubuntu和FreeBSD
  5. 完成小型Hosting後台管理的Application
  6. 繼續改寫PEAR
  7. 好吃好睡好動畫~

2007/01/18

新版的量測

下載
更新部份:
  • 刪除 清除()、統計()
  • 顯示() 強制使用utf8編碼
  • 對測試功能的單一測試:陣列()與多重測試:陣列() 刪除傳遞次數參數
  • 對測試功能增加函數一欄,刪除最大、最小兩欄
  • 彙整:時點()、彙整:測試(),執行後自動清除內部使用陣列

設計量測類別(5) 完結篇

這一篇是完結篇,要談最後一部分:調用 功能的設計。
先看源碼:

    function 開始($註記 = false){
        $this->調用 = array();
        $this->堆疊 = array();
        $this->從 = array();
        $this->至 = array();
        if (false == $註記){
            $註記  = md5(uniqid(rand(), true));
        }
        $this->量測 = true;
        $this->註記 = $註記;
        $this->進入($註記);
    }
   
    function 結束(){
        if (false == $this->量測) {
            return;
        }
        $堆疊 = &$this->堆疊;
        if (1 < count($堆疊)) {
            $堆疊 = array(reset($堆疊));
        }
        $this->離開($this->註記);
        $this->量測 = false;
    }
   
    function 進入($註記){
        if (false == $this->量測){
            return;
        }
        $預設 = 0;
        $索引 = count($this->堆疊);
        if (0 < $索引){
            $調用 = $this->堆疊[$索引 - 1][0];
            $從 = &$this->從[$註記][$調用];
            $至 = &$this->至[$調用][$註記];
            變數:預設($從, $預設);
            變數:預設($至, $預設);
            $從++;
            $至++;
        }

        $次數 = &$this->調用[$註記]['次數'];
        變數:預設($this->調用[$註記]['註記'], $註記);
        變數:預設($次數, $預設);
        $次數++;
        $this->堆疊[] = array($註記, microtime(true));
    }

    function &離開($註記, $出口 = 0, &$輸出 = ''){
        $離開 = microtime(true);
        $索引 = count($this->堆疊);
        if (false == $this->量測 || 0 == $索引 || $this->堆疊[$索引 - 1][0] != $註記){
            return $輸出;
        }
        $預設 = 0;
        list($註記, $進入) = array_pop($this->堆疊);
        $耗時 = abs($進入 - $離開);
        $累計 = &$this->調用[$註記]['累計'];
        $出口次數 = &$this->調用[$註記]['出口'][$出口]['次數'];
        $出口累計 = &$this->調用[$註記]['出口'][$出口]['累計'];
        變數:預設($this->調用[$註記]['出口'][$出口]['出口'], $出口);
        變數:預設($累計, $預設);
        變數:預設($出口次數, $預設);
        變數:預設($出口累計, $預設);
        $累計 += $耗時;
        $出口次數++;
        $出口累計 += $耗時;
        return $輸出;
    }
    private function &整理:調用($註記){
        $預設 = array();
        $輸出 = array();
        $調用 = &$this->調用[$註記];
        if (false == isset($調用)){
            return $輸出;
        }
        $this->整理:出口($註記);
        $輸出 = $調用;
        $輸出['從'] = 變數:預設($this->從[$註記], $預設);
        $輸出['至'] = 變數:預設($this->至[$註記], $預設);
       
        $比較 = &$this->調用[$this->註記]['累計'];
        $預設 = 0;
        變數:預設($比較, $預設);
        $輸出['比例'] = 100 * 數學:除法($調用['累計'], $比較);
        $輸出['比例'] = number_format($輸出['比例'], 2, '.', '') . ' %';
        return $輸出;
    }
   
    private function 整理:出口($註記){
        $調用 = &$this->調用[$註記];
        if (false == isset($調用)){
            return;
        }
        foreach ($調用['出口'] as &$出口){
            $出口['比例'] = 100 * 數學:除法($出口['累計'], $調用['累計']);
            $出口['比例'] = number_format($出口['比例'], 2, '.', '') . ' %';
        }
    }

    function &彙整:調用($表格 = false){
        if ($this->量測) {
            $this->結束();
        }
        $輸出 = array();
        foreach ($this->調用 as $註記 => &$調用){
            $輸出[$註記] = &$this->整理:調用($註記);
            if (0 == count($輸出[$註記])) {
                unset($輸出[$註記]);
            }
        }
        if ($表格) {
            $輸出 = 網頁:表格($輸出, $this->編碼['調用'], '調用', $this->屬性);
        }
        return $輸出;
    }
   

由於輸出的報表比較複雜,所以彙整的部份我拆成三塊來作,運用的技巧跟測試系列相關的作法一樣,都是一層層套疊:彙整:調用=>整理:調用=>整理:出口。

這樣的設計是因為比較容易看懂每個區塊的意圖,如果全部寫在同一個函數中,源碼的長度變得比較長,相互之間要使用的變數名稱可能要重新命名,也不容易看懂,所以我會把這樣的函數拆出來。

由於是類別內的成員,可以將一部分的函數設成private,使外部無法存取,也由於在類別內,可使用的假設限制也比較寬,不用擔心傳進來的參數會有格式不符的問題。

$量測 是這整個功能的一個旗標,預設是false,所以必須使用 開始($註記) 函數來使這項功能能夠使用,這是因為必須在整個調用功能的最外層設置一個時間點,讓報表中的比例欄位有一個可以比較的對象。

而報表中的比例欄位,指的是該函數佔整個執行時間的比例,同樣在出口一欄中也有比例欄位,但在出口中的比例欄位比較的對象則是該函數的總執行時間。
從比例欄位中,我們可以得知執行的瓶頸在哪裡。

而 從 - 至 欄位,則是我借用作業研究這門學科中的從至圖的概念,把調用該函數(從),以及該函數所調用的函數或是返回的函數(至),作一個調用次數上的分析,可以從這個欄位中看出函數間相互調用的關係。

進入 和 離開 函數中,是使用堆疊作為調用功能在計算相關數據的依據,所以在使用上會假設要量測的相關函數內的進入點及離開點都有作「正確」的設置。在這方面,我在 離開 函數上做的測試比較多,可以有效防止離開點未設置的錯誤,但不能防止在使用上被「錯誤」的設置。

不過我想這是使用者的責任,防呆措施我認為做到這個程度應該是可以接受的。
調用功能是這三大功能中最複雜的,主要是牽涉到一些資料結構運用上的問題。

2007/01/17

設計量測類別(4)

今天要談的是有關測試這個功能項目的解說,廢話不多說,來看源碼:


    private function &耗時(&$開始, &$結束){
        $耗時 = array();
        $次數 = count($開始);
        for ($索引 = 0; $索引 < $次數; $索引++){
            $耗時[$索引] = abs($結束[$索引] - $開始[$索引]);
        }
        return $耗時;
    }

    function &彙整:測試($表格 = false){
        $輸出 = &$this->測試;
        if(false == $表格) {
            return $輸出;
        }
        $輸出 = 網頁:表格($輸出, $this->編碼['測試'], '測試', $this->屬性);
        return $輸出;
    }

    private function &統計(&$開始, &$結束){
        $耗時 = &$this->耗時($開始, $結束);
        sort($耗時);
        $輸出 = array(
            '最小' => reset($耗時), '最大' => end($耗時),
            '次數' => count($耗時), '累計' => array_sum($耗時)
        );
        $輸出['平均'] = 數學:除法($輸出['累計'], $輸出['次數']);
        return $輸出;
    }
   
    function &多重測試($次數, $函數表){
        $測試 = array();
        foreach ($函數表 as $註記 => &$參數) {
            $函數 = &$參數[0];
            array_unshift($參數, $次數, $註記);
            $測試[$註記] = &call_user_func_array(array($this, '單一測試'), $參數);
        }
        return $測試;
    }
   
    function &多重測試:陣列($次數, $函數表){
        $測試 = array();
        foreach ($函數表 as $註記 => &$參數) {
            $測試[$註記] = &$this->單一測試:陣列($次數, $註記, $參數[0], $參數[1]);
        }
        return $測試;
    }

    function &單一測試(){
        $參數 = &func_get_args();
        $次數 = &array_shift($參數);
        $註記 = &array_shift($參數);
        $函數 = &array_shift($參數);

        $_參數 = array();
        for ($索引 = 0; $索引 < $次數; $索引++){
            $_參數[$索引] = &$參數;
        }
        return $this->單一測試:陣列($次數, $註記, $函數, $_參數);
    }

    function &單一測試:陣列($次數, $註記, $函數, &$參數 = array()){
        $開始 = array();
        $結束 = array();
        $耗時 = array();
        if (false == is_callable($函數)) {
            return $耗時;
        }

        for ($索引 = 0; $索引 < $次數; $索引++){
            $開始[$索引] = microtime(true);
            call_user_func_array($函數, $參數[$索引]);
            $結束[$索引] = microtime(true);
        }
       
        $測試 = &$this->測試[$註記];
        $測試 = $this->統計($開始, $結束);
        $測試['註記'] = $註記;
        return $測試;
    }

四種不同測試呼叫,最終都會呼叫 單一測試:陣列 來作實際的量測。
這裡把量測時間放在迴圈內,只是希望得到比較精確的數值,不過話說回來了,之前我測試各種呼叫方式的差異,這裡使用 call_user_func_array 本來就已經不準了,所以下一版我應該會把量測時間點移出迴圈外。

基本上那個統計函數也只是作簡單的敘述統計而已,但是下一版若把量測時間點移出迴圈外,就無法取得最大最小值,所以下一版會把這兩個統計值給拿掉。就實用上而言,若真的需要這兩個值,不如就使用調用功能還比較清楚。

還有一個改進的地方是跟 :陣列 有關的兩個函數,下一版對這兩個函數會取消 $次數 的參數傳遞,改以取得參數陣列中項目的個數作為次數。

這兩天放的程式碼都還算簡單,需要詳加解釋地方不多,基本上對PHP的陣列操作熟悉大概都不是問題。

設計量測類別(3)

恩~感謝朋友們的關心,這幾天有點小感冒,不過總算把藍星侵略者143完食。
現在讓我們來看一下源碼解說,以下三個函數是時點這項功能相關的函數:


    function 時點($註記){
        $this->時點[$註記] = microtime(true);
    }

    function 時差($開始, $結束){
        $時點 = microtime(true);
        $開始 = &$this->時點[$開始];
        $結束 = &$this->時點[$結束];
        變數:預設($開始, $時點);
        變數:預設($結束, $時點);
        return abs($開始 - $結束);
    }

    function &彙整:時點($表格 = false){
        $索引 = 0;
        $累計 = 0;
        $輸出 = array();
        asort($this->時點);
        $開始 = reset($this->時點);
        foreach ($this->時點 as $註記 => $時點) {
            $時差 = abs($時點 - $開始);
            $累計 += $時差;
            $輸出[$索引] = compact(array_keys($this->編碼['時點']));
            $開始 = $時點;
            $索引++;
        }
        if ($表格) {
            $輸出 = 網頁:表格($輸出, $this->編碼['時點'], '時點', $this->屬性);
        }
        return $輸出;
    }

function 網頁:表格(&$陣列, &$編碼, $標題 = '', &$屬性 = array()){
    $輸出 = array();
    $排序 = array_keys($編碼);
    $輸出[] = '<table' . 網頁:屬性($屬性, true) .'>';
    if (false == empty($標題)) {
        $輸出[] = '<caption>' . htmlspecialchars($標題) . '</caption>';
    }
    $輸出[] = '<tr><th>' . implode('</th><th>', $排序) . '</th></tr>';

    foreach ($陣列 as &$內容){
        $內容 = 陣列:排序($內容, $排序, true);
        if (0 == count($內容)) {
            continue;
        }
        $內容 = 陣列:編碼($內容, $編碼);
        foreach ($內容 as $鍵 => &$值) {
            if ('' == $值) {
                $內容[$鍵] = '&nbsp;';
            }
        }
        $輸出[] = '<tr><td>' . implode('</td><td>', $內容) . '</td></tr>';
    }

    $輸出[] = '</table>';
    return implode("\n", $輸出);
}


對於量測這個類別來說,在可能的範圍下,盡量求取精確是很重要的一點,所以Benchmark才會去支援BCMath的一些運算方法,希望能提高浮點數的精確度。

實際上,若用BCMath配合microtime,可以得到的精確度是小數點後四位。不過相對於精確度來說,數個樣本的比較希望得到「顯著」的差異,這才是對量測來說更重要的事情。
因為得不到實質上的好處,所以我放棄支援BCMath,專心來處理其他的問題。

在處理跟時間相關的問題時,我發現有時明明比較後面才取得microtime的,可是其數值卻比較小,因此我在所有求取時差的部份全部加上abs來計算絕對值。

彙整:時點 的部份我在處理時先作過排序是因為怕不小心重複使用了相同註記,所以先依照取得的microtime做好排序,然後再產生報表。

輸出的部份我設計了一個通用的輸出HTML的表格的函數,這個函數可以接受一個二維陣列作為資料來源,另外一個一維陣列指示如何處理資料,你可以從 $編碼 這個成員變數看出如何彈性設置,讓這個函數發揮效用。不過呢,這個函數中的迴圈跑了太多次,我會想想該如何改進。

2007/01/14

設計量測類別(2)

讓我們繼續之前的話題,上一次我說會考慮加入自動註記的功能,不過我考慮了一下,決定還是不要,因為我沒辦法假設每個出入口都會被設置進入與離開點,但是在下個版本,我還是會加上一個欄位「函數」,來標明這個時點是屬於哪個函數的,接下來讓我們來看看各項功能的成員函數的解說。

時點:
  • 時點($註記):設定某個時點。
  • 時差($開始, $結束):傳回兩個註記之間的時差。
  • &彙整:時點($表格 = false):將曾紀錄過的時點資訊傳回,若$表格 == true,則傳回HTML的表格資料。

測試:
  • &單一測試($次數, $註記, $函數, [$參數1, ...]):傳回測試 $函數 的結果,這裡的 $函數 必須是可以通過 is_callable 驗證的函數或是物件的成員函式。
  • &單一測試:陣列($次數, $註記, $函數, &$參數 = array()):同單一測試,只是參數傳遞的方式不同。
  • &多重測試($次數, $函數表):對多個函式執行單一測試,$函數表 的格式為 $註記 => array($函數, [$參數1, ...])。
  • &多重測試:陣列($次數, $函數表):同多重測試,$函數表 的格式為 $註記 => array($函數, $參數)
  • &彙整:測試($表格 = false):將曾紀錄過的測試資訊傳回,若$表格 == true,則傳回HTML的表格資料。

調用:
  • 開始($註記 = false):在包含所有要分析的函數外設置的第一個時點,與 結束() 配合提供比例欄位的計算基準,這個函數會清空在類別內部使用的相關陣列。
  • 結束():結束紀錄調用的進入、離開函數,在此函數之後,若不再次使用 開始(),那麼後續的進入、離開函數將不會有作用。
  • 進入($註記):請將此函數設置在要分析的函數內的開頭。
  • &離開($註記, $出口 = 0, &$輸出 = ''):請將此函數設置在分析的函數內的 return 的前一行,亦可與 return 在同一行,只要把原本欲輸出的變數傳入 $輸出 內即可。
  • &彙整:調用($表格 = false):將曾紀錄過的調用資訊傳回,若$表格 == true,則傳回HTML的表格資料,另外,此函數會自動呼叫 結束()。

其他:
  • &單例():取得本類別的單例參照。
  • 清空():清空時點與測試功能內部所使用的相關陣列,下一個版本會刪除這個成員函式,改由彙整時自動清除。
  • 顯示($編碼 = 'utf8'):顯示三個功能的報表,下個版本會刪除 $編碼 參數,強迫指定 utf-8 編碼方式。

使用範例請參考壓縮檔內的example.php,今天先到這裡。程式碼的說明讓我延到下一篇吧~又無端熬夜看動畫了~

設計量測類別(1)

首先讓我來談一下,我改寫的這個量測類別與PEAR::Benchmark有何不同。
  1. 量測是一個單一且未繼承其他類別的單純類別,而Benchmark是繼承PEAR類別且根據功能拆分成三個小類別。
  2. 量測在輸出的部份僅輸出HTML,而Benchmark可選擇輸出HTML及Plain。
  3. 量測在輸出上交由其他的一般函數來處理,而Benchmark則交由成員函數來處理。
  4. 量測在計算上不考慮使用BCmath模組,而Benchmark則有。
  5. 量測在測試函數的方式上可選擇不同的參數傳遞方式,而Benchmark則僅有一種方式。
  6. 量測在調用函數的分析上考慮了多重出口的情況,而Benchmark不考慮。

大致上就如以上所述,接下來我先談一下量測這個類別與其他的程式有何不同,以及為何要如此設計的考量。

量測的類別通常使用於開發過程中,或是在維護階段尋找程式執行的瓶頸,幾乎不會被用在一般正在線上執行的系統中,所以我才會將三個功能全部放在一起。
這三項功能分別是:
  1. 量測兩個程式執行的時間點的時間差。
  2. 重複測試一或多個函數的執行效率。
  3. 分析一或多個函數在調用時的狀態。

第一個功能很單純,只要紀錄時間點後,再根據時間點間的時差作計算。

第二個功能稍微複雜一點,我在量測中只增加兩個功能:每次執行的參數可以不同、可以一次針對多個函數進行測試。(這裡要稍微說明一下我個人喜歡的一個技巧:就是把資料先準備好,然後作單一的動作。)
量測對應的四種測試方法分別是:單一測試、單一測試:陣列、多重測試、多重測試:陣列。
後面有「:陣列」的方法,差異在於參數傳遞的方式不同,以及可以讓每次執行的參數可以不同。
如果你看源碼的話就會發現這四個方法是互相調用的,之所以設計成四個方法,是為了讓不同習慣的人方便。
不過由於這個類別是我所寫的,因此下次維護的時候我或許會將四個方法減少也不一定。

第三個功能算蠻複雜的,而且我也沒完全看懂Benchmark的作法,主要是對他計算某個指標($_subSectionsTime)感到不解,不過我個人覺得那個指標或許跟我添加的多重出口有關係也不一定就是。
所謂的多重出口,簡單講就是一個函數可以有一個以上的return。
這個功能可以分析一個函數被調用、或是在函數中調用其他函數,以及每個出口所花的時間,因此這個功能主要是在找出執行瓶頸,提供改善的資訊。

接下來談一下我個人的程式設計哲學:容錯。
如果我們看Benchmark的源碼,在第三個功能的使用上,在某些條件上有一些錯誤訊息的產生,而我改寫的量測類別並不會有。

這個條件是假設使用者會有一些疏忽,設置了函數的進入點,卻忘了設置函數的離開點、或是反過來,也有可能在同一個函數使用不同的註記、或是在不同函數中使用相同的註記。

由於我個人的認知,第三個功能應屬於微調階段使用,因此我偏向不引起錯誤訊息,而讓報表的數字來引起程式設計者注意。這項功能產生的報表,大多是已經很有經驗的工程師或是系統分析人員才會去看的,一般的工程師大部份使用第一或是第二個功能就足以滿足了。
由於我做的檢查比較多,所以即使發生上述狀況也沒關係,只是報表上數字會很奇怪而已,下一個版本我會傾向自動產生註記,以避免意外狀況產生。

今天到此為止,下一個版本大概會在一個禮拜後出現。
明天我會拿幾個實際的程式碼片段來說明。

2007/01/13

量測程式碼執行效率分析的物件

要下載請按這裡
這是我參考PEAR::Benchmark寫出來的一個效能分析物件。
使用方法請參考原始碼以及example.php

這幾天我會把設計上的考量以及為甚麼要如此設計分幾篇文章寫出來。
這禮拜都沒睡好~恩,其實是熬夜寫程式後又繼續撐著看動畫的關係。

2007/01/11

為何要用中文寫程式

目前我認識的人裡面,像我一樣「大量」使用中文來作變數名稱、認真寫程式的人,我只認識一位,只不過他是寫JAVA的高手。

用中文來寫程式不是什麼稀奇的事情,但重點在於我為何「一定」要用中文寫程式呢?
當然這是有理由的,請聽我詳述:
  1. 我希望在我的專業領域使用中文
  2. 我希望我的中文比其他的語文好
  3. 我希望我寫出來的程式比較易懂

第一個理由,我不敢說我的專業有多強,但我希望有一天當我要把跟專業相關的東西教給其他人時,不需要講滿口的ABC,要知道初學者有不懂ABC的權利。
我個人認為,沒有任何理由,讓我們學習知識還得受限於語文能力。

第二個理由,我識字不多,在寫程式或是與人表達方面,都有些障礙,有些東西我就是講不出來,或是得拐彎抹角解釋一大篇別人才懂。而我一天的活動,有四分之一都在作跟程式相關的活動,包括在想變數該用什麼方式命名較好等等,因此我想多多練習「用字」與「識字」的能力。
我個人並無出國唸書或是工作的規劃,也不會想去作與外國人溝通的工作,所以基本上英文能力只要保持看得懂外國網站的內容這樣的程度即可。

第三個理由,這個部份我曾跟某位前輩討教過,就他的講法是:
因為我的中文能力比英文能力高,所以在寫程式方面,用中文寫和用英文寫的差異並不大。
我個人是希望,將來有一天我用中文寫出來的程式會比英文程式來的更容易讓人了解。

2007/01/10

使用參照的幾個原則

話說我這個使用參照到病態的人,來寫這個應該蠻適合的。

首先要提得是為甚麼要使用參照?
最重要的理由是為了減少複製變數的成本,這個成本包括記憶體的使用以及時間上的消耗。
如果你看過PHP的原始碼,PHP的變數基本上是由ZVAL這個結構所控制的,所以不管是不是使用參照,當我們作函數的參數傳遞、返回值以及變數賦值的時候,這個結構的產生是不可避免的。

這個問題還好,畢竟這個結構很小,但重點是一些複雜的變數型態,像是陣列、字串、物件等等,這些在複製花的成本上都比數字、邏輯值來的大,當然這也不能一概而論,但重點是在於,陣列、字串、物件這些型態的實際佔用的記憶體空間並不固定,更遑論像物件的複製可能還會有其他的問題產生。
當然啦,數字與邏輯值這些型態,用參照也得不到什麼額外的好處就是。

我個人是比較欣賞JavaScript的作法,傳值還是傳參照由型態決定,而決定的方式有公開的標準。

接下來我來談什麼樣的情況非得用參照不可?我發現有兩個原則:
  1. 無法確定你要操作的變數「在哪裡」的時候。
  2. 你要修改的是「原件」而非「副本」的時候。
第一種情況不太常見,不過程式寫多就會碰到,尤其是當你像我使用參照到病態的地步時,就會知道我說得是啥。
第二種情況則是比較常見,尤其函數的參數傳遞、返回值的應用。

另外要提的是,當一個變數被宣告為參照時,後續的賦值動作「全部」會被當成對被參照的變數作賦值動作,沒有辦法解除這種狀態,除非你unset這個參照變數。

除了上述的原則,有幾個情況建議使用參照:
  1. 函數的參數、返回值若為陣列、物件、資源時。
  2. 字串則要看函數調用時是否會直接給值而定,若不常發生直接給值的情況,也建議用參照。
對於函數傳回值的部份,有一點要注意。
當我們的自訂函數前面加上&符號時,代表會傳回參照,這個部份一直到調用函數然後傳回值這一段都是正確的。
但是!
當我們要把這個傳回值,直接變成另一個函數的參數,或是賦值給另外一個變數時,就不一定是參照了。

另外要說明的是,參照有其可用範圍,其範圍存在於函數之內,但不存在於此函數中呼叫另一函數之中,唯一的例外是此參照的對象是物件。

舉例說明:
function &a(){
...
}
function &b(&$a){
  $c = &$a; //$c = null
  $c = $a; // $c = $a的複製品 (若$a為物件的參照,則$c等同於$a所參照的原件)
  $a['a'] = 1; // 修改的是$a的原件(因為$a是用參照的方式傳進b()中)
...
}
$c = b(a());//這裡傳進去的a()的傳回值不是參照,$c取得的也不是b()傳回值的參照
$c = b(&a());//這個會引發錯誤:call_time_pass_reference
//正確作法
$temp = &$a();
$c = &b($temp);

$aa = array();
$bb = &$aa['a']['b']; // 這是OK的,尚未「運算」之前,參照可以指向任何東西
$bb = 1; // 這表示$aa['a']['b'] = 1

為何不要PHP的魔術方法?

在寫PHP程式的時候,我常常希望能有「同名異式」的東西出現,不過我知道那是辦不到的。
C++的同名異式是編譯器靠著函數的簽名型態自己處理,實際上是在編譯的時候把函數名稱編碼掉了,只是寫程式的人不自知而已。
由於PHP的變數是弱型態,所以用C++的方式也沒法達到同樣的效果。

而我比較愚蠢,同時我也信任自己的編程手法,所以我自己對函數作命名,而不依賴__call這種魔術方法作前處理,而對於單純的函數,即便想用魔術方法也沒輒。

對於命名空間來說,除非你把相關的函數封在一個class裡面,不過這樣程式碼並不會比較清晰。
另外,我個人對於參照的使用到了有些病態的地步,所以我會經常review我自己寫的Code,看看哪裡「可以」使用參照,不過我目前嘗試反過來作,看看哪裡「可以不」使用參照。

底下是我示範的幾個函數設計:

function &變數:預設(&$參照, &$預設 = '', $複製 = true){
    if(false == isset($參照)){
        $參照 = &變數:賦值($參照, $預設, $複製);
    }
    return $參照;
}

function &變數:賦值(&$參照, &$值 = '', $複製 = true){
    if ($複製 && is_object($值)){
        $參照 = clone $值;
    } else {
        $參照 = $值;
    }
    return $參照;
}
function &陣列:參照(&$陣列, &$索引表 = array()){
    $參照 = &$陣列;
    foreach ($索引表 as &$索引) {
        if (false == is_array($參照) && false == is_null($參照)) {
            $輸出 = false;
            return $輸出;
        } else if (empty($索引)) {
            $參照 = &$參照[];
        } else {
            $參照 = &$參照[$索引];
        }
    }
    return $參照;
}

function &陣列:賦值(&$陣列, &$索引表 = array(), &$值 = '', $複製 = true) {
    $參照 = &陣列:參照($陣列, $索引表);
    變數:賦值($參照, $值, $複製);
    return $陣列;
}

function 陣列:過濾(&$陣列, &$預設 = array()){
    return array_intersect_key($陣列 + $預設, $預設);
}

function &陣列:轉換(&$陣列, &$表格 = array()){
    $輸出 = array();
    foreach ($表格 as $鍵 => &$值) {
        if (isset($陣列[$鍵])) {
            $輸出[$值] = &$陣列[$鍵];
        }
    }
    return $輸出;
}

function &陣列:連接(&$陣列, &$表格 = array(), $符號 = '='){
    $輸出 = array();
    foreach ($表格 as $鍵 => &$值) {
        if (isset($陣列[$鍵])) {
            $輸出[$值] = $值 . $符號 . $陣列[$鍵];
        }
    }
    return $輸出;
}

2007/01/08

各種呼叫方式的比較

今天在找資料時看到這一篇,我心血來潮重做了一下這個實驗,結果發現差異沒有那麼明顯。
當然可能是因為我用的是PHP 5.2,或是機器是雙核心的關係,實際的最大差距也不過是2~3倍。

程式碼在下面:

<?php
//  呼叫測試
$測試 = array();
$陣列 = array();
$次數 = 100000;

function 函數(&$參數){
    return htmlentities($參數);
}

class 物件 {
    function __call($方法, $參數){
        return htmlentities(reset($參數));
    }
  function 函數(&$參數){
        return htmlentities($參數);
  }
  static function 靜態(&$參數){
        return htmlentities($參數);
  }
}

$函數 = '函數';
$物件 = &new 物件();
$參照 = array($物件, $函數);

for ($索引 = 0;$索引 < $次數;$索引++){
    $陣列[$索引] = &md5(uniqid(rand(), true));
}

//  測試開始
//  Native
$時間 = &microtime(true);
for ($索引 = 0;$索引 < $次數;$索引++){
    htmlentities($陣列[$索引]);
}
$測試['Native'] = microtime(true) - $時間;

//  函數
$時間 = &microtime(true);
for ($索引 = 0;$索引 < $次數;$索引++){
    函數($陣列[$索引]);
}
$測試['函數'] = microtime(true) - $時間;

//  $函數
$時間 = &microtime(true);
for ($索引 = 0;$索引 < $次數;$索引++){
    $函數($陣列[$索引]);
}
$測試['$函數'] = microtime(true) - $時間;

//  $物件->函數
$時間 = &microtime(true);
for ($索引 = 0;$索引 < $次數;$索引++){
    $物件->函數($陣列[$索引]);
}
$測試['$物件->函數'] = microtime(true) - $時間;

//  $物件->$函數
$時間 = &microtime(true);
for ($索引 = 0;$索引 < $次數;$索引++){
    $物件->$函數($陣列[$索引]);
}
$測試['$物件->$函數'] = microtime(true) - $時間;

//  物件::靜態
$時間 = &microtime(true);
for ($索引 = 0;$索引 < $次數;$索引++){
    物件::靜態($陣列[$索引]);
}
$測試['物件::靜態'] = microtime(true) - $時間;

//  物件->__call
$時間 = &microtime(true);
for ($索引 = 0;$索引 < $次數;$索引++){
    $物件->隨機($陣列[$索引]);
}
$測試['物件->__call'] = microtime(true) - $時間;

//  call_user_func_$函數
$時間 = &microtime(true);
for ($索引 = 0;$索引 < $次數;$索引++){
    call_user_func($函數, $陣列[$索引]);
}
$測試['call_user_func_$函數'] = microtime(true) - $時間;

//  call_user_func_$參照
$時間 = &microtime(true);
for ($索引 = 0;$索引 < $次數;$索引++){
    call_user_func($參照, $陣列[$索引]);
}
$測試['call_user_func_$參照'] = microtime(true) - $時間;

//  call_user_func_array_$函數
$時間 = &microtime(true);
for ($索引 = 0;$索引 < $次數;$索引++){
    call_user_func_array($函數, array($陣列[$索引]));
}
$測試['call_user_func_array_$函數'] = microtime(true) - $時間;

//  call_user_func_array_$參照
$時間 = &microtime(true);
for ($索引 = 0;$索引 < $次數;$索引++){
    call_user_func_array($參照, array($陣列[$索引]));
}
$測試['call_user_func_array_$參照'] = microtime(true) - $時間;

//  顯示結果
echo '<pre>';
print_r($測試);
echo '</pre>';
?>

2007/01/07

目前DB Layer的進度

對於目前可以簡單搞到的資料庫系統,以及PHP官網正式登錄的API,在連線的部份我都做了一番研究。
也由於實做的關係,我決定這一個禮拜要改寫程式碼,把目前的設計由工廠模式改為策略模式。
也就是說,會根據目前可用的extension來決定用什麼樣的Driver來操作DB。

之前曾在某個網站上有人提出測試報告,執行的速度分別為:
PDO > Native Function > DBX

因此我正在寫的這套,將會以上述的順序來進行判斷,決定使用哪套Driver。
這裡有一份我研究PDO連線的DSN資料。

2007/01/06

PHPSecInfo 介紹

PHPSecInfo 是一套檢測 php.ini 裡面相關的安全設定的工具。
目前檢測的項目不多,不過可以簡單的作一下自我測試,我舉程式裡幾個跟數字有關的上限值(單位都是bytes):
  • memory_limit:8*1024*1024 (8M,PHP預設建議值為 16M)
  • post_max_size:256*1024 (256K,PHP預設建議值為 8M)
  • upload_max_filesize:256*1024 (256K,PHP預設建議值為 2M)
說實話,在實際應用上即便以 PHP的建議值我都覺得太苛,沒想到這套軟體的標準更嚴苛。

在 Windows 的環境下,透過他的建議,除了以下三項,其他的項目都可以PASS:
  • gid test
  • memory_limit
  • uid test

我的 Apache 設定方式

目前我使用的 Apache 的版本是 2.2.3,前幾天心血來潮,想看看哪些 Module 可以不用載入的,所以到官網去查資料,修改了一下 httpd.conf 。
底下相關路徑請自行修改

LoadMoudle 部份,我只載入下面的 Module:
  • actions_module (*)
  • alias_module (*)
  • authz_host_module
  • cgi_module (*)
  • dir_module
  • mime_module
  • userdir_module
  • php5_module

上面有(*)的是執行CGI模式所必須增加的。

而關於php5的設定如下:

# For PHP 5 do something like this:
ScriptAlias /php/ "D:/Program/php/"
LoadModule php5_module "D:/Program/php/php5apache2_2.dll"
#Action application/x-httpd-php "/php/php-cgi.exe"
AddType application/x-httpd-php .php

# configure the path to php.ini
<IfModule php5_module>
    PHPIniDir "D:/Program/php"
</IfModule>

除此之外還有三個地方要改:

<Directory />
    Options FollowSymLinks
    AllowOverride None
    Order deny,allow
    Deny from all
    Satisfy all
</Directory>

改成

<Directory />
    Options FollowSymLinks
    AllowOverride None
    Order deny,allow
#    Deny from all
    Satisfy all
</Directory>

<IfModule dir_module>
    DirectoryIndex index.html
</IfModule>

改成

<IfModule dir_module>
    DirectoryIndex index.php index.htm index.html
</IfModule>

增加:

<IfModule userdir_module>
UserDir D:/Website
</IfModule>

2007/01/05

parse_str 與 build_http_query 的問題

parse_str 與build_http_query的問題都出在陣列的「索引」編解碼上。
若索引中含有 '.' (dot), ' ' (white space)等字元,parse_str解出來的會變成 '_' (underline)。
build_http_query則是把索引前後的[]也編碼掉了,所以如果索引中含有 '[', ']' 的話,在解碼的判斷上會有問題,不過有趣的是,parse_str可以很正確的解出來。

2007/01/03

陣列用的賦值函數

我發現parse_str在解碼含有' ', '.'等字元時,會發生問題,所以我打算寫個函數取代它。
而這個陣列用的賦值函數算是副產品,會被使用在一些特殊的情況中。


function &陣列:參照(&$陣列, &$索引表 = array()){
    $參照 = &$陣列;
    foreach ($索引表 as $索引) {
        if (false == is_array($參照) && false == is_null($參照)) {
            $輸出 = false;
            return $輸出;
        } else if (empty($索引)) {
            $參照 = &$參照[];
        } else {
            $參照 = &$參照[$索引];
        }
    }
    return $參照;
}

function &陣列:賦值(&$陣列, &$索引表 = array(), &$值 = '', $拷貝 = true) {
    $參照 = &陣列:參照($陣列, $索引表);
    if (false === $參照) {
        return $陣列;
    } else if (false == $拷貝) {
        $參照 = &$值;
    } else if(is_object($值)) {
        $參照 = clone $值;
    } else {
        $參照 = $值;
    }
    return $陣列;
}


底下是個簡單的範例,執行看看就知道這個函數在幹麼。

$陣列 = array('a'=>array(1,2));
$參照 = array('a', '', 'c');
$值 = 'd';
陣列:賦值($陣列, $參照, $值);
$參照 = array('a', '');
$值 = 'z';
陣列:賦值($陣列, $參照, $值);
echo '<pre>';
print_r($陣列);
echo '</pre>';

2007/01/02

解析網址函數


配合之前寫的路徑修正函數,稍微調整一下,寫出新的兩個函數:解析網址、路徑修正。


function &解析網址($網址) {
    $解析 = parse_url($網址);
    foreach ($解析 as $鍵 => $值) {
        if ('query' == $鍵) {
            parse_str($值, $解析[$鍵]);
        } else if ('path' == $鍵) {
            $解析[$鍵] = 路徑修正($值, true, 'urldecode');
        } else {
            $解析[$鍵] = urldecode($值);
        }
    }
    return $解析;
}

function &路徑修正($路徑, $輸出陣列 = false, $函數 = false, $調整 = false) {
    //    取代 '/./', '/ /', '//' 這三類字串成為 '/'
    $比對 = '/\/((([\s]+)|([\s]*\.[\s]*))?\/)+/';
    $取代 = '/';
    $陣列 = explode('/', preg_replace($比對, $取代, strtr($路徑, '\\', '/')));
   
    if (false == is_callable($函數)) {
        $函數 = false;
    }

    $路徑 = array();
    foreach ($陣列 as $變數) {
        $測試 = trim($變數);
        if ('..' == $測試) {
            $測試 = end($路徑);
            if ('' == $測試 || '..' == $測試) {
                $路徑[] = '..';
            } else {
                array_pop($路徑);
            }
        } else if (false == $函數) {
            $路徑[] = $變數;
        } else {
            $路徑[] = call_user_func($函數, $變數);
        }
    }
   
    if (false == $輸出陣列) {
        $路徑 = implode('/', $路徑);
        if ($調整 && (PHP_OS == 'WIN32' || PHP_OS == 'WINNT')){
            $路徑 = strtr($路徑, '/', '\\');
        }
    }

    return $路徑;
}

2007/01/01

dbx_close 與 SQLite 的問題

其實前幾天就發現這個問題了,只是當時還沒有時間去作report bug,剛剛花了點時間去報告

這個問題我是覺得應該是與sqlite_close的傳回值是void有關,因為dbx的函數其實也是去呼叫相對應的資料庫API函數,而void == false,所以當用dbx來操作SQLite時,就會發生dbx_close總是傳回false的問題。

其他像:mysql、pgsql、mssql、oci8都是正常的,因為他們對應的xxxxx_close函數都會有傳回值。

Firebird 與 PHP 連線

Firebird 與 PHP 的連線,其實是很簡單的,在安裝 PHP 的機器上,編輯底下這個檔案
%WINDIR%\\system32\\drivers\\etc\\services
在檔案中加上這一行:
gds_db           3050/tcp                           #firebird Database
這樣就可以連線了。

另外,Firebird的管理工具,推薦使用
FlameRobin

DB2 Express-C 與 PHP 連線

DB2 Express-C 的與 PHP 連線的方式,其實只要安裝DB2 9 Runtime Client套件就可以了,這是比較保險的方式,不過這個檔案要23MB(僅English),有點大就是。

其他的方式不推薦,因為需要額外設定一些環境變數,有點麻煩。

Oracle 10g Express Edition 與 PHP 連線

這兩天用VPC做了一個虛擬機器,把實驗用的資料庫們丟過去,順便測一下網路上寫的連線方式看對不對。
我發現對於Oracle 10g Express Edition的方式,作法上跟網路上這篇以及這篇說的有點出入。

首先要解釋的是,那篇文章講的應該是是Oracle 10g Enterprise/Standard Editions 的作法,所以他所說的那三個在Instant Client裡面的dll,並不適用於Oracle 10g Express Edition。

正確的作法是,要去下載Oracle Database 10g Express Client這個套件,下載的連結頁面同Oracle 10g Express Edition,其大小是30MB左右。當然啦,如果把Oracle 10g Express Edition跟Apache、PHP裝在同一台機器上的話,可以不用作底下的設定。

下載Oracle Database 10g Express Client回來後,把他安裝起來,裝完後在其安裝目錄下的bin目錄中,可以找到許多檔案,需要的只有下面三個:
  • oci.dll
  • ociw32.dll
  • oraocixe10.dll
至於orannzsbb10.dll裡面也有,不過目前我還沒發現這個檔案是否為必須,因為連結上並不需要這個檔案也可以進行連結。

把上面三個檔案複製到PHP的安裝目錄中即可,之後可以把Oracle Database 10g Express Client給反安裝移除掉