NEKOB-log-E

Play Games, and Follow the Rules

Flower

Posts Tagged ‘php’

PHP Frameworks: necit

這一兩個月以來幾乎都在寫PHP, 想架構, 沒什麼在動老本行.

我實在不算是夠格的PHP Coder, 相對之下我還比較愛寫Perl & C. 不過最近好幾個需要弄PHP的案子, G社(不是Google)的網站, 幫老朋友的車廠寫管理系統, 自己的Rmail Web界面, 一堆東西要寫PHP, 煩都煩死了.

原本打算找個簡單易用的PHP Framework來用, 大概就CakePHP, Zend Framework, CI, Kohana等等的東西, 不過實際用了一下真的覺得實在不能用, 一來是效能問題, 這種Framework根本談不上效能, 對後端DB來說更是大災難, 大概是因為我本身是玩系統出身, 很難接受這種犧牲效能跟擴充性來達到的開發速度; 二來是彈性很糟糕, 尤其是MVC之下要放進Javascript跟AJAX這類做UI必備的部份, 該把Javascript/AJAX塞到view嗎? 還是要塞到control? 這種小麻煩實在讓我用都不想用.

要說我不懂MVC也行, 要說我龜毛也行, 反正我找不到滿意的Framework. 找不到就自己刻吧! 我要的很簡單, 就有Template與Code Logic分離, 能內含一些泛用API的(例如Session, Database Handler, Image Processing), 能支援gettext就好, 重點是乾淨, 我需要乾淨不吃現有Framework/Libs的環境, 讓我可以很輕鬆的調整效能問題.

我喜歡單一入口的設計模式, 所以我會使用Rewrite加上module/action的做法. Javascript跟AJAX的部份無可避免得用上jQuery, Session有現成的memcached跟Database Stored, Image Processing部份直接調用簡單的imagick(其實我也不想用PECL的ImageMagick, 我是自己寫class去呼叫convert), Database Handler直接做個wrapper去搭各種driver就好(ADODB爛到可以, 我很難相信真的有大網站用這玩意兒), 麻煩在Template問題.

如我之前所說, Template裡頭事實上包含了最終輸出的東西, 所以肯定會有swap template問題, 會有gettext問題, 會有Javascript問題, 更會有pre-define AJAX的問題. 用Smarty不是不行, 麻煩是效能爛到有剩(之前就很多人討論過Smarty存在的價值與意義, 畢竟PHP本身就是一個Template Engine了); 其他不管是Smarty-Lite或是Zend_view彈性都很差, 所以只能決定自己弄個簡單的, 這時候就要狗幹一下PHP5, 難道不能有個 include_get 之類的function嗎? 逼著非得用ob_start()才能拿到include檔案執行完的內容; evel要考慮variables scope, 而且又是fork出新的php來跑, 對httpd/fastcgi的衝擊很大的.

再者AJAX也是大問題, 以往慣用XAJAX, 但是XAJAX太混雜, 本身摻雜了view與control, 要在AJAX裡頭搞gettext更是要規避一些很鳥的問題, Javascript/AJAX一搭上Smarty要做一堆{literal}, 更是友善度大減.

揚棄了Smarty與AJAX後, 就得自己解決與面對Template/AJAX部份, 目前基本的方向是直接用統一的module/action來取Template, Javascript, AJAX Backend, 這樣寫一個頁面與功能只要同時製作 html, javascript, backend ajax就好, 剩下的讓template class/engine去搞定, 這樣子撰寫過程就會很純化, 對以往開發其他新網站也會相對簡單.

目前這個framework(如果可以稱作framework的話啦)已經完成了80%左右了, 現在剩下template engine的部份還在參詳, 由於gettext需要textdomain, 所以也只好不免俗的給這個東西取個名字, 就叫做necit了.

現在不打算開放出來給大家用, 畢竟是未完成品, 也是丟出去給大家看到我醜陋的PHP寫法會汗顏的未完成品, 大概等以上幾個網站都完成且通過實戰測試確定好用後, 再考慮要不要Release.

啊? necit是什麼意思? necit = nekobe’s kit = nekit = necit, 如果要一個比較GNU的說法的話, 呃, 那就 Necit Enhanced Common Internet-application Toolkit, 夠噁心了吧.

Convert UTF-8 Character to Codepoint

Pear看到的Codes, 因為用的到, 所以紀錄一下.

  1. function utf8ToCodepoint( $char ) {
  2.     $z = ord( $char{0} );
  3.     if ( $z & 0x80 ) {
  4.         $length = 0;
  5.         while ( $z & 0x80 ) {
  6.             $length++;
  7.             $z <<= 1;
  8.         }
  9.     } else {
  10.         $length = 1;
  11.     }
  12.     if ( $length != strlen( $char ) ) {
  13.         return false;
  14.     }
  15.     if ( $length == 1 ) {
  16.         return ord( $char );
  17.     }
  18.     $z &= 0xff;
  19.     $z >>= $length;
  20.     for ( $i=1; $i<$length; $i++ ) {
  21.         $z <<= 6;
  22.         $z |= ord( $char{$i} ) & 0x3f;
  23.     }
  24.     return $z;
  25. }

