Aug 25

PHP 自 5.3.0 開始,引入 mysqlnd 這個 extension。
5.3.0 的 Full ChangeLog 中可以看到這行:

Added mysqlnd extension as replacement for libmysql for ext/mysql, mysqli and PDO_mysql. (Andrey, Johannes, Ulf)

依照個人的過往經驗,native driver 的效能都會比較好,而 mysqlnd 也不例外(有興趣的可以看看這篇文章)。

比較可惜的是,mysqlnd 在 Windows 平台的 PHP 是預設套件;在 *NIX 平台,使用前必須在編譯前就先做好設定(configure;參數可參考 PHP 官方的mysqlnd 安裝文件)。

剛才,很高興看到 FreeBSDPHP5 ports 加入了 option,讓我們可以輕鬆搞定。 8)

Technorati Tags: , , ,

(Visited 629 times)
Aug 08

發現 MongoDB 是好一陣子前的事,而我也在某台機器上把它裝起來。
之前玩的時候,發現 MongoDB 不須帳號與密碼就可以連接,而且也無法配置使用者權限,所以把它丟在旁邊。

前幾天,我在網路上看到網友詢問 MongoDB 有沒有辦法作 JOIN,得到的答案是否定的。
趁著今天在家閒閒沒事作,書也看完幾個段落,就透過 PHP 來試試這種 ODBMS 如何實作資料關聯。

MongoDB 與常見的資料庫(例如:MySQL)有些微的不同:

資料庫 資料表
MongoDB 稱之為 Database(DB) Collection
MySQL 稱之為 Database(DB) Table

先把這個觀念講清楚,底下的範例程式碼才不會看得霧煞煞。 8)

首先,先建立 DB 與 Collection:

// 連接 MongoDB
$m = new Mongo();

// 連接資料庫,名稱就是 test
$testDB = $m->selectDB( 'test' );

// 建立 Collection,名稱分別是 user 與 sex
$testDB->createCollection( 'user' );
$testDB->createCollection( 'sex' );
  • MongoDB 沒有 createDB 這種指令。只要選擇資料庫,建立 Collection 之後,系統就會自動產生 DB。
  • 指令列模式下,選擇資料庫的指令跟 MySQL 相同(USE DB_NAME)。

再來,存入性別資料:

// 連接 MongoDB
$m = new Mongo();

// 連接資料庫,名稱就是 test
$testDB = $m->selectDB( 'test' );

// 在 sex 這個 Collection 裡面放入資料,好讓程式分辨男性與女性
$testDB->sex->insert( array('sex_name' => 'Female') );
$testDB->sex->insert( array('sex_name' => 'Male') );

// 把 sex 這個 Collection 的資料倒出來看
$cursor = $testDB->sex->find();
$array = iterator_to_array($cursor);
var_dump($array);
  • 以往我們儲存在 Table 的 data row 會有個 id 值,方便我們建立關聯。MongoDB 這種資料庫則是在 Collection 裡面放物件,可以不需要 id 值。

開始存入使用者名稱,並紀錄他(她)們的性別:

// 連接 MongoDB
$m = new Mongo();

// 連接資料庫,名稱就是 test
$testDB = $m->selectDB( 'test' );

// 找出男性資料,並取得關聯值
$male = $testDB->sex->findOne( array('sex_name' => 'Male') );
$refMale = $testDB->sex->createDBRef( $male );

// 找出女性資料,並取得關聯值
$female = $testDB->sex->findOne( array('sex_name' => 'Female') );
$refFemale = $testDB->sex->createDBRef( $female );

// 存入使用者資料
$testDB->user->insert( array('name' => 'BoyName', 'sex' => $refMale) );
$testDB->user->insert( array('name' => 'GirlName', 'sex' => $refFemale) );

