高見龍

iOS app/Ruby/Rails Developer & Instructor, 喜愛非主流的新玩具 :)

Doctrine, the PHP ORM Framework

image

Doctrine是一個在PHP上的資料庫ORM(Object Relational Mapper),最近手邊的幾個小案子透過Doctrine來實作。ORM的確是讓開發者少寫不少的SQL查詢,但相對的效能一定是比簡單的SQL要來得 差,我想魚跟熊掌要兼得並不容易,便利跟效能之間總有個天平讓開發者來自已調整,所以ORM好不好用就見人見志囉。

一、前置作業:

1. 系統需求

依官網上面提到的,Doctrine需要PHP 5.2.3以上的版本,除此之外就不用再另外安裝其它的套件或函式庫即可運作,不過如果要使用PDO(PHP Data Object)的話,則可能會需要另外安裝PDO驅動程式。

2. 檢查PDO驅動程式

如果你要用PDO的話,想要知道你的主機上是不是有安裝你所需要的PDO驅動程式,只要一行簡單的PHP程式放到主機上執行:

1
<?php phpinfo(); ?>

如果你有看到一段PDO的資訊,那差不多應該就是有安裝了,再確認一下是不是有你要的版本,以及是否啟用即可。

二、取得原始檔

有幾種取得Doctrine的方法:

  • SVN(Subversion)
  • Pear
  • Zip-package

如果你熟悉SVN的操作的話,這應該是最簡單使用的了。如果你只是想試玩一下Doctrine的功能的話,你可以下載官網上打包好的Sandbox套件來玩。我個人比較喜歡官方打包好的壓縮檔 :)

什麼是Sandbox套件?

Doctrine網站的下載區有貼心的打包了免設定的方便套件,直接下載、解壓縮後,就可以拿來試玩了。這裡面包括了一些範例schema檔案以及可以匯入的資料檔,讓你可以很快的就可以開始體驗Doctrine的優點。

三、開始第一個專案

在Doctrine裡,Doctrine_Record是最基本的元件。資料庫裡的每個資料表都會有一個對應的Doctrine_Record類別。這個類別是以Active Record這個設計模式實作的,所以在其它語言像是在ROR(Ruby on Rails)上才看得到方便功能,在Doctrine裡也可以使用到了,即使是複雜的資料庫操作,透過這個類別也可以簡單的完成。

在使用Doctrine_Record來建立資料表時,如果沒有特別指定Primary Key的話,Doctrine會自動幫你加一個叫做id的主索引欄位(自動編號)。接下來你需要做的事情,就是建立一個繼承自 Doctrine_Record類別的子類別,然後透過setTableDefinition()以及hasColumn()就可以來建立你的資料表。

為了單純一些,我們這裡只先用一個表格做範例(多個表格之間的關聯性操作,例如一對一、一對多、多對多等關係,再另外開幾篇再寫)。假設我們現在要建立一個 叫做member的會員資料表,這個資料表裡會有id、username、password以及建立日期這幾個欄位,我們可以這樣做:

檔案:models/Member.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
     class Member extends Doctrine_Record
     {
         public function setTableDefinition()
         {
             // 預設將會使用跟"user"做為資料表的名稱(同名)
             // 並且會自動加一個欄位id的主索引鍵
             $this->hasColumn('username', 'string', 30);
             $this->hasColumn('password', 'string', 30);
         }

         public function setUp()
         {
             $this->actAs('Timestampable');
         }
     }

除了手工打造模型類別外,你也可以使用YAML格式的檔案來建立資料表的schema檔案,底下這個範例的設定跟上面這個類別是一樣的意思,而且也可以透過Doctrine的一些指令來互相轉換。

檔案:schemas/schema.yml

1
2
3
4
5
Member:
  columns:
    username: string(30)
    password: string(30)
  actAs: [Timestampable]

這裡要注意的是,YAML的縮排是有意義的,所以要特別注意縮排的層級。你可以用一行簡單的語法就可以把YAML檔案(.yml)轉換成模型類別(.php):

檔案:createMode.php

1
2
3
<?php
     require_once 'config/doctrine_config.php';
     Doctrine::generateModelsFromYaml('schemas/schema.yml', 'models');

