首頁‎ > ‎電子期刊‎ > ‎2007 年 2 月號‎ > ‎

介紹Cakephp與MVC


Leads to frustrating code to maintain and update ?

以往以來,撰寫php的程式設計師都會遇到一個問題。

我引用這個投影片

http://www.phpontrax.com/downloads/trax_presentation.ppt

的一個小片段:

 
<?
$link = mysql_connect('localhost', 'myuser', 'mypass');
if (!$link) {
die('Could not connect: ' . mysql_error());
}
If($submit) {
$sql = "INSERT INTO my_table (name,address,city,state,zip) VALUES (";
$sql .= "’$name’,’$address’,’$city’,’$state’,’$zip’)";
mysql_query($sql);
} else {
$result = mysql_query("SELECT * FROM my_table WHERE id = 1");
$userArray = mysql_fetch_array($result);
}

?>
<div>My HTML code blah blah</div>
<form> Name: <input />

</form>…

你該不會真的這樣寫吧...我記得這是我初學php的時候的寫法。而現在許多書籍的範例都不錯,至少會讓你寫出來看起來像這樣:

 
<?
require_once("config.inc.php");
require_once("database.inc.php");

$dbh = dbConnect();
If($submit) {
$sql = "INSERT INTO my_table (name,address,city,state,zip) VALUES (";
$sql .= "’$name’,’$address’,’$city’,’$state’,’$zip’)";
$dbh->query($sql);
} else {
$result = $dbh->query("SELECT * FROM my_table");
$userArray = $dbh->fetchRow($result);
}
printHeader();
?>

<div>My HTML code blah blah</div>
<form> Name: <input />

</form>…

我相信可能十個php設計師有九個是這樣寫的。

很多人會說:「這又有什麼不好?」 我最常聽到的甚至會說這樣子的程式碼直覺簡單易懂。

說真的,投影片上的名言:Leads to frustrating code to maintain and update. 其實一點也沒有在開玩笑。

我再舉個例子。

我最近看見一個商業公司的網站,主要是使用smarty來撰寫。在smarty的官方網站上,有提到這是為了要解決以往html與程式碼混 淆不清的情況,有的人稱他為「義大利麵式的程式碼」。而我仔細翻翻了那個網站,卻也發現這樣子的作法而上述的第二段程式碼並無差別,只是單純地將html 與code分開,但卻還是無法解決sql語法維護的問題。單純只有兩三個表格的程式不會有啥問題,我們討論的是有將近10個以上表格的系統。你會發現,最 嚴重的狀況就是編輯器畫面中有一半是where和join,長長的語句需要一直斷行在斷行,不然實在是做不下去。

如以下程式碼:

 
if($postdata['opt1']) $opt1string="col1='".$opstdata['opt1']."'";
if($postdata['opt2']) $opt2string="col2 like '%".$opstdata['opt2']."%'";
if($postdata['opt3'] && $postdata['opt4'])
$opt3string="col3 between '".$postdata['opt3']."' and '".$postdata['opt4']."'";
$sql="select * form Student where $opt1string $opt2string $opt3string ...blah blah";

if($postdata['opt5'])
{
$sql.="join StudentScore on Student.ID = StudentScore.StudentID where col6='{$postdata['opt5']}'";
if($postdata['opt6'])
$sql.="and col7='Is there any way to maintain sql string?'";
else
$sql.="and col8='I can't type any more...'";
}
else if($thereIsAnotherOption)
{
$sql.="this drives me crazy...";
}
else
{
$sql.="oh...god...";
}

要是這樣的頁面有20張,將來在維護程式碼的時候,你就得維護20個這樣的sql語法,偏偏這20個很像卻又不一樣。又因為客戶的需求,或是有個 bug,會造成你總是來來回回地取消註解,測試印出,確定正常然後再將除錯碼註解起來。你在做的事情總是一些重複的檢查,重複的語法,重複的邏輯,而似乎 你的價值也只有同等於維護這些重複的事物。當你看不下去了,有新的想法了,正想要改code的時候,你的老闆會說「能動就好」然後又開始接下一個專案。一 而再再而三地這樣下去,軟體就沒有品質可言了。

為了改善這些情況,我們需要MVC來幫助我們釐清應用程式的架構。

什麼是MVC

Model,指的就是資料模型。有別於以往的開檔讀檔作法,現在的應用程式都需要資料庫,來將整個應用程式的資料與狀態存在資料庫中。以往我們對資料庫的設計就是傳統的Relational Model(關連式模型), 你會先定義存放資料的表格,定義欄位,然後將各種表格的關係利用foreign Key(外部主鍵)關連起來。在查詢的時候,你便能夠使用select ... join的方式,一次能夠取得多個表格資料。然而就實際的應用來說,資料庫表格的命名也會趨向實際的名稱,如果你是留言版,你可能會有個posts表格, 作為發表的紀錄用,裡面是一筆一筆的post。

由於是透過SQL語法進行存取,字串與資料的轉換就出現問題。例如說使用者的資料其實是陣列,可是在塞入資料庫前卻必須轉成SQL的字串語 法。或者資料庫傳回的是二維陣列,但是使用者的資料可能只需要字串,你又得做很多循序讀取的工作才能轉成字串。因此造成了SQL語法的維護困難。

