第一節
何謂model?
Model用於將企業邏輯與使用者介面邏輯分離,使應用程式各部分的邏輯獨立。
Model通常是一個資料庫存取點,更精確的說,Model相當於是一個資料表。
CakePHP將Model的名字預設為資料表名稱的單數型,比如名為'User'的Model,使用的資料表名為'users'。
Model也可含有資料檢驗的規則、關聯的資訊與資料表特有的方法。
這兒列出Cake中User model應該有的長像。
User Model範例, 存放於 /app/models/user.php
<?php
//AppModel具有所有Cake的Model具有的功能
class User extends AppModel
{
// 包含這個變數是個好習慣
var $name = 'User';
// 這兒使用了資料檢驗。請參考"資料檢驗"一章
var $validate = array();
// 這兒定義了資料表間的關聯。詳細資料請見第四節。
var $hasMany = array('Image' =>
array('className' => 'Image')
);
// 這是自己的函式:
function makeInactive($uid)
{
// 自訂的邏輯...
}
}
?>第二節
Model 函式
從PHP的角度看來,Model為繼承自AppModel的類別。AppModel類別的原始定義放在cake/ 目錄內。如果想定義自己的AppModel,可以把它放在app/app_model.php內。自訂的AppModel可以實作多數Model可能共用的方法。AppModel本身繼承自Model類別,在標準Cake函式庫中,被定義於cake/libs/model.php。
這節我們使用的是Cake的Model類別中最常使用的函式,可以到http://api.cakephp.org 取得完整的資料。
使用者自訂函式
下面的範例幫資料表定義了專用的方法,功能是隱藏與顯示blog內的文章內容。
Model 函式範例
<?php
class Post extends AppModel
{
var $name = 'Post';
function hide ($id=null)
{
if ($id)
{
$this->id = $id;
$this->saveField('hidden', '1');
}
}
function unhide ($id=null)
{
if ($id)
{
$this->id = $id;
$this->saveField('hidden', '0');
}
}
}
?>
撈出資料
下面是使用model取得資料的標準方法:
- findAll
- string $conditions
- array $fields
- string $order
- int $limit
- int $page
- int $recursive
傳回條件符合$conditions的所有資料的$fields欄位值,以$order指定的欄位排序,最多傳回 $limit 筆,
由第$page頁開始列(預設由第一頁開始)。
$conditions格式與SQL裡定義的相同,例如:$conditions = "race = 'wookie' AND thermal_detonators > 3"。
當$recursive被設成比1大的整數時,findAll()會繼續向有關聯的Model找下去,層數由$recursive指定。
若某個資料表和另一個資料表相關,另一個資料表又和另一個資料表相關,findAll()就會把這堆相關的資料,一層層撈出。
- find
- string $conditions
- array $fields
- string $order
- int $recursive
傳回第一筆符合條件的資料中的$fields欄位值。如果沒指定欄位,就全部傳回。
$recursive設成1到3的整數時可以叫find()繼續向關聯的Model找下去,層數最多3層。
- findAllBy<fieldName>
- string $value
filedName可以擺放任何欄位名稱。
這堆神奇的函式是以單一欄位值檢索的捷徑。
只要把想要查詢的欄位放在fieldName的位置,把值放在$value ,即可傳回符合條件的值。
下面有幾個例子:
$this->Post->findByTitle('My First Blog Post');
$this->Author->findByLastName('Rogers');
$this->Property->findAllByState('AZ');
$this->Specimen->findAllByKingdom('Animalia');傳回值是一組陣列,格式就和find()或findAll()傳回的格式相同。
- findNeighbours
- string $conditions
- array $field
- string $value
查出符合$field=$value條件的上一筆和下一筆資料內的$field欄位值。$conditions用於濾除不要的資料。
這個函式在需要取得上一筆和下一筆資料時非常有用,可用來一一檢索Model內的內容。指定的欄位資料型態只能是數字或日期。
class ImagesController extends AppController
{
function view($id)
{
// 假設我們要顯示圖片...
$this->set('image', $this->Image->find("id = $id");
// 但同時也想要上一個和下一個圖片...
$this->set('neighbours', $this->Image->findNeighbours(null, 'id', $id);
}
}在這個範例中,首先我們會得到整個 $image['Image'] 陣列,接下來得到$neighbours['prev']['Image']['id'] 與 $neighbours['next']['Image']['id']二個值。
- field
- string $name
- string $conditions
- string $order
查出條件合乎$conditions的資料,依$order指定的欄位排序,傳回$name欄位的值。
- findCount
- string $conditions
計算符合條件的資料筆數。
- generateList
- string $conditions
- string $order
- int $limit
- string $keyPath
- string $valuePath
此函式是取得多組鍵/值組合最簡便的方法-在利用查詢結果建立HTML時,特別有用。
$conditions, $order, 和 $limit 的使用方法和findAll一樣。
$keyPath 與 $valuePath 則是告訴model要拿什麼建立鍵和值的配對。
如果要用Role model的role_name為值,id為鍵,呼叫方法就如同下面所示:
$this->set(
'Roles',
$this->Role->generateList(null, 'role_name ASC', null, '{n}.Role.id', '{n}.Role.role_name')
);
// 傳回值像這樣:
array(
'1' => 'Account Manager',
'2' => 'Account Viewer',
'3' => 'System Manager',
'4' => 'Site Visitor'
);- read
- string $fields
- string $id
用此函式取回目前所指那筆記錄的欄位值,也可以由$id指定。
請注意一點,read()只取第一層的資料,無視於model中的$recursive。若想取得更深層的資料,請用find()或findAll().
- query
- string $query
- execute
- string $query
如果想要執行SQL指令,可使用Model的query()或execute()其中之一。二者的差別在於query()的SQL指令是SQL查詢(需要傳回查詢結果的SQL指令);execute()的SQL指令則是SQL命令(不需要傳回查詢結果的SQL指令)。
使用query()執行SQL指令
<?php
class Post extends AppModel
{
var $name = 'Post';
function posterFirstName()
{
$ret = $this->query("SELECT first_name FROM posters_table
WHERE poster_id = 1");
$firstName = $ret[0]['first_name'];
return $firstName;
}
}
?>
複雜的查詢條件(使用陣列)
Model裡的查詢函式指定查詢條件的方法很多,其中最簡單的是使用SQL的WHERE子句。
如果需要更複雜的條件控制,則可使用陣列。
陣列讓程式更容易閱讀也更容易建立查詢,使用的語法同時將查詢條件裡各元素(欄位、值、運算子等)分離,便於操作。
如此一來不僅能讓Cake查詢動作更具效能,更能確定SQL語法的語意是否合於需求。
最基本的陣列式查詢看起來就像這樣:
使用陣列查詢條件的基本範例:
$conditions = array("Post.title" => "This is a post");
// Model內使用範例
$this->Post->find($conditions);這個結構一看就知道他的意思是:找到所有標題是"This is a post"的文章。
這裡可以注意一點,我們可以只輸入"title"指定欄位名稱。
但實際要建立查詢指令時,最好連Model名稱一起指定。
如此可以讓程式碼更清析,也可以避免未來發生不相容的問題。
那麼別的條件查詢起來又如何呢?一樣容易。
假設我們要找所有標題不是"This is a post"的文章:
array("Post.title" => "<> This is a post")只要在表示式前加上'<>'就全部完成。
Cake看得懂SQL裡所有合法的運算元,包括比較用的像LIKE、BETWEEN或REGEX,只要在運算元和運算式(或值)間留個空白。
其中一個例外是IN(...)的比對。
假設我們想要找到標題等於三個之一的文章,程式得這麼寫:
array("Post.title" => array("First post", "Second post", "Third post"))加入更多的條件就和在陣列中加入鍵/值對一樣容易:
array
(
"Post.title" => array("First post", "Second post", "Third post"),
"Post.created" => "> " . date('Y-m-d', strtotime("-2 weeks"))
)Cake預設動作會把所有條件以AND結合:
上頭的條件表示把建立時間在這二星期內,且標題在三個其中之一的文章找出來。
如果想要改變這個預設行為也很容易:
array
("or" =>
array
(
"Post.title" => array("First post", "Second post", "Third post"),
"Post.created" => "> " . date('Y-m-d', strtotime("-2 weeks"))
)
)Cake接受所有SQL裡可以使用的布林運算元,如AND,OR,NOT,XOR等,同時不分大小寫。
查詢條件也可以無限地串連。
例如在Posts和Authors間有hasMany/belongsTo的關係,就表示要在Post上使用LEFT JOIN。
再例如想找到Bob所發表的文章,且內容含有某關建字或發表日期在最近二星期內:
array
("Author.name" => "Bob", "or" => array
(
"Post.title" => "LIKE %magic%",
"Post.created" => "> " . date('Y-m-d', strtotime("-2 weeks")
)
)儲存資料
想要把資料存入Model中,必須提供要存的資料內容,透過save()方法儲存。
資料格式看起來像這樣:
Array
(
[ModelName] => Array
(
[fieldname1] => 'value'
[fieldname2] => 'value'
)
)如果你想用同樣的格式把資料顯示在網頁上,最簡單的方法就是使用HTML Helper。
它會負責建立表單元件,同時以Cake想要的方式命名。
然而你也可以不使用它:只要確定表單元件的名稱格式像這樣data[Modelname][fieldname]。
$html->input('Model/fieldname')還是最容易,不是嗎?
自網頁表單上傳來的資料會自動以這種格式存放,然後被放在controller裡的$this->data變數裡。
如此一來,要把網頁上輸入的資料存起來,就變得相當容易。
Controller裡的edit()函式看起來會像這樣:
function edit($id)
{
//註:Property model自動被載入,放在$this->Property。
// 檢查是否有表單資料...
if (empty($this->data))
{
$this->Property->id = $id;
$this->data = $this->Property->read();// 產生表單欄位
}
else
{
// 試著儲存資料,save()函式會自動進行資料檢驗
if ($this->Property->save($this->data['Property']))
{
// 快速顯示一段訊息,然後重新導向
$this->flash('Your information has been saved.',
'/properties/view/'.$this->data['Property']['id'], 2);
}
// 如果發現有任何錯誤,處理的程式碼放這兒
}
}注意一下儲存動作被放在條件式中:當我們企圖存放資料時,Cake會自動使用Model內設定的方法檢驗資料的合理性。
如果想知道更多關於資料檢驗的內容,請參考"資料檢驗"一章。
如果資料存入前不需要檢驗資料,可以這麼呼叫save($data, false)。
另一個有用的函式是:
- del
- string $id
- boolean $cascade
刪除$id指定的資料,或刪除model目前所指的資料。
如果這個model和別的model相關聯(關聯陣列中設有相依的鍵),那麼當$cascade被設成true時,這個方法也會同時把關聯model中的資料刪除。
成功刪除時,傳回true。
- saveField
- string $name
- string $value
用來儲存單一欄位的資料。
- getLastInsertId
傳回最新建立的資料的ID。
Model的回呼函式
我們幫Model加了一些回呼函式,讓您在Model做任何動作之前與之後,有機會額外做一些事。要
在您的應用程式內加入這樣的功能,只要在Cake model的子類別內重新定義這些函式即可。
- beforeFind
- string $conditions
beforeFind()回呼函式會在查詢動作執行前被呼叫,請在此放入任何詢查前想做的事。
當您回呼函式內因為某些因素而不想讓查詢動作繼續,可以傳回false,查詢動作會在函式結束後也一併中止;
相反的,如果一切正常,也記得傳回true,否則不會有任何結果傳回。
- afterFind
- array $results
透過這個回呼函式修改查詢結果,或放入任何查詢後想做的事。
這函式的參數就是查詢的結果,傳回值則是修改後的資料。
- beforeValidate
這回呼函式內放著檢驗資料前要做的事。
在資料被檢驗之前可以修改資料內容。
也可以使用model的data變數($this->data)取得資料,一一檢驗內容是否合理,
再配合Model::invalidate()便可設計更複雜的檢驗邏輯。
函式傳回false時,save()函式會中斷執行。
- beforeSave
這回呼函式內放著儲存前要做的事。
這函式會在資料確認合理後立即被執行,接下來資料才會被儲存(如果資料被認為不合理,save()就會中斷,這個回呼函式當然也就不會被執行)。
同樣的,如果你想讓儲存動作繼續,就讓函式傳回true,否則傳回false。
下面的用法是在資料儲存前,依不同的資料庫引擎更改不同的時間格式:
// 由HTML Helper建立Date/time 欄位:
// 這段程式會被放在view裡
$html->dayOptionTag('Event/start');
$html->monthOptionTag('Event/start');
$html->yearOptionTag('Event/start');
$html->hourOptionTag('Event/start');
$html->minuteOptionTag('Event/start');
/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
// Model的回呼函式同時轉換時間與資料,以便儲存
// 這段程式碼可以在Event model中找到:
function beforeSave()
{
$this->data['Event']['start'] = $this->_getDate('Event', 'start');
return true;
}
function _getDate($model, $field)
{
return date('Y-m-d H:i:s', mktime(
intval($this->data[$model][$field . '_hour']),
intval($this->data[$model][$field . '_min']),
null,
intval($this->data[$model][$field . '_month']),
intval($this->data[$model][$field . '_day']),
intval($this->data[$model][$field . '_year'])));
}- afterSave
這裡放的是儲存動作結束後想做的事。
- beforeDelete
這裡放的是資料刪除前要做的事。傳回false可以中止刪除動作,傳回true則會繼續。
- afterDelete
把資料刪除後想做這事放在這個回呼函式中。
沒有留言:
張貼留言