執行之後看一下models資料夾有一個自動產生的generated資料夾(如下圖),你會看到有一個Member.php以 及BaseMember.php。其中,Member.php是可以讓你自己定義一些功能用的,而BaseMember.php裡的內容則是 依據YAML的內容幫你寫好的。(要特別注意BaseMember類別會在每次執行轉換的時候重新產生一次,而Member類別只會產生一次)。

image

我們現在有一個透過YAML建立的Doctrine_Record類別,接下來就可以把它匯入資料庫來建立資料表了。

檔案:modelToDatabase.php

1
2
3
4
<?php
     require_once 'config/doctrine_config.php';
     // 建立資料表
     Doctrine::createTablesFromArray(array('Member'));

這樣就搞定了!看一下資料表的樣子:

image

如前面說的,它會自動加上id這個欄位,而Timestampable則會自動幫忙加上created_atupdated_at這兩個欄位。

現在我們有一個資料表以及一個對應的模型類別可以讓我們來練習資料的基本操作了(新增、修改、刪除及查詢)。

新增:

檔案:testInsert.php

1
2
3
4
5
6
7
<?php
    require_once 'config/doctrine_config.php';

    $myTable = new Member;
    $myTable->username = 'eddie';
    $myTable->password = 'mypassword';
    $myTable->save();

資料表欄位的指定,除了上面這種物件式的存取方式外,也可以使用陣列的方式:

1
2
3
4
5
<?php
  $myTable = new Member;
  $myTable['username'] = 'eddie';
  $myTable['password'] = 'mypassword';
  $myTable->save();

不用寫什麼insert..into..,只要一個save()就完成了! 回頭看一下資料表就會發現,有一些神奇的事情它幫忙做了。上面這段程式碼執行後,member資料表會長得像這樣:

image

修改:

假設剛剛寫入的那筆資料的id編號是1號,更新方法是先找到它,然後一樣用save()來更新:

檔案:testUpdate.php

1
2
3
4
5
6
7
<?php
    require_once 'config/doctrine_config.php';

    $myTable = new Member;
    $record = $myTable->getTable()->find(1);
    $record->password = 'mynewpassword';
    $record->save();

結果如下:

image

你會發現,password欄位變成”mynewpassword”之外,它的更新時間(updated_at)也跟著更新了。

刪除:

跟更新差不多,也是先找到那筆資料後,再給它一個delete()

檔案:testDelete.php

1
2
3
4
5
<?php
    require_once 'config/doctrine_config.php';

    $myTable = new Member;
    $myTable->getTable()->find(1)->delete();

查詢:

假設我要找出id編號是1號的資料,可以這樣做:

1
2
3
<?php
  $myTable = new Member;
  $result =$myTable->getTable()->find(1);

除了透過find方法外,Doctrine還有它自已一套叫做DQL的語法,使用方法大致如下:

檔案:testDQL.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
    require_once 'config/doctrine_config.php';

    $myTable = new Member;
    $record = $myTable->getTable()->find(1);
    $record->password = 'mynewpassword';
    $record->save();
    $result = Doctrine_Query::CREATE()
                ->select('*')
                ->from('Member')
                ->where('id = ?', 1)
                ->fetchArray();

    print_r($result);

印出來的結果如下:

image

上面的DQL相當於底下的SQL:

1
select * from member where id = 1;

其實DQL的細節也可以另外來寫一篇 :)

結論

老實說,如果只是簡單的查詢,Doctrine寫起來不見得比原本的SQL快,而且效能又比較差一些,但如果是比較複雜的查詢,透過Doctrine包裝好 的一些方法,可以不用去想那些單引號、雙引號的問題外,也可以安全的避開可能的危機(例如SQL Injection) ,就如同最前面說的,便利跟效能,我會比較偏向便利的這一方。

更多的細節,可參考Doctrine官網手冊,寫得滿詳細的。

上面的程式碼我也有打包了一份供大家參考囉 :)

檔案下載

使用前請先看一下config/doctrine_config.php裡面的資料庫相關設定。

相關連結

AMFPHP - 搭起 Flash 與 PHP 的美麗橋樑

image