如果應用到物件導向的觀念,posts表格就好像是就好像是post的物件集合,而一筆post資料,其中的欄位就好像post物件的所有屬性,這個就被稱做是ORM(Object-Relational Mapping)。 這對於不熟悉關連式資料庫的設計師而言相當有用,因為物件導向的想法可以充分利用在整個系統中。但也有相對的缺點,就是當要進行計算加總的時候,還是得乖 乖仰賴資料庫,使用物件的話就會很緩慢。運用ORM的方法,我們可以宣告Post類別,並且可以透過其物件的方法呼叫,來存取資料庫。在這個情況之下,不 管什麼資料庫的存取都是透過物件,物件本身本來就是陣列化的資料,因此就不會有陣列轉字串的苦惱。

View指的就是應用程式顯示的部分,其實你會發現現在的應用系統,大多遵循著MVC的架構,但他的View並不見得是網頁,視窗程式也其 實有可能。只是如果以視窗程式作為View,就會造成一個情況,顯示用的元件會與程式邏輯混合在一起。當然因為是視窗程式,再加上現在不管是Java或 是.Net都將顯示用的元件做得很簡單,混在一起自然也就習慣了。可是這種情況不能夠發生在Web上,畢竟html是不能拿來寫程式,而 javascript也不能拿來存取資料庫,他們最好只負責網頁顯示就好。

Controller就是所謂的應用程式邏輯。因為界在中間,所以對於View而言,此層就是處理View傳送來的使用者資料,進行驗證, 或是後續處理,此外就是將資料傳送給View。對於Model而言,就是將上述的資料轉換成Model可以接受的資料而填入,反之亦然。以往都是我們自己 處理這樣的轉換,如今有了ORM,便不需要花太多時間做這些重複性的工作,便可以專注在應用程式的邏輯。

物件導向的重要性

不知道是不是長久以來的刻苦耐勞所影響,許多設計師們常常都會執著在一些常人無法完成的事,因而降低了產能與增加了維護時間。例如說我記得我的大學 教授,我曾經上了他一堂Socket Programming,就對物件導向大大地搖頭:「物件導向做的這樣複雜的事,還原到CPU能夠執行的組合語言,在邏輯上,結果上絕對都是一樣的,那為 什麼不用最直接用組合語言寫?」。可是這位教授忘記一件事情,如果現在叫他將物件導向可以做到的系統用組合語言寫,他一定會寫不下去。

有人說物件導向是個雙面刃,你善用他就能夠開發出穩定而大的系統,也有人說那樣會造成效能緩慢。但事實上在2006年的今天,物件導向卻被 廣為接受。大多數人所需要用到的「應用系統」,無論是網頁或是視窗程式,比較偏向使用物件導向的觀念來開發。但少數要求效能的系統,例如說http server,可能用物件導向寫就不太適合了。由於CPU和記憶體的成本越來越低,效能也都遵守著莫耳定律在前進,軟體開發的模式只會越來越朝向設計面而不是實做面。

php是一種好上手的直譯式語言,還記得很久很久以前,有人也稱他為動態網頁語言。但實際上各位都知道,它絕不只是個撈資料庫寫網頁的 script而已。php擁有廣大的函示庫,與perl不相上下,與unix系統連結性高,幾乎unix有的函示庫,都可以用php呼叫。在幾年之前,也 有看見滿多嵌入式的主機,寧願用php來撰寫Web介面管理程式,因為他速度也快。因為這些優點,php成長了很久,很多非資訊相關科系的若想要寫網頁程 式,都在寫php,就知道他的接受度其實相當高的。但是畢竟,學習物件導向還是有其門檻,所以並未被大多數php設計師所接受。

可是在這幾年,Java和.Net的崛起,甚至來了一個Ruby,他們在Web Programming領域的佔有度大幅度地向上攀升。php的佔有度雖然高,卻沒有在成長,各位看google trend就知道,在這裡我尋找的關鍵字就是"asp .net, java j2ee,ruby rails, php framework"。

從圖中可以很顯地見到幾個趨勢,java和.net其實應該要算不相上下,但php的使用者對於framework這個字眼,就沒有 ruby的使用者對於rails這個字眼來得熟悉了。我想這代表雙方對於物件導向的理解程度有相當大的差異,ruby本身就是純物件導向語言,而ruby on rails又是一個針對Web 2.0而設計得相當好的MVC Framework,真是來勢洶洶。php的設計師要怎樣堅守陣地呢?經過我私下瞭解,發現多數php的設計師還是寧願自己開發輪子。一方面是並未徹底瞭 解物件導向,造成不清楚php的MVC Framework帶來的物件重複使用性,會在專案中有多大的幫助,二方面是深怕這樣子下一代的php設計師便會像使用了微軟的.Net Framework一樣,如果沒有Framework裡的功能,就絲毫無法自行開發或維護Web應用程式。php本來就是在寫Web應用程式,要是因為有 Framework而不熟html或是javascript,那就啥都寫不出來了。

