2012年3月21日 星期三

[PHP] CodeIgniter (PHP Framework) 筆記


圖片來源:http://codeigniter.com/


最近因工作上的關係,重溫 PHP & MySQL 的懷抱。說真的,我上一次寫 PHP & MySQL 已經是 2005 年的事情,但我離 PHP 還沒那麼遠,至少 2009 年還有寫過。最近工作上採用 CodeIgniter Framework,算是第一個我接觸的 PHP Framework 了。我記得在 2009 年時,我還很討厭 framework 這類的東西,但工作幾年後,我發現 framework 在某個角度來說是很有用的,例如工作交接、維護與傳承。


老實說用 framework 開發速度不見得快,但好處之一就是可以讓一群人依循某種共同的開發架構(MVC)撰寫程式,如此一來要接手的人比較不會太痛苦,至少痛苦的機率小了那麼一丁點。這邊就先簡短記錄一些筆記。


效能:


參考 Google Search 的第一筆資料 - PHP Framework 的效能比較。簡單的說,在一些常見的 framework 中,CodeIgniter 效能不算差的,但與原生 PHP 來比,效能還是差了一大截。


架構:


稱得上很標準的 MVC 吧?目錄結構有 application/controllers、application/models 和 application/views ,如其名的架構,其中寫 controller 時,大部分都是用一個  class 的架構,依照該 class 來實作:


class Welcome extends CI_Controller {
        public function index()
        {
                echo "Page at /welcome";
        }
        public function test()
        {
                echo "Page at /welcome/test";
        }
        public function update($x = NULL)
        {
                echo "Page at /welcome/update";
        }


所以,你的網頁上就有 http://domain/index.php/welcome、http://domain/index.php/welcome/test、http://domain/index.php/welcome/update 和 http://domain/index.php/welcome/update/x 四種網址位置(其中最後的 x 是變數)。


接著 Models 的部分,也是繼承某個 class 的寫法:


class MyModel extends CI_Model {
        function get_all_entries()
       {
                $query = $this->db->get('mytable');
                return $query->result();
       }
}


如此一來,在 controller 裡頭就可以用:


class Welcome extends CI_Controller {
        public function index()
        {
                $this->load->model('MyModel');
                $this->MyModel->get_all_entries();
        }
}


而 views 比較像傳統 PHP 用法,不用是個 class 而是像以前寫 PHP 那樣夾雜 HTML、PHP、CSS 和 Javascript 等,而這個 view 能接收的參數需要從 controller 傳進來:


class Welcome extends CI_Controller {
        public function index()
        {
                $x = array('123','456');
                $this->load->view( 'myview' , array( 'data' => $x ) );
        }


接著在 application/views/myview.php 中,就可以透過 global 變數 $data 取得資料。


 缺點:


錯誤訊息不見得會顯示


有道是越難除錯的錯誤,通常是最蠢的問題,對於 CodeIgniter 來說,像 controller 和 models 這種 class 架構來說,不小心漏寫{}時,系統不見得會顯示錯誤,此時只會看到 500 internal server error 這種訊息,這跟直接用 PHP 會顯示錯誤訊息來說,實在很不方便。除此之外,如果使用 MySQL 時,未安裝 php5-mysql 相關套件時,也一樣空白一片。


這些真的還滿糟糕的。


Session 預設為 client side session 架構


對 PHP 來說,原先有 $_SESSION 使用,但在 CodeIgniter 必須改用 $this->session->all_userdata() 這類的存取方式,但真正棘手的是它的實作方式,預設是把 session 資料進行加密儲存在 cookie 裡頭,除了安全問題外,本身 cookie 就跟瀏覽器的實作有關,所以不見得所有資料都可以正常透過 setcookie 方式儲存起來,例如 $session['中文字'] = true 在 2.1.0 版就會發生問題!更別說 cookie 資料被破解、使用限制等等的。


另一個作法是把 session 資訊儲存在 Databases 裡頭,如此也可能導致 DB 連線頻繁而產生效能危機。


更多資訊可以參考:Session 的瓶頸與解決方法,在此就是文中第三種方式。