Flash在跟Server在交換資料,比較常見的是一般的文字訊息或是XML,我自己在趕時間的時候,會偷懶的隨便echo一個字出來知會Flash說server程式已經做完了或是程式執行之後的結果,不趕時間且資料量較多的時候,則會乖乖的用XML。不過,最近開始我比較常用的是AMF跟JSON這兩個方法,其實都還滿方便的,各有其優缺點。

AMF(Action Message Format),由Adobe公司推出的規格,主要是用來給flash以RPC的方式來交換資料用的,在flash player 6的版本就已經開始支援(AMF0),直到flash player 9才又推出新規格(AMF3)。其它細節可參考:http://en.wikipedia.org/wiki/Action_Message_Format

接下來就來個簡單的實作吧 :)

1. 下載AMF gateway

找個自己順手的語言

  • AMFPHP:這是PHP的
  • PyAMF:這是Python的(還不熟,努力學習中)
  • AMF.NET:這是.NET的

光看名字大概就能猜到它的後端是用什麼做的。其它還有for Java或其它的,有興趣的可以繼續google翻翻看。

2. 安裝AMF gateway(以AMFPHP為例)

安裝方法很容易的,只要把下載下來的壓縮檔解開,放在Web server底下一個自己找得到的路徑就行了。我是放在http://127.0.0.1/test/amfphp/底下。

AMFPHP有提供一個很棒的Service Browser,網址是http://127.0.0.1/test/amfphp/browser,這是個用Flex做的介面,可以清楚列出目前在AMF gateway上的服務。

3. 在AMF gateway上新增一個服務

我做了一個簡單的php類別檔,存檔為callme.php,並放在AMF gateway的services資料夾裡面(文末有原始檔連結)。PHP不像Java或AS3一樣硬性規定類別名稱一定要跟檔名一樣..至少php5還沒有,但建立這習慣也不錯。

1
2
3
4
5
6
7
8
<?php
  class callme
  {
      function callmeplease($text)
      {
          return $text;
      }
  }

上面,我做了一個callme的類別,然後裡面放了一個callmeplease的方法,這個方法會接收一個參數,執行完成之後,會把傳入的參數return回來。這裡只是簡單直接return而已,更複雜的資料庫存取程式也可以寫在這裡…

接下來看一下Service Browser,會發現寫寫的那個新的服務在的左手邊的列表裡:

image

在右邊的Text輸入文字,按一下右邊的「Call」,應該就可以直接看到結果。(中文支持問題請見文末補充)

4. 新增一個Fla(這裡以AS3.0為例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import flash.net.*;

// 新增一個NetConnection物件
var test:NetConnection = new NetConnection();

// 連上AMF gateway
// 這裡每家AMF gateway的實作方法應該都有差別,細節請記得看該實作說明
test.connect('http://127.0.0.1/test/amfphp/gateway.php');

// 準備一個Responder物件來接收事件,用法及參數請參考F1說明
var returnResult:Responder = new Responder(ok, ng);

function ok(res:Object):void {
  trace(res);
}

function ng(res:Object):void {
  trace(res);
}

//以上,我做了ok跟ng兩個簡單小function,分別是trace出它們接收到的物件

test.call('callme.callmeplease', returnResult, 'hi, flash');
// 這裡是最重要的動作,就是直接用call這個方法
// "呼叫"在gateway上面的callme這個服務裡的callmeplease這個方法
// 第二個參數是接收反應的responder,第三個參數是給callmeplease的參數

以上,callme.callmeplease會把”hi, flash”傳給AMF gateway,然後gateway回傳”hi, flash”回來給flash。順利的話,Ctrl + Enter執行,就會在output視窗看到輸出的東西了。

就這樣,你的Flash已經可以跟各家有支援AMF的gateway”對談”了。

上面提到的原始檔在這裡可以拿得到(其實沒幾行字,就一個.php跟.fla)

還是看不懂嗎? 沒關係,這裡有個很棒的影音教學,看完跟著作一遍就知道大概是怎麼運作的了 http://www.gotoandlearn.com/player.php?id=78

希望對大家有幫助 :)

補充1:

AMF不久之後已經可以在Zend Framework裡找到native module了,ZF真的是包山包海了 :) ((而且ZF-AMF就是由AMFPHP裡的主導人Wade Arnold來親自操刀)。

補充2:

AMFPHP預設的編碼是不支援中文的(會出現亂碼),不過只要修改一下gateway的程式碼即可。

使用文字編輯器,開啟AMFPHP資料夾的gateway.php,應該可以找到一段跟字碼有關的:

$gateway->setCharsetHandler("utf8_decode", "ISO-8859-1", "ISO-8859-1");

把它修改成:

$gateway->setCharsetHandler("utf8_decode", "UTF-8", "UTF-8");

或是如果你的PHP模組裡有iconv的話也可以這樣改:

$gateway->setCharsetHandler("iconv","UTF-8","UTF-8");

記得,不要改flash裡的useCodePage = true,雖然可能也ok,但這是走回頭路…能使用Unicode就盡量使用它吧 :)

dojo toolkit入門(一)

image

雖然我個人還是比較喜歡jQuery的簡潔方便,而且一些常用的function也都jQuery化了,不過畢竟DojoZend Framework 1.6版後就被officially的收進去了,為了ZF寫起來的完整性,還是花點時間來研究一下。

廢話不多說,先來試玩一下!

步驟一:下載它

dojo toolkit的網站即可下載(不過其實也可以不用下載..請見步驟二)

步驟二:安裝它

基本上,下載壓縮檔回來後,解壓縮後放在你的網頁的目錄(放哪裡都可,只要你能找得到就好)。假設我放在js/dojo底下,接下來只要一行:

<script type="text/javascript" src="js/dojo/dojo/dojo.js"></script>

這樣就算完成安裝了..

除了這種方式外,也有不用下載整包回來的方法,就是使用別人家(CDN or Google)準備好的服務:

1. AOL的CDN(Content Delivery Network)

<script type="text/javascript" src="http://o.aolcdn.com/dojo/1.1.1/dojo/dojo.xd.js"></script>

2. Google Ajax Libraries API:

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/dojo/1.1.1/dojo/dojo.xd.js"></script>

註:Google上還有host別的Ajax library(如jQuery、MooTools..等),詳情請洽Google Ajax Libraries API

使用線上版跟本機版各有優缺點,線上版的好處就是省頻寬,畢竟dojo整包檔案解開壓縮也不小,有十幾MB,若能使用像Google這種又快又穩定的主機頻 寬來host,多少也是可以省一些的;當然缺點就是畢竟是別人家免費提供的服務,萬一有什麼”萬一”的話,可能會導致全站的功能都出問題。而本機版的優缺 點就剛好跟線上版的相反(個人比較偏好本機版安裝法)。

步驟三:使用它

在步驟二的地方,當你指定<script>src的時候,其實就已經使用了。先來試一下DOM selector跟onLoad事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Dojo Test</title>
<script type="text/javascript" src="http://o.aolcdn.com/dojo/1.1.1/dojo/dojo.xd.js"></script>
<script type="text/javascript">
  dojo.addOnLoad(function(){
    dojo.byId("test1").innerHTML = "Hello, Dojo";
    dojo.connect(dojo.byId("btn1"), "onclick", function(evt){
      alert("Hello again, Dojo!");
    })
 });
</script>
</head>
<body>
<div id="test1"></div>
<input type="button" value="clickme!" id="btn1" />
</body>
</html>

線上測試

簡單的說明上面用到的幾個功能:

1. dojo.addOnLoad

就是在頁面讀取完成之後會做的事,有點像一般的onLoad功能,不過順序在它之後

2. dojo.byId(“test1″)

這將會回傳在頁面裡面叫id叫做test1的元素(在上面這裡是一個div),功能有點像原來的document.getElementById()。再透過設定這個回傳回來的物件的innerHTML的值,就可以直接讓這個Div區塊裡面的值有變化。

3. dojo.connect()

這個比較有趣,這個function可以傳入四個參數。第一個是參數是物件,第二個參數是事件(字串),第三個參數也是物件(可省略),第四個參數是要綁的事件(字串),可以另外在別的地方定義好function再掛上來,也可以像上例一樣直接給它一個anonymous function 簡單的說,dojo.connect()可以翻譯成「把某個事件(或function),綁到某個物件的某個事件(或function)上」,直接來看上面的例子:

1
2
3
dojo.connect(dojo.byId("btn1"), "onclick", function(evt){
  alert("Hello again, Dojo!");
})

以上例來翻譯成白話文就是說「我把一個anonymous function,綁到id為btn1的那個按鈕的onclick事件上」,所以,當觸發這個按鈕的onclick事件(也就是點擊它),就會跟著觸動這個function,丟出”Hello again, Dojo!”的字樣出來。

更多的細節可再參考Dojo的線上手冊。

目前國內用dojo的人看起似乎並不多,日前去書局弄了一本Dojo-The Definitive Guide,仍在研究中,看來這個Dojo能做的事情還挺多,有心得再來繼續貼。

Omit the PHP closing tag “?>”

自從開始改用ZF後,開始慢慢的調整自己的code style,過程也許會痛苦,但相信結果應該會是甜美的。在ZF的Coding Standard裡有一段是這麼寫的:

For files that contain only PHP code, the closing tag (“?>”) is never permitted. It is not required by PHP, and omitting it prevents the accidental injection of trailing whitespace into the response.

看不太懂哪來的injection,後來再翻了一下PHP的官方資料才發現也有提到這段:

The closing tag of a PHP block at the end of a file is optional, and in some cases omitting it is helpful when using include() or require(), so unwanted whitespace will not occur at the end of files, and you will still be able to add headers to the response later. It is also handy if you use output buffering, and would not like to see added unwanted whitespace at the end of the parts generated by the included files.

後來自己動手做了一下實驗:

檔名 inc.php

1
2
3
<?php
// do something
?>

檔名:test.php

1
2
3
4
<?php
require_once('inc.php');
session_start();
?>

執行test.php,看起來是沒什麼大問題;不過如果在inc.php的”?>”後面再按幾下enter,多加幾行空白行,test.php就會丟出警告訊息”Cannot send session cookie – headers already sent by..”,如果把inc.php的”?>”拿掉就ok了。

原來,在”?>”之後多按的幾個enter(newline)也會被當成資料丟給瀏覽器,所以如果include了這樣的檔案進來,會讓header()session_start()之類的指令丟出”headers already sent by..”的警告訊息,而如果省去結尾的”?>”則可以避免這個問題。

果然魔鬼都是還是躲在細節裡,PHP的手冊還是要多看啊!

順帶一提,ZF有PDF版的離線手冊了,一千多頁,雖然不見得有比線上方便,但沒網路的時候倒還不錯用(我是把它放到PDA裡,上廁所可以看)

Zend Framework之Zend_Tool

看到DjangoRails之類的Web Framework都有可以快速的產生scaffold的工具,甚至連同樣都是PHP framework的CakePHP也有..還好我之前都是在Zend Studio裡直接new一個Zend Framework Project,它也會自動產生基本的架構出來。隨著Zend Framework 1.6版的上市,它也提供了類似的方便工具(不過從目錄名字看起來還是在實驗階段而已..)

我的作業環境是WinXP,如果要在別的系統可參考最底下的參考資料,有提到怎麼修改路徑。

簡單的使用方法如下:

Step 1.

ZF官網下載Zend Framework 1.6,解壓縮之後裡面有個laboratory資料夾,裡面有個ZendL,把它放到你的include_path底下。

Step 2.

在laboratory/Zend_Tool/bin裡有個zf.bat跟zf.php,改一下zf.bat裡的php.exe的路徑(如果放在跟php.exe同目錄就不用改了)

Step 3.

以上設定完成之後,到cmd模式下,輸入zf show version,應該會出現「Zend Framework Version: 1.6.0」字樣

Step 4.

隨便找一個資料夾來試試,輸入zf create project,等一會,它就會幫你把資料夾跟檔案產生好了(如下圖)

image

完成! 這樣就完成了基本的ZF MVC的骨架出來了,也做了簡單的bootstrap跟幾個預設的controller跟view。不過我發現這個Zend_Tool會把ZF的 library整個也複製一份到library底下,雖然是方便發布,不過我個人是習慣不把它放在web資料夾下..

結論:不管是Zend Studio或是Zend_Tool都可以快速的產生架構出來,我還是習慣用自己的架構..

參考資料:Zend Developer Zone