另外一個是在這裡看到的, 他的功能多一些, 是可以一次轉一整個String到一個Codepoint Array中

  1. function utf8ToUnicode(&$str)
  2. {
  3.   $mState = 0;     // cached expected number of octets after the current octet
  4.                    // until the beginning of the next UTF8 character sequence
  5.   $mUcs4  = 0;     // cached Unicode character
  6.   $mBytes = 1;     // cached expected number of octets in the current sequence
  7.  
  8.   $out = array();
  9.  
  10.   $len = strlen($str);
  11.   for($i = 0; $i < $len; $i++) {
  12.     $in = ord($str{$i});
  13.     if (0 == $mState) {
  14.       // When mState is zero we expect either a US-ASCII character or a
  15.       // multi-octet sequence.
  16.       if (0 == (0x80 & ($in))) {
  17.         // US-ASCII, pass straight through.
  18.         $out[] = $in;
  19.         $mBytes = 1;
  20.       } else if (0xC0 == (0xE0 & ($in))) {
  21.         // First octet of 2 octet sequence
  22.         $mUcs4 = ($in);
  23.         $mUcs4 = ($mUcs4 & 0x1F) << 6;
  24.         $mState = 1;
  25.         $mBytes = 2;
  26.       } else if (0xE0 == (0xF0 & ($in))) {
  27.         // First octet of 3 octet sequence
  28.         $mUcs4 = ($in);
  29.         $mUcs4 = ($mUcs4 & 0x0F) << 12;
  30.         $mState = 2;
  31.         $mBytes = 3;
  32.       } else if (0xF0 == (0xF8 & ($in))) {
  33.         // First octet of 4 octet sequence
  34.         $mUcs4 = ($in);
  35.         $mUcs4 = ($mUcs4 & 0x07) << 18;
  36.         $mState = 3;
  37.         $mBytes = 4;
  38.       } else if (0xF8 == (0xFC & ($in))) {
  39.         /* First octet of 5 octet sequence.
  40.          *
  41.          * This is illegal because the encoded codepoint must be either
  42.          * (a) not the shortest form or
  43.          * (b) outside the Unicode range of 0-0x10FFFF.
  44.          * Rather than trying to resynchronize, we will carry on until the end
  45.          * of the sequence and let the later error handling code catch it.
  46.          */
  47.         $mUcs4 = ($in);
  48.         $mUcs4 = ($mUcs4 & 0x03) << 24;
  49.         $mState = 4;
  50.         $mBytes = 5;
  51.       } else if (0xFC == (0xFE & ($in))) {
  52.         // First octet of 6 octet sequence, see comments for 5 octet sequence.
  53.         $mUcs4 = ($in);
  54.         $mUcs4 = ($mUcs4 & 1) << 30;
  55.         $mState = 5;
  56.         $mBytes = 6;
  57.       } else {
  58.         /* Current octet is neither in the US-ASCII range nor a legal first
  59.          * octet of a multi-octet sequence.
  60.          */
  61.         return false;
  62.       }
  63.     } else {
  64.       // When mState is non-zero, we expect a continuation of the multi-octet
  65.       // sequence
  66.       if (0x80 == (0xC0 & ($in))) {
  67.         // Legal continuation.
  68.         $shift = ($mState - 1) * 6;
  69.         $tmp = $in;
  70.         $tmp = ($tmp & 0x0000003F) << $shift;
  71.         $mUcs4 |= $tmp;
  72.  
  73.         if (0 == --$mState) {
  74.           /* End of the multi-octet sequence. mUcs4 now contains the final
  75.            * Unicode codepoint to be output
  76.            *
  77.            * Check for illegal sequences and codepoints.
  78.            */
  79.  
  80.           // From Unicode 3.1, non-shortest form is illegal
  81.           if (((2 == $mBytes) && ($mUcs4 < 0x0080)) ||
  82.               ((3 == $mBytes) && ($mUcs4 < 0x0800)) ||
  83.               ((4 == $mBytes) && ($mUcs4 < 0x10000)) ||
  84.               (4 < $mBytes) ||
  85.               // From Unicode 3.2, surrogate characters are illegal
  86.               (($mUcs4 & 0xFFFFF800) == 0xD800) ||
  87.               // Codepoints outside the Unicode range are illegal
  88.               ($mUcs4 > 0x10FFFF)) {
  89.             return false;
  90.           }
  91.           if (0xFEFF != $mUcs4) {
  92.             // BOM is legal but we don't want to output it
  93.             $out[] = $mUcs4;
  94.           }
  95.           //initialize UTF8 cache
  96.           $mState = 0;
  97.           $mUcs4  = 0;
  98.           $mBytes = 1;
  99.         }
  100.       } else {
  101.         /* ((0xC0 & (*in) != 0x80) && (mState != 0))
  102.          *
  103.          * Incomplete multi-octet sequence.
  104.          */
  105.         return false;
  106.       }
  107.     }
  108.   }
  109.   return $out;
  110. }

