2007年5月15日 星期二

CakePHP使用手冊-何謂model?(一)

轉貼自 http://www.ezluk.org/

第一節



何謂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






把資料刪除後想做這事放在這個回呼函式中。



沒有留言:

網誌存檔

關於我自己

Aspire freedom , Hope to do Soming make self complete ~