// 取出並顯示使用者資料
$cursor = $testDB->user->find();
$array = iterator_to_array($cursor);
foreach ( $array as $user ) {
        // 找出性別的關聯物件
        $sexRef = $testDB->user->getDBRef($user['sex']);
        echo "Name: {$user['name']}\tSex: {$sexRef['sex_name']}\n";
}

以上,簡單的試玩心得。 :oops:

Technorati Tags: , , , ,

(Visited 921 times)
Jul 14

昨晚在 PTT 的 PHP 板回了一篇文章,提到 PHP 的內建常數;現在來分享一下,順便作個紀錄,方便日後查詢。

PHP 有不少實用的內建常數,方便我們寫程式時直接呼叫。
有興趣研究的人,可以看看這個網頁,或是用 get_defined_constants() 把內建常數全部倒進陣列,再用 var_dump()print_r() 顯示。

下表列出一些我覺得常用的內建常數,範例值來自 64 位元版本的 FreeBSD ports 安裝之 PHP 5.3.2:

常數名稱 常數型態 範例值或說明 可用版本
PHP_VERSION string "5.3.2" 無限制
PHP_MAJOR_VERSION integer 5 5.2.7+
PHP_MINOR_VERSION integer 3 5.2.7+
PHP_RELEASE_VERSION integer 2 5.2.7+
PHP_EXTRA_VERSION string "" 5.2.7+
PHP_OS string "FreeBSD" 無限制
PHP_PREFIX string "/usr/local" 4.3.0+
PHP_BINDIR string "/usr/local/bin" 無限制
PHP_LIBDIR string "/usr/local/lib/php" 無限制
PHP_DATADIR string "${prefix}/share" 無限制
PHP_SYSCONFDIR string "/usr/local/etc" 無限制
PHP_LOCALSTATEDIR string "/usr/local/var" 無限制
PHP_CONFIG_FILE_PATH string "/usr/local/etc" 無限制
PHP_CONFIG_FILE_SCAN_DIR string "/usr/local/etc/php" 無限制
PHP_SHLIB_SUFFIX string "so" 4.3.0+
PHP_EOL string 此變數可用來判斷 "\n"、"\r"、"\r\n"(三個通吃,超好用)。 4.3.0+
PHP_MAXPATHLEN integer 1024 5.3.0+
PHP_INT_MAX integer 9223372036854775807 4.0.4+ 與 5.0.5+
DATE_COOKIE string "l, d-M-y H:i:s T"
輸出範例:
Wednesday, 14-Jul-10 20:25:07 CST
5.1.1+
DATE_ISO8601 string "Y-m-d\TH:i:sO"
輸出範例:
2010-07-14T20:26:18+0800
5.1.1+
DATE_RFC822 string "D, d M y H:i:s O"
輸出範例:
Wed, 14 Jul 10 20:27:39 +0800
5.1.1+
DATE_RFC850 string "l, d-M-y H:i:s T"
輸出範例:
Wednesday, 14-Jul-10 20:28:44 CST
5.1.1+
DATE_RFC1036 string "D, d M y H:i:s O"
輸出範例:
Wed, 14 Jul 10 20:29:40 +0800
5.1.1+
DATE_RFC1123
DATE_RSS
string "D, d M Y H:i:s T"
輸出範例:
Wed, 14 Jul 2010 20:31:51 CST
5.1.1+
DATE_RFC2822 string "D, d M Y H:i:s O"
輸出範例:
Wed, 14 Jul 2010 20:31:51 +0800
5.1.1+
DATE_RFC3339
DATE_ATOM
DATE_W3C
string "Y-m-d\TH:i:sP"
輸出範例:
2010-07-14T20:36:18+08:00
5.1.1+

Technorati Tags: ,

(Visited 1028 times)
May 27

以前,我喜歡用 ADOdb 來連接資料庫;自從 PDO 被放進 PHP 5.1 後,它便成了我的新寵...