兩個都能轉出我要的東西, 而且第二個在Parsing上對我比較方便, 但是自從PHP支援Multibyte function後, 就不這麼重要了, 兩個function拿來轉同樣10個字的String, 第一個只要0.000017秒, 第二個卻要整整三倍多的0.000052秒. 看來, 我應該會用第一個吧.

太久沒有去鑽PHP, 反應變遲鈍了, 唉.

Zend Studio 5.5 Build 270 繁體中文化

雖然我是寫C比較多, 但是總是難免需要弄一點UI, 要我寫Win32的東西我會本能性抗拒, 所以一般都是走Web介面. 一旦要寫Web介面, 要繁複在Browser和Console切換, 總是麻煩, 所以我去抓了Zend Studio來專門寫PHP用, 不負所望的, 該有的功能一點也不少(SCP Client, Line Number….etc), 該多的功能我一個都用不到(Local debug, Debug Server, ADODB Interface….), Zend Studio雖然是Java Base的東西, 不過跑起來還算順暢, 可能是因為我機器的RAM都夠吧.

可是用起來有一點非常不滿, Zend Studio會自動偵測你的Locale, 所以一裝好打開就是中文介面, 不過是殘體簡體中文.

雖然說可以在Options裡頭換回英文版, 但是不曉得為什麼, 在我的x31上每次切到英文版, 下次開啟還是簡體中文版, 每次都要去改一下實在較人很不爽, 所以才自己想辦法繁體中文化.

繁體化的方式很單純, 先找出ZendIDE.jar這個檔案, 知道這是所有的Resource jar Archive, 然後解開後用DJ Java Decompiler把zh的Resource Files反組譯後, 用Word(沒錯, 簡繁互轉我真的覺得Word轉的比較好)轉換完後塞回去, 最後用JDK重新compile起來.

這個動作就碰到了一些問題, 只要我重新compile回去後, 一開就發現zh locale完全消失, 雖然符合我想要的樣子啦!(我只是不想看到簡體中文) 不過事情做到一半實在很不爽. 所以去查了一下class file的header, 發現原來是我用的JDK 1.5太新了, Zend Studio用的是JDK 1.4 來Compile的, 於是乎抓了JDK 1.4來compile, 一試成功!

我不知道有多少人也在用Zend Studio, 不過為了推廣繁體中文, 還是放出來給大家分享一下! 請由下頭的連結抓取吧! 抓完放到該放的地方重開Zend Studio就成啦!

請注意, 目前只套用過Zend Studio 5.5 Build 270, 在Win32跟Mac OSX都沒問題.

翻譯部份翻得很爛, 我完全丟給Word轉, 如果有寫要改掉的字串, 請留言或Mail給我吧, 我會更新版本的. 請有抓去用了沒問題的人協助回報一下OS跟Zend Studio版本, 感謝.

Zend Studio 5.5 Build 270 Traditional Chinese Locale

2006/06/11補: 應觀眾要求, 給點Magic Words, 通常軟體註冊最好是註冊給這個軟體的名字, 然後該大寫該小寫的都不要動, 當然加上版號是很蠢的事情, 千萬別做! 前幾天夢到媽祖, 祂托夢給我這一串字 "PT1RT3pBak00RWpNeGNUUndZME16VWtN", 我想東想西想不通, 突然想到媽祖托夢時給我的信封上寫著B64-Rev-B64, 我一下子搞清楚這怎麼一回事了, 原來這就是永久使用的秘訣.

(看不懂我沒辦法….神明托夢總是有點玄機)

有時候不能相信手冊

嗯….太相信手冊會吃虧的.

這幾天在重新把一些PHP Framework補齊, 重寫之前HDD掛點時損失的一些小lib, 同時把以前用普通寫法做的functin都物件化.

弄到i18n的部份時, 以往是靠大量的variables配合require/include去做, 現在學了gettext就想改成gettext base, 結果災難就來了. 首先是PHP最新的手冊上關於gettext的sample.

  1. <?php
  2. // Set language to German
  3. setlocale(LC_ALL, 'de_DE');
  4.  
  5. // Specify location of translation tables
  6. bindtextdomain("myPHPApp", "./locale");
  7.  
  8. // Choose domain
  9. textdomain("myPHPApp");
  10.  
  11. // Translation is looking for in ./locale/de_DE/LC_MESSAGES/myPHPApp.mo now
  12.  
  13. // Print a test message
  14. echo gettext("Welcome to My PHP Application");
  15.  
  16. // Or use the alias _() for gettext()
  17. echo _("Have a nice day");
  18. ?>