 以上算是目前粗淺使用的經驗。


2012年3月15日 星期四

[Linux] 使用 Tarball 更新 OpenSSL @ Ubuntu 10.04

又到了一年一度機器被掃的時刻了,這次碰到的問題是 OpenSSL 版本太舊,有資安疑慮。


然而在 Ubuntu 上安裝時,直接用 ./config 後,安裝完的路徑卻不太對。網路上有看到別人用 ln -s 的方式強制切換位置,但安裝其他軟體時,一樣容易碰到 openssl header 跟 openssl library 版本不合的問題,摸索一下才找到正確的解法。


$ sudo apt-get remove openssl libssl-dev


$ tar -xvf openssl-w.x.yz.tar.gz
$ cd openssl-w.x.yz
$ ./config --prefix=/usr
$ sudo make install


如此一來才安全地搞定了。確認 OpenSSL 版本:


$ openssl version -a


只是更新完還有一堆東西要重編 Orz


2012年3月13日 星期二

[Python] 使用 PyPNG (Python PNG encoder/decoder) 產生 PNG 圖檔

sequential color random color


因研究關係,需要產生一些圖片像素有特殊規則的圖檔,找了一下剛好有 PyPNG 可以使用。


Python PNG encoder/decoder


$ tar -xvf pypng-0.0.12.tar.gz
$ cd pypng-0.0.12
$ sudo python setup.py install


程式碼:


import png
import random


rawRow = []
rawColumn = ()
width = 1024
height = 1024
for i in range(256):
        if len(rawRow) >= height:
                break
        if i == 0:
                continue
        for j in range(256):
                if len(rawRow) >= height:
                        break
                if j == 0 :
                        continue
                for k in range(256):
                        if len(rawRow) >= height:
                                break
                        if k == 0:
                                continue
                        if len(rawColumn) < width*3:
                                rawColumn = rawColumn + (i,j,k,)
                                #rawColumn = rawColumn + (random.randint(1,255),random.randint(1,255),random.randint(1,255),)
                        else:
                                rawRow.append( rawColumn )
                                rawColumn = (i,j,k,)


f = open("test"+str(width)+"x"+str(height)+".png","wb")
w = png.Writer(width,height)
w.write( f, rawRow )
f.close()


程式碼有點醜,但堪用 XD 所以還是好好珍惜光陰吧!


2012年3月12日 星期一

Android 開發教學筆記 - 研究 Renderscript 以 Gravity 為例


From: http://code.google.com/p/renderscript-examples/wiki/Gravity


這是一個重力特效的範例。看過 Renderscript HelloWorld 的架構後,很快就能了解 Gravity,他們倆是一樣的架構,只是畫圖的部份不一樣。


這邊有四個檔案 Gravity.java、GravityRS.java、GravityView.java 和 gravity.rs,以 GravityRS.java 和 gravity.rs 最為重要,因此挑這兩個檔案研究一下:


gravity.rs:


(gTouchX, gTouchY) 座標資訊


用來決定重力中心位置,這會從螢幕上的 TouchEvent 後,慢慢傳遞到這兩個變數,所以在畫面上任一點點一下,則改變重力中心位置


Point 資料結構(delta、座標和顏色)


用來紀錄每一個畫在螢幕上的點資訊,包括座標、顏色,而 delta 則是用來計算與重力中心距離相關的數值,在畫面上則是用來決定靠近重力中心的速率,也就是座標變化量


void initParticles()


用來初始化 Point 位置,首先先得知有多少 Point,接著得知 Width 跟 Height 後,把所有 Point 座標用亂數建立出來,並設定顏色,而靠近重力中心的座標變化量就先定為 0


int root()


計算各 Point 與重力中心點 (gTouchX, gTouchY) 的位置,並決定該 Point 移動到重力中心點的座標變化量(越接近移動越快)。最後,則是透過 rsgDrawMesh(partMesh); 畫出各個 Point,並且 return 1 代表 1ms 更新畫面一次,也代表座標會重算一次


GravityRS.java:


void init(RenderScriptGL rs, Resources res, int width, int height) 裡頭包括決定 Point 個數、用 Mesh 結構把 Point 集中起來(方便畫圖),剩下的則是相關的初始化動作。


Android 開發教學筆記 - 研究 Renderscript 以 HelloWorld 為例

ImportSample


此例是一個很簡單的 Renderscript 的 HelloWorld,程式跑起來後,螢幕上只會顯示一個 "Hello World!" 字串,並且隨著你點擊螢幕的位置,該字串就會在那個位置上顯示。


使用方式:


[File]->[Project]->[Android]->[Android Sample Project]->[Android 4.0]->[RenderScript > HelloWorld]


AddSampleProject


接著簡述架構,共有 4 個檔案需留意:


HelloWorld.java


HelloWorld.java 就像一般 Android app 中的角色,為一個 Activity,裡頭新增一個 HelloWorldView 後,再把它加到自身的 View 身上。除此之外,則是對 Android app 的程式流程資訊(onResume、onPause)傳給 HelloWorldView 處理。


HelloWorldView.java


HelloWorldView.java 是一個繼承於 RSSurfaceView 的物件,用來負責初始化 Renderscript 所需的環境資源,以及接收 View 上頭的事件資訊進行處理,例如 onTouchEvent 等,而此例則是接收 TouchEvent 的位置後,把資訊傳遞給 Renderscript 以決定畫字串的位置。


初始化 Renderscript 運行的環境:


RenderScriptGL.SurfaceConfig sc = new RenderScriptGL.SurfaceConfig();
RenderScriptGL mRS = createRenderScriptGL(sc);
// Create an instance of the script that does the rendering
HelloWorldRS mRender = new HelloWorldRS();
HelloWorldRS mRender.init(mRS, getResources());


更新字串位置:


if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        mRender.onActionDown((int)ev.getX(), (int)ev.getY());
        return true;
}


HelloWorldRS.java


void init(RenderScriptGL rs, Resources res)


負責將 HelloWorldView 初始化 Renderscript 所需的執行環境資源接收起來,用全域變數紀錄。接著執行自身函數 void initRS()。


void initRS()


初始化 mScript 變數,將 Java 與 Renderscript 接起來。其中 mScript = ScriptC_helloworld 代表要使用 helloworld.rs 程式。初始化完後,再 bind 到 Renderscript 執行環境。


void onActionDown(int x, int y)


把座標資訊傳遞給 mScript(helloworld.rs) 使用。


HelloWorld.rs


HelloWorld.rs 的工作主要就只是依據指定的 (gTouchX, gTouchY) 座標把 "Hello World!" 字串印出。負責印出的函式是 int root(void),回傳的整數代表更新螢幕的時間(ms),如回傳 20ms 代表每 20ms 作一次 root 內容函式。


From: http://developer.android.com/guide/topics/renderscript/graphics.html
A root() function. This is the main worker function for your Renderscript and calls Renderscript graphics functions to render scenes. This function is called every time a frame refresh occurs, which is specified as its return value. A 0 (zero) specified for the return value says to only render the frame when a property of the scene that you are rendering changes. A non-zero positive integer specifies the refresh rate of the frame in milliseconds.


colorCode


因此,想要體會 20ms 執行 root 內容函式的話,那更新一下 helloworld.rs 片段程式碼,將 rsgFontColor(1.0f, 1.0f, 1.0f, 1.0f); 更新為 rsgFontColor(rsRand(1.0f), rsRand(1.0f), rsRand(1.0f), 1.0f); 如此一來,就可以看到字串顏色不斷得變化囉。(該程式碼只是字型顏色改成亂數產生而已)


最後一提,不曉得這是不是一個很傷硬體的範例?程式執行沒多久後,我那塊 PandaBoard 就會掛掉 :P 或許 root 裡頭改成回傳 200(ms) 也適合,因為 0.2 秒的反應速度對人來說還 ok 囉。