我在 PHP 5.1.6 環境上幫人家抓蟲時,看到以下兩段程式。

  1. $pdo = New PDO(......);
    
    $sql = "SELECT .....";
    $st = $pdo->prepare($sql);
    $st->execute();
    $rows = $st->fetchAll();
  2. $pdo = New PDO(......);
    
    $sql = "SELECT .....";
    $st = $pdo->query($sql);
    $rows = $st->fetchAll();

雖然以上兩段程式都可以取出 $rows ,但重複並交叉執行的話,就會出現問題。

  • 這段程式可以正確取出 $rows1 與 $rows2:
    $pdo = New PDO(......);
    
    $sql = "SELECT .....";
    $st = $pdo->query($sql);
    $rows1 = $st->fetchAll();
    
    $sql = "SELECT .....";
    $st = $pdo->prepare($sql);
    $st->execute();
    $rows2 = $st->fetchAll();
  • 這段程式只能正確取出 $rows1 ,$rows2 會是個空的陣列:
    $pdo = New PDO(......);
    
    $sql = "SELECT .....";
    $st = $pdo->prepare($sql);
    $st->execute();
    $rows1 = $st->fetchAll();
    
    $sql = "SELECT .....";
    $st = $pdo->query($sql);
    $rows2 = $st->fetchAll();

這問題有兩種解法:

Technorati Tags: , ,

(Visited 2530 times)
Apr 16

FreeBSD ports tree 的 lang/php5 升級到 5.3.2, www/eaccelerator 也升級到 0.9.6。
但是... 目前,FreeBSD ports tree 的 www/pecl-APC 依然是 3.0.19,無法在 PHP 5.3 運作;為了讓它正常運作,jnlin送過 PR(我沒有先查詢,也送了一個 :oops: )。

其實 eAccelerator 0.9.6 在 2010 年 2 月初就已經釋出了,可是,從 changelog 看來,很多不錯的功能被砍掉(反璞歸真?):

Changes in this version:

* Support for PHP 5.3.
* The encoder is removed
* The user cache functions are removed
* The session handler is removed
* The minimal PHP version supported is now 5.1
* Some internal refactoring to clean up the code
* Fixed some bugs (and probably added some)

這麼看來,2008 年的這篇文章的參考價值就低了些...
為了滿足我自己的好奇心,就把 APC 3.1.3p1 裝起來測試看看。

系統/硬體:

  • CPU: Intel(R) Pentium(R) 4 CPU 3.00GHz
  • Memory: 6G(DDR2 800 MHz, Dual-Channel)
  • FreeBSD 8.0-RELEASE-p2 amd64
  • Apache HTTPD 2.2.14
  • PHP 5.3.2

設定:

  • eAccelerator 0.9.6:
    eaccelerator.shm_size="128"
    eaccelerator.cache_dir="/tmp/eaccelerator"
    eaccelerator.enable="1"
    eaccelerator.optimizer="1"
    eaccelerator.debug="0"
    eaccelerator.log_file="/var/log/eaccelerator.log"
    eaccelerator.name_space = ""
    eaccelerator.check_mtime="1"
    eaccelerator.filter=""
    eaccelerator.shm_max="0"
    eaccelerator.shm_ttl="0"
    eaccelerator.shm_prune_period="0"
    eaccelerator.shm_only="0"
    eaccelerator.compress="1"
    eaccelerator.compress_level="9"
    eaccelerator.keys="shm_and_disk"
  • APC 3.1.3p1:
    apc.enabled=1
    apc.shm_segments=1
    apc.shm_size=128
    apc.ttl=7200
    apc.user_ttl=7200
    apc.num_files_hint=1024
    apc.mmap_file_mask=/tmp/apc.cache
    apc.enable_cli=1

測試結果(ab -c5 -n500):

Pure PHP eAccelerator APC
WordPress 2.9.2 3.00 req./sec.
332.834 ms/req.
7.61 req./sec.
131.487 ms/req.
8.87 req./sec.
112.750 ms/req.
MediaWiki 1.15.3 1.81 req./sec.
552.822 ms/req.
2.75 req./sec.
363.124 ms/req.
5.93 req./sec.
168.580 ms/req.
Gallery 2.3.1 3.51 req./sec.
284.979 ms/req.
7.91 req./sec.
126.421 ms/req.
7.38 req./sec.
135.506 ms/req.