照著做之後, 發現gettext()總是不會給我正確的translations, 從apache, php5, extensions的安裝全都檢查過了, 就是弄不好, 開始懷疑是不是自己的 .mo 檔案做錯了, 去找了GNU gettext examples來依樣畫葫蘆寫了一個a.out來測試, 很奇怪的是用C來寫非常正常, 但是PHP的setlocale永遠給我NULL.

手冊應該不會錯才是. 照著GNU gettext的手冊一切正常, 類似的function在PHP下就總是不行, 照手冊的Sample來測試怎麼會連跑都不能跑呢? 加上沒有debug message, 也沒有任何log可以看, 實在不知道該怎麼辦才好.

心想, 該不會是OS問題? Locale可能跟 OS 支援有關係吧. 找一張舊的Debian CD, 在VMWare下裝好測試, 竟然也是一樣的狀況: setlocale(LC_ALL, "") 正常, 但是setlocale(LC_ALL, "en")就不行.

這時候衛斯理說的話就產生作用了: 當所有可能性都不正確時, 最不可能的答案就是答案.

是的, 正解就是, 手冊錯了!

PHP的setlocale並不會複寫這個session用的locale, 必須要配合putenv把locale設定進去, 但是只用putenv還是不成, 因為後頭的bindtextdomain還是要吃setlocale. 也就是說, bindtextdomain要setlocale, 而setlocale要putenv, 所以一定要先putenv再setlocale, 最後bindtextdomain才能正確的讓gettext運作.

  1. <?php
  2. putenv("LC_ALL=en");
  3. setlocale(LC_ALL, "en");
  4. bindtextdomain("myApp", "./locale");
  5. textdomain("myApp");
  6. ?>

這樣寫才會正常, 順序怎麼換都不行. 盡信書不如無書, 盡信手冊不如去STFG. 唉.

Wordspew Spam Patch 2: Sync Spammer’s IP.

Wordspew Live Shoutbox的Spam IP Patch第二彈!!

上一篇改寫過後, 相繼檔掉了一些Spammer’s IP, 但是慢慢發現了幾個小困擾. 首先是一個人收集Spammer’s IP很緩慢, 以為已經檔掉了大多數的Spammer, 但是可能過幾天又跑出一些, 這是第一個問題; 第二個問題是, 我"管理"的Word Press其實有兩份, 每次都要下指令把我這邊的Spammer’s IP跟Ellen的做Sync就覺得很煩人, 加上有位 guest 先生留言認為檔Spammer IP並沒有效用, 我想試試看是否真如他所說, 所以才有了這個版本的誕生.

這個版本的特色是, 加入了一個Spam Feeds的設定, 一行一個URL, 然後提供一個手動功能可以從你列表的URL中獲取他的Spammer’s IP加入自己的, 如此一來就可以跟別人共享SPAM IP資料庫, 不需要各自維護各自的.

當然為了吐出資料, 加上了一個jal_spam_publish的參數讓wordspew本身吐出資料來.

操作流程大致是, 先去找別人的SPAM IP Feed URL, 把URL複製到自己的設定中貼上, 然後想到的時候去Sync一下就成了, 由於是用INSERT IGNORE去塞IP, 所以不用擔心重複的問題.

唯一需要注意的是, 請慎選自己的Sync Feeds, 若一旦Sync到某人的blog檔掉自己的ip, 那可能連自己都沒辦法留言了, 或者是有人把192.168.x.x或172.17.x.x之類的ip加進去, 就會發生有趣的事情.

抓了我這個版本後, 在Wordspew的chat form下會出現一行字提供自己的Spam Feed給大家, 不喜歡就手動刪除吧.

老規矩, 希望有抓走的人comments, 或有問題有bug也可一起交流一下.

下載點在下頭, 希望對大家有幫助.

下載: wordspew-patched2.tar.gz

Slogan更換

我一直喜歡Ellen拍的照片, 雖然我們都不是扛著DSLR + 一堆大砲鏡頭出門的人, 但是手拿著傻瓜級數位相機, 卻能拍出這麼多好照片的人, 真的少見.

Ellen在美感上遠勝於我, 美貌也是. 所以這次挑了一張他拍的北海道照片來當素材, 藍的過頭的天空, 配上色彩鮮明的建築物, 仰望著的角度, 正好就是我想要從文字中看世界的角度.

修好圖後, 在想要用什麼顏色怎麼表現 NEKOB-log-E 呢? 藍天當然就是配上白雲了, 飄渺的白雲配上我拙劣的手繪大概就是這個樣子吧!

不曉得大家喜歡嗎?