這樣的疑慮並沒有錯,可是我認為這是在無法看見原始碼的情況下,才會發生的問題。感謝開放原始碼,讓我們能夠任意瞭解他人更好的創意,而不單只是使用,甚至可以進而協助修改。所以在接下來的文章中,我會經常花時間解釋cakephp的架構,以及他做到了什麼。

logo-mini.gif

所以正在讀這篇文章的各位讀者,如果你與我有相同的想法,認為php的設計師們應該要改變了。 那你一定會需要這個cakephp framework

Cakephp

Model, View, Controller Architecture

cakephp是一個標準的MVC Framework。 也就是說Model(資料層),View(網頁),Controller(應用程式邏輯)的程式碼是分開的。為了要讓初學者快速上手,使用很多慣例性的作 法,但也部分提供一些可以修改的範圍,在之後會慢慢介紹。我們會以下面兩個圖做為例子。一個是以往的寫法,一個是在cakephp中的寫法。

View Helpers for AJAX, Javascript, HTML Forms and more

現在的網業設計逐漸朝向Web 2.0,為了讓使用者有更好的觀感,使用了Ajax。cakephp預設提供了三個Helper,可以在View中使用。 例如說$ajax->autocomplete(...)可以產生一個自動完成的textbox,或者是$html->form(...)可 以產生一個html表單,還有$javascript->event(...),可以產生一 段<script></script>區塊,裡面放著是script.aculo.us的處理事件方法。

Built-in Validation

表單驗證是很必要的工作,cakephp的作法是將驗證的型態定義在Model裡,然後用Model->validate方法來驗證,得到的驗證正確與否的資料,再交由Controll通知View來產生錯誤訊息。

Application Scaffolding,Application and CRUD code generation via Bake

cakephp可以利用一支工具程式叫做bake來快速地定義Model,並且產生相關的sql,controller,view的程式碼。這樣就像是在烤蛋糕一樣 :)

Access Control Lists

有獨特的ini based權限控制檔,並且有附Aco和Aro兩個類別讓你可以在程式裡任意存取設定。當然一般來說可能都還會在資料庫裡有個user table,不過也可以使用cakephp的作法,會是比較方便修改的方式。

Data Sanitization

有許多人會試著尋找使用sql或html的漏洞來將你的網站資料弄亂,你可以使用Sanitize->paranoid, Sanitize->html, Sanitize->sql來選擇性的過濾所有的post資料,以防止惡意的攻擊。

View Cache

我想這是如同smarty一樣的功能,終究是一定要有的。一個Web應用程式有很多部分是靜態的,但是由於cakephp將view拆成許多小部分,因此可以選擇性地讓靜態的部分產生快取,如此就不需要經過php程式執行過就可以輸出,加快整體的效能。


總結

因為這些上述的功能,cakephp將目錄也隨著功能作配置。

Controller的程式碼就放在app/controllers,View的程式碼放在app/views,依此類推。

可以見到Controller的額外功能就是Component,如果在Controller裡宣告 了$conponent=array("Security"),便可以在Controller的method裡使用$this-> Security->dosomething()。

Model使用的是ORM的方式,卻不需要你真的去寫對應程式碼。當然其代價就是你在規劃table的時候就得照著cakephp的慣例 了。只要你在$hasOne, $hasMany, $belongsTo, $hasAndBelongsToMany裡宣告好要對應的表格,就可以很輕鬆地使用ORM。接著使用Model->findAll()就可以取得 所有資料,當然這些資料都幫你join好,或者是Model->save()他就會幫你判斷有修改過的資料然後存回。

View的檔案必須為.thtml的副檔名。而在其中是與smarty->assign()一樣的作法,只要你呼叫 Controller->set('key','value'),view中就有這樣子的一個$key變數可以用,而且值就是'value'。然而 對於重複性的資料,由於view中還是允許<?php ?>的程式碼,也就是說你還是可以用foreach將資料輸出。

View有一種作法叫做layout,檔案在app/view/layouts也可以說是共用樣版,除了default.thtml,你也 可以選擇性地載入其他的共用樣版。但可惜的是共用樣版不支援多層次的作法,所以目前只能夠在共用樣版裡使用$contents_for_layout來輸 出所要求的view。

app/view/elements裡面放著的是選擇性的輸出樣版,其實也是同等於彌補了共用樣版的缺點。也就是說你可以在任何一個 view裡呼叫$this->renderElement('element.thtml'),如此便可以選擇性地輸出任何一個放在 elements目錄的樣版。

app/view/helper就是如同之前所說html,ajax,javascript的helper一樣,這裡放的應該要是繼承自class Helper的php程式。


app/webroot放的就是除了thtml以外的所有靜態頁面,包括js(javascript),css,img

(圖片),files,當然你可以繼續加入目錄。cakephp使用了mod_rewite,讓你可以透過
 
http://host/application/js, http://host/application/img ...
等來存取這些資料。

app/vendors是存放外部的函示庫,這樣就可以使用vendor(xxx)來將函示庫include進來,這個資料夾也同等於/vendors。

連結參考

Comments