依照結果看來,APC 3.1.3p1 的確略勝 eAccelerator 0.9.6。 8)
可惜的是.. Xcache 目前的最新版本(1.3.0)沒辦法在 PHP 5.3 跑...

Technorati Tags: , , , , ,

(Visited 3128 times)
Jan 09

有安裝 Gallery 2 作為相簿系統的人可能跟我一樣,會在 Apache 的 error log 裡面看到一大串如下的 log:

PHP Notice: Undefined index: sfPhotosRecursiveLimit in [Gallery目錄]/modules/rss/SimpleRender.inc on line 78

追了一下程式,發現是變數沒處理好。
於是,我修改了 modules/rss/SimpleRender.inc,加了以下這段程式碼處理變數:

        if ( !isset($params['sfPhotosRecursiveLimit']) ) {
                $params['sfPhotosRecursiveLimit'] = $params['sfPhotosRecurseLimit'];
        }

修改過後的某個片段長這樣:

        $params['feedDate'] = $params['sfDate'];

        if ( !isset($params['sfPhotosRecursiveLimit']) ) {
                $params['sfPhotosRecursiveLimit'] = $params['sfPhotosRecurseLimit'];
        }

        /* apply defaults */

跟我一樣,覺得那些 log 很礙眼的,就參考一下吧。
我把 patch 一併貼到官方論壇了(Problem fix for RSS module),不知道會不會被採用就是了。

Technorati Tags: , ,

(Visited 4902 times)
Dec 10

我們在開發 PHP 專案時,時常會把一些常用變數放在某個 PHP 檔案,接著用 require()require_once()incluce()include_once() 等函式將之引入。
若系統目錄很複雜,就會在很多檔案裡面看到類似這樣的語法:

require_once('../../Config.php');

然而,當這種系統必須變動目錄結構與檔案所在目錄時,開發人員就得額外多花些時間來更改上述之程式碼。

其實,PHP 的設定檔中,有個很少被注意到的變數,就是 auto_prepend_file

假設目前有個 PHP 專案,專案根目錄之系統絕對路徑為 /var/www/html/Project,
共用設定檔為 /var/www/html/Project/Config.php 。
我們可以在 /var/www/html/Project 下建立 .htaccess 檔案,內容如下:

php_value auto_prepend_file "/var/www/html/Project/Config.php"

而 /var/www/html/Project 目錄下所有的檔案,以及所有子目錄中的檔案都會自行引入 /var/www/html/Project/Config.php 。

PS. 由於 auto_prepend_file 的設定是透過 require() 來實作,使用這種方法要特別注意以下兩點:

  1. 一旦設定了 auto_prepend_file ,該檔案就必須要存在,否則就會有 error ,導致該目錄下的所有 PHP 檔案無法正常執行。
  2. include_path 會影響 auto_prepend_file。

Technorati Tags: , , , , , , ,

(Visited 6011 times)
Sep 27

以下是在 PTT 的 PHP 板看到某篇文章後的心得,以及小小的經驗分享。

Smarty 稍有經驗的人,應該都知道樣板內可使用 {include} 這個 tag 來嵌入其他 template file。
然而,因為 Smarty 內的變數都是全域變數,所以我對這個 tag 的看法是「能不用,就不用」。

用常見的網站論壇系統舉個簡單的例子:

  • A 設計師負責開發顯示 HTML header 的 template,使用了 $title 變數作為 page title,ex:
    <html><head><title>$title</title></head>
  • B 設計師負責開發顯示論壇文章內容的 template,恰巧也使用了 $title 作為文章標題。

若 B 設計師在其 template file 中使用了 {include} 來嵌入 A 設計師的 template file,就可能會產生預期之外的顯示結果。

當然,若是開發團隊已事先溝通好各項變數的命名,就不會有這種情況。
但為了減少此類風險,降低 debug 的難度,我們會選擇使用這種方式:

  • 在系統全域共用的函式檔案中增加負責顯示 HTML header 的 function,例如 function page_header($title) { ...} ,並在 function 中 assign 變數,引入 A 設計師開發的 template file。
  • 在論壇文章內容顯示的程式檔中,呼叫 page_header($title),再 assign 文章標題的變數,引入 B 設計師開發的 template file。

當然,若嵌入的 template file 內沒有任何變數,就不須考量以上的狀況,開發/設計人員可以大膽地隨意使用。 8)

Technorati Tags: , ,

(Visited 6639 times)
Sep 09

昨晚,有個學長透過 MSN 問我有無簡單好用的 PHP 圖形驗證碼,又讓我想到 reCAPTCHA
上一次使用時,reCAPTCHA 僅提供顏色變更;如今,reCAPTCHA 已經開始支援多國語言了。

剛才稍微玩了一下,寫了這個簡單的網頁
reCAPTCHA 的多國語言化相關資訊可以參考 這裡,而我使用的中文化程式碼片段為:

<script type="text/javascript">
	var RecaptchaOptions = {
		custom_translations : {
			visual_challenge : "取得圖形驗證碼",
			audio_challenge : "取得音效驗證碼",
			refresh_btn : "重新整理圖形",
			instructions_visual : "輸入兩個英文單字:",
			instructions_audio : "輸入您聽到的聲音:",
			help_btn : "獲得協助",
			play_again : "重新播放音效",
			cant_hear_this : "將音效下載為 MP3",
			incorrect_try_again : "錯誤! 請再試一次"
		}
	};
</script>

Technorati Tags: , , , ,

(Visited 7436 times)
Jul 18

這兩天在處理 RoundCube 的郵件中文夾檔問題。
原本覺得情況很詭異,因為 Firefox 都很正常,可是 IE 有這兩種狀況:

  • 直接以左鍵點選,中文檔名的附帶夾檔無法下載。
  • 以滑鼠中鍵(開新的 Tab),就正常了。

坦白說,RoundCube 的程式架構實在很難摸,所以追了蠻久的..

我幫忙追問題的 RoundCube 版本是 0.1-STABLE 。
最後追到的解法是去修改 program/steps/mail/get.inc , diff -u 生出來的檔案內容是這樣:

--- program/steps/mail/get.inc.orig     2008-07-18 02:01:46.000000000 +0800
+++ program/steps/mail/get.inc  2008-07-18 15:45:37.000000000 +0800
@@ -106,9 +106,19 @@
       }
     else
       {
-      header(sprintf("Content-Disposition: %s; filename="%s";",
+       $HTTP_USER_AGENT = $_SERVER["HTTP_USER_AGENT"];
+
+       if (strstr($HTTP_USER_AGENT, "compatible; MSIE ") !== false &&
+            strstr($HTTP_USER_AGENT, "Opera") === false) {
+             header(sprintf("Content-Disposition: %s; filename="%s";",
+                     $_GET["_download"] ? "attachment" : "inline",
+                     $part->filename ? rawurlencode(abbreviate_string($part->filename, 55)) :
+                                       rawurlencode("roundcube.$ctype_secondary")));
+       } else {
+             header(sprintf("Content-Disposition: %s; filename="%s";",
                      $_GET["_download"] ? "attachment" : "inline",
                      $part->filename ? abbreviate_string($part->filename, 55) : "roundcube.$ctype_secondary"));
+       }

       // turn off output buffering and print part content
       $IMAP->get_message_part($MESSAGE["UID"], $part->mime_id, $part, true);

關鍵就在... 給 IE 吃的話,要先用 rawurlencode() 處理一遍。 XD

Technorati Tags: ,

(Visited 9626 times)