CakePHP中文手册【翻译】-Model

Model

1

model是什么?

model是什么?是MVC中的M

它能做什么?把逻辑从表现层中分离出来,独立应用程序的逻辑。

一般来说,model是一个数据库的访问入口,更特殊的是,它是某个特定数据库表会的访问入口。缺省的,每个model使用数据表,此表的表名是它自己的复数形式,例如,‘Usermodel使用’users‘表。Model也可以包含数据验证规则,关联信息,以及指定到它使用的表的方法。下面是一个简单的User modelCake中的样子:

User model实例,保存在app/models/user.php

<?php

 

//AppModel gives you all of Cake's Model functionality

 

class User extends AppModel

{

    // Its always good practice to include this variable.

    var $name = 'User';

 

    // This is used for validation, see Chapter 11.

    var $validate = array();

 

    // You can also define associations.

    // See section 6.3 for more information.

 

    var $hasMany = array('Image' =>

                   array('className' => 'Image')

                   );

 

    // You can also include your own functions:

    function makeInactive($uid)

    {

        //Put your own logic here...

    }

}

 

?>

 

2

Model 功能

PHP的角度看,model扩展了AppModel类。AppModel类在cake/目录中定义,如果你想创建你自己的类,请把它放在app/app_model.php.它应该包含在2个或更多的model之间可共享的方法。它自己扩展了Model类,此类是一个标准的Cake库,定义在cake/libs/model.php中。

本节介绍大部分常用的Cake Model函数。记住使用http://api.cakephp.org获取全部参考,这是非常重要的。

用户定义函数

Model中,某个特定表的函数的例子是一对用来隐藏/显示blog中的post的方法。

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(如果有的话)并且上限为$limit条的指定字段,并从页面$page(缺省为页面1)开始列出。$conditions看起来和他们在SQL语句里的一样,例如$conditions = "race = 'wookie' AND thermal_detonators > 3"

$recursive设置为一个大于1的整数时,findAll()操作会尽可能返回与findAll()找到的那些相关联的model。如果你的财产(Property)有多个拥有者,并且他们拥有多个合同(contract),那么一个Property modelfindAll()方法将递归返回这些相关的model

find

  • string $conditions
  • array $fields
  • string $order
  • int $recursive

返回匹配$conditions的第一个记录的指定字段(或所有字段,如果没有指明)

$recursive选项设置为一个13之间的整数值,findAll()操作尽可能返回与findAll()找到的那些相关联的model。递归查找最深可到第3层。如果你的财产(Property)有多个拥有者,并且他们拥有多个合同(contract),那么一个Property model的递归find()方法将返回最多三层深的关联model

findAllBy<fieldName>

  • string $value

这些魔幻函数可以作为一个快捷方式来查询你的表,并获得一行给定了特定的字段,特定的值的记录。将你想查询的字段名字放上去,并使用骆驼峰的格式。实例(在Controller里)可以为

$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

返回一个相邻的Model数组(仅用指定的字段),并由$field$value指定,由SQL条件$conditions进行过滤。

在这种情形下,它是非常价值的。你可以让用户通过'Previous''Next'的链接在你的model入口里获取有序的记录。它仅对数字和日期字段有效。

class ImagesController extends AppController

{

    function view($id)

    {

        // Say we want to show the image...

 

        $this->set('image', $this->Image->find("id = $id");

 

        // But we also want the previous and next images...

 

        $this->set('neighbours', $this->Image->findNeighbours(null, 'id', $id);

 

    }

}

 

它会给我们一个全集的$image['Image']数组,并在view中接着有 $neighbours['prev']['Image']['id'] 以及$neighbours['next']['Image']['id']

field

  • string $name
  • string $conditions
  • string $order

返回一个字符串,即匹配$conditions 条件以及$order顺序的第一个记录的单个字段。

findCount

  • string $conditions

返回与给定条件匹配的记录数。

generateList

  • string $conditions
  • string $order
  • int $limit
  • string $keyPath
  • string $valuePath

本函数是可以快捷的得到一键/值列特别是对手动创建一个modelHTML select标记。使用$conditions, $order, 以及$limit这些参数来作出一个findAll()请求。通过$keyPath$valuePath,告诉model在哪里找到生成列表的键与值。例如,如果你打算生成一列基于Role Model的角色,并且键为它们的整数id,完整的调用可以如下:

$this->set(

    'Roles',

    $this->Role->generateList(null, 'role_name ASC', null, '{n}.Role.id', '{n}.Role.role_name')

);

 

//This would return something like:

array(

    '1' => 'Account Manager',

    '2' => 'Account Viewer',

    '3' => 'System Manager',

    '4' => 'Site Visitor'

);

 

read

  • string $fields
  • string $id

使用本函数得到当前已加载记录的字段以及它们的值,或者为$id的记录。

请注意,read()操作仅仅获得第一层相关model值,与model$recursive值没有关系。为了获取其他层,使用find()findAll().

query

  • string $query

execute

  • string $query

可以使用modelquery()execute()来执行自定义的SQL调用。两者之间的区别是query()用来执行自定义的SQL查询(返回结果),而execute()用来执行自定义的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;

    }

}

?>

 

复杂的查找条件(使用Array

大多数model的查找调用会以这样那样的方式传入一组条件。解决此最简单的方式就是在SQL里使用WHERE子句,但是如果需要更多的控制,你可以使用数组。数组的使用可以使读取更加清楚,同时也更加简单,而且使查询的创建变得相当简单。本语法也将查询的元素(字段,值,操作等)变成严谨但可控制的部分。这让Cake可以生成最有效的查询,并保证恰当的SQL语法,以及脱离每个查询的独立部分。

最基础的是,一个基于数组的查询可以这样:

使用Array的基本查询条件实例

$conditions = array("Post.title" => "This is a post");

 

//Example usage with a model:

$this->Post->find($conditions);

 

本结构自己就能非常好的解释它自己:它会查找任何title"This is a post"post。注意我们在前面可以把“title”作为字段名,但是在建立查询时,一直指定model名将会是一个优秀的实践,因为它改进了代码的清晰性,并帮助阻止未来的冲突,因为在未来你可能会选择改变你的schema。那么其他的匹配呢?同样简单。让我们查找一些title"This is a post"post

array("Post.title" => "<> This is a post")

就在表达式前增加一个'<>‘。Cake可以解析任何有效的SQL比较操作符,包括匹配表达式,如LIKEBETWEEN,或REGEX,只要你在表达式或值等操作符之间留一个空格。这里还是有一个例外,那就是IN)样式的匹配。查找title为给定值的post可以是:

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,意思就是,上面的片段仅仅匹配那些在2周前创建的post,而且title需要匹配其中一个给定的值。虽说如此,我们可以很容易的查找那些匹配任何条件的post

array

("or" =>

    array

    (

        "Post.title" => array("First post", "Second post", "Third post"),

        "Post.created" => "> " . date('Y-m-d', strtotime("-2 weeks"))

    )

)

Cake接受所有有效的SQL布尔操作,包括ND, OR, NOT, XOR等,并且它们可以为大写,也可以为小写,不管你喜欢哪种。这些条件也可无限嵌套。PostsAuthors之间的关系是hasMany/belongsTo,它会使POST上的查询有一个LEFT JOIN操作。你打算查找所有包含某个关键字或在2周前创建的Post,但是你想约定这些PostBob编写,可以是:

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'

        )

)

为了让你的数据按这种方式发表在Controller上,最简单的方式就是使用HTML Helper来实现,因为它创建表单元素,这些元素都以Cake期望的方式命名的。当然,你也没有必要使用它:保证你的表单元素按类似data[Modelname][fieldname]的方式命名。最简单的方式是使用$html->input('Model/fieldname')

表单Post的数据会自动格式化,并将$this->data 放入Controller里。因此保存web表单的数据是非常简单的。一个合适的controller编辑函数可能看起来如下:

function edit($id)

{

 

   //Note: The property model is automatically loaded for us at $this->Property.

 

   // Check to see if we have form data...

   if (empty($this->data))

   {

        $this->Property->id = $id;

        $this->data = $this->Property->read();//populate the form fields with the current row

   }

   else

   {

      // Here's where we try to save our data. Automagic validation checking

      if ($this->Property->save($this->data['Property']))

      {

         //Flash a message and redirect.

         $this->flash('Your information has been saved.',

                     '/properties/view/'.$this->data['Property']['id'], 2);

      }

      //if some fields are invalid or save fails the form will render

   }

}

注意,save操作是怎样在一个条件语句中放置的:当你试图将数据保存到model时,Cake自动试图根据你提供的规则验证数据。为了学到更多关于数据验证的知识,参看第12。如果你不想让save()验证数据,使用save($data, false).

其他有用的存储函数:

del

  • string $id
  • boolean $cascade

删除指定$idmodel,或者model的当前id。如果这个model和其他model相关联,并且'dependent'间已经在关联数组中设置,本方法也会删除那些关联的model,如果$cascade设为true的话。

如果成功返回true

saveField

  • string $name
  • string $value

用来保存单个字段值。

getLastInsertId

返回最近创建记录的ID

Model回调

我们已经加入了一些model回调(函数),它们允许你在某个model操作前或后悄悄的加点逻辑在里面。为了在应用程序中使用本功能,使用提供的参数,并且在Cake Model里覆盖这些函数。

beforeFind

  • string $conditions

beforeFind()回调函数仅在一个查找操作之前执行。可以在本方法里放入任何预先查找的逻辑。当你在model里覆盖时,如果你想执行查找,返回true,若是想取消,返回false

afterFind

  • array $results

使用本回调会修改一个查询操作返回的结果,或者进行其他的post查找逻辑。Model查找操作返回的结果就是本函数的参数,并且返回值就是修改的结果。

beforeValidate

model验证前使用此回调修改model数据。它也能用来增加额外的,更加复杂的验证规则,使用Model::invalidate()验证。在本上下文中,model数据可以通过$this->data访问。本函数也必须返回true,否则save()执行会终止。

beforeSave

在本函数中加入任何预保存的逻辑。本函数在model 数据验证后(假设它一验证,否则save()调用取消,并且本回调不会执行)立即执行。但是在数据保存之前,如果你想保存工作本函数也会返回true,本函数也应该返回true,如果你想让保存操作继续,如果取消,则返回false

beforeSave的一个用法可以是格化式指定数据库引擎上的时间数据。

/ Date/time fields created by HTML Helper:

// This code would be seen in a view

 

$html->dayOptionTag('Event/start');

$html->monthOptionTag('Event/start');

$html->yearOptionTag('Event/start');

$html->hourOptionTag('Event/start');

$html->minuteOptionTag('Event/start');

 

/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

 

// Model callback functions used to stitch date

// data together for storage

// This code would be seen in the 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

在本函数中放置任何预删除逻辑。如果你想让删除操作继续的话,本函数应该返回true。否则你想终止的话则返回false

  • afterDelete

在本回调方法里,每个删除操作之后,放置你想执行的任何逻辑。

3

Model 变量

当创建model时,你需要设置某些特定的变量来访问Cake的功能:

$primaryKey

如果model和一个数据库表相对应,并且表的主键的命名不是”id”,那么使用本变量告诉Cake你的主键名。

$recursive

它将设置Cake使用find()和findAll()操作获取相关联model数据的层数。

假想你有一个组(Group),每组有许多用户(User),而这些用户又有很多文章(Article)。

Model::recursive 选项

$recursive = 0

Cake 获取Group

$recursive = 1

Cake 获取Group以及相关联的Users

$recursive = 2

Cake 获取Group, 相关联的Users, 以及Users关联的Articles

$transactional

告诉Cake是否为本model启用事务(例如,begin/commit/rollback)。设置为一个布尔值。仅当数据库支持时。[1]

$useTable

如果你想使用的数据库表不是复数格式的model名(以及你不打算修改表名),将本变量设置为你想让model使用的表名。

$validate

用来验证传递到model的数据。参看第12章。

$useDbConfig

记住了在/app/config/database.php配置的数据库设置了吗?使用本变量来切换他们-使用在数据库配置文件里创建的数据库链接变量的名称。你已经猜到,缺省的是‘defualt’

4

关联

介绍

CakePHP最厉害的特性之一就是Model提供的关系映射。在CakePHP中,表之间的关系是通过关联处理的。关联就是相关逻辑单元之间的胶水。

CakePHP4中关联:

1.    hasOne

2.    hasMany

3.    belongsTo

4.    hasAndBelongsToMany

当定义model之间的关系时,Cake会自动获取与正在工作的model相关的model。例如,如果一个Post ModelAuthor Model相关,并使用hasMany关联,调用Controller$this->Post->findAll()将会获得Post记录,同时也会获得与他们相关的Author记录。

为正确使用关联,建议遵循CakePHP命名规则(参考附录)。如果你使用了CakePHP的命名规则,你也可以使用scaffolding可视化你的应用数据,因为scaffolding检测并使用model之间的关联。当然,在没遵循Cake的命名规则时,你也可以一直自定义model关联来进行工作,但我们会在后面说明一些技巧。现在,让我们盯住这些规则吧。在这里我们关心的命名规则是主键,Model名,以及表名。

下面是Cake期望这些不同元素名称的概览:(参看附录获取更多关于命名的信息):

1.    外键:【单数model名】_id.例如,可以将指回到Author属于的Post的“author”表的外键命名为”post_id”.

2.    表名:【复数的对象名】,因为我们想存储Blog post和作者的信息,表名应该分别为"posts" "authors"

3.    Model名:【骆驼峰命名,表名的单数形式】。"posts" 表的Model名为"Post""authors"表的model名为"Author"

CakePHPScaffolding希望你的关联与你的列保持同样的顺序。因此如果你有一个Article belongsTo(属于)3个其他的modelAuthor, Editor, Publisher),你需要3个键:uthor_id, editor_id, Publisher_id.Scaffolding期望你的关联和表中的键保持一样的顺序(例如,第一个是Author,第二个是Editor,最后是Publisher)。

为了描述这些关联如何工作,让我们继续使用Blog应用程序作为实例吧。假设我们打算为blog创建一个简单的用户管理系统。也假设它会在不跟踪Users的情况下进行,但是我们希望每个用户都有一个关联的ProfileUser hasOne Profile.Users也会创建留言并且保留他们的关联(User hasMany Comments),一旦我们让用户系统运行,我们会将视线转移到使用hasAndBelongsToMany(Post hasAndBelongsToMany Tags)Posts以及与其相关的Tag对象。

定义和查询hasOne

为建立关联,我们假设你已经创建了User以及Profile Model。为了定义他们的hasOne关联,我们需要为Model增加一个数组告诉Cake它们是怎样关联的。这里是它们如何关联的:

/app/models/user.php hasOne

<?php

class User extends AppModel

{

    var $name = 'User';

    var $hasOne = array('Profile' =>

                        array('className'    => 'Profile',

                              'conditions'   => '',

                              'order'        => '',

                              'dependent'    =>  true,

                              'foreignKey'   => 'user_id'

                        )

                  );

}

?>

$hasOne数组是Cake用来创建UserProfile model之间的关联的。数组中的每个键允许你更进一步的配置关联:

1.    className (必须): 关联的model 类名。

例如,我们会指定'Profile' model类名。

2.    conditions: SQL条件部分,它定义了关系。

我们可能会使用它来告诉Cake只关联Profile,此Profile有一个绿色的标记头,如果我们愿意。为了定义这个条件,可以为此键指定一个SQL条件作为其值:”Profile.header_color = 'green'"

3.    order:关联model的顺序

例如,如果你想让你的关联model以某顺序排列,为此键设置一个SQL排序谓语:"Profile.name ASC"

4.    dependent:如果设置为true,当它销毁时,相关联的model也会销毁。

例如,如果"Cool Blue"Profile"Bob"相关联,并且我们删除用户"Bob","Cool Blue"Profile也会删除。

5.    foreignKey:指向关联的model的外键名。

在这里,你正在操作一个没有遵循Cake命名规则的数据库。

现在,当使用Profile model来执行 find()findAll()时,我们应该也看到相关联的User model:

$user = $this->User->read(null, '25');

print_r($user);

 

//输出:

 

Array

(

    [User] => Array

        (

            [id] => 25

            [first_name] => John

            [last_name] => Anderson

            [username] => psychic

            [password] => c4k3roxx

        )

 

    [Profile] => Array

        (

            [id] => 4

            [name] => Cool Blue

            [header_color] => aquamarine

            [user_id] = 25

        )

)

定义和查询belongsTo

如果User想看到他的Profile,我们需要定义一个关联,因此Profile可以看见他自己的User。在Cake里,可以使用belongsTo关联实现。在Profile model里,我们可以如下操作:

/app/models/profile.php belongsTo

<?php

class Profile extends AppModel

{

    var $name = 'Profile';

    var $belongsTo = array('User' =>

                           array('className'  => 'User',

                                 'conditions' => '',

                                 'order'      => '',

                                 'foreignKey' => 'user_id'

                           )

                     );

}

?>

$belongsTo数组是Cake用来创建UserProfile model之间的关联的。数组中的每个键允许你更进一步的配置关联:

1.    className (必须):你想要关联的model的类名

例如:我们想指定‘User’model 类名。

2.    conditions:定义关系的SQL条件语句

我们可以使用它告诉Cake仅关联活动的User。为了实现它,将键值设置为"User.active = '1'"或者其他类似的。

3.    order:关联model的顺序。

如果你想让你的关联model以某顺序排列,为此键设置一个SQL排序谓语:"User.last_name ASC"

6.    foreignKey: 指向关联model的外键名。

在这里,你正在操作一个没有遵循Cake命名规则的数据库。

现在,当使用Profile model执行 find()findAll(),我们应该也可以看到相关联的User model:

$profile = $this->Profile->read(null, '4');

print_r($profile);

 

//output:

 

Array

(

 

    [Profile] => Array

        (

            [id] => 4

            [name] => Cool Blue

            [header_color] => aquamarine

            [user_id] = 25

        )

 

    [User] => Array

        (

            [id] => 25

            [first_name] => John

            [last_name] => Anderson

            [username] => psychic

            [password] => c4k3roxx

        )

)

 

定义和查询hasMany

尽管UserProfile model已经关联,并且工作正常,让我们再构建我们的系统,以至User记录和Comment记录关联。在User model中可以如下定义:

/app/models/user.php hasMany

<?php

class User extends AppModel

{

    var $name = 'User';

    var $hasMany = array('Comment' =>

                         array('className'     => 'Comment',

                               'conditions'    => 'Comment.moderated = 1',

                               'order'         => 'Comment.created DESC',

                               'limit'         => '5',

                               'foreignKey'    => 'user_id',

                               'dependent'     => true,

                               'exclusive'     => false,

                               'finderQuery'   => ''

                         )

                  );

 

    // Here's the hasOne relationship we defined earlier...

    var $hasOne = array('Profile' =>

                        array('className'    => 'Profile',

                              'conditions'   => '',

                              'order'        => '',

                              'dependent'    =>  true,

                              'foreignKey'   => 'user_id'

                        )

                  );

}

?>

$hasMany数组是Cake用来创建UserComment model之间的关联的。数组中的每个键允许你更进一步的配置关联:

1.    className(必须):你想要关联的model的类名

例如:我们想指定‘Comment’model 类名。

2.    conditions: 定义关系的SQL条件语句

我们可以使用它告诉Cake仅关联通过管理的Comment。为了实现它,将键值设置为" Comment.moderated = 1"或者其他类似的。

3.    order: 关联model的顺序。

如果你想让你的关联model以某顺序排列,为此键设置一个SQL排序谓语:"Comment.created DESC "

4.    limit:你想让Cake获取关联model的最大数

在本例,我们不会获取所有用户的留言,仅仅为5.

5.    foreignKey: 指向关联model的外键名。

在这里,你正在操作一个没有遵循Cake命名规则的数据库。

6.    dependent:如果设置为true,当它销毁时,相关联的model也会销毁。

例如,如果"Cool Blue"Profile"Bob"相关联,并且我们删除用户"Bob","Cool Blue"Profile也会删除。

7.    exclusive:如果设置为true,所有关联的对象在没有让beforeDelete回调运行时在一个SQL语句中会被删掉,

对于比较简单的关联,使用起来很好,因为它能更快。

8.    finderQuery:指定一个完整的SQL语句获取关联。

对于复杂的依赖多个表的关联,这是一种优秀的方法。如果Cake的自动关联不工作,在这里,你可以定义它。

现在,当使用User model执行 find()findAll(),我们应该也可以看到相关联的Comment model:

$user = $this->User->read(null, '25');

print_r($user);

 

//输出:

 

Array

(

    [User] => Array

        (

            [id] => 25

            [first_name] => John

            [last_name] => Anderson

            [username] => psychic

            [password] => c4k3roxx

        )

 

    [Profile] => Array

        (

            [id] => 4

            [name] => Cool Blue

            [header_color] => aquamarine

            [user_id] = 25

        )

 

    [Comment] => Array

        (

            [0] => Array

                (

                    [id] => 247

                    [user_id] => 25

                    [body] => The hasMany assocation is nice to have.

                )

 

            [1] => Array

                (

                    [id] => 256

                    [user_id] => 25

                    [body] => The hasMany assocation is really nice to have.

                )

 

            [2] => Array

                (

                    [id] => 269

                    [user_id] => 25

                    [body] => The hasMany assocation is really, really nice to have.

                )

 

            [3] => Array

                (

                    [id] => 285

                    [user_id] => 25

                    [body] => The hasMany assocation is extremely nice to have.

                )

 

            [4] => Array

                (

                    [id] => 286

                    [user_id] => 25

                    [body] => The hasMany assocation is super nice to have.

                )

 

        )

)

在这里,当我们不打算记录处理过程时,定义"Comment belongsTo User"关联也是一个相当不错的注意,因此2model都能看到对方。当你试图使用scaffolding时,未定义2model之间的关联常常是一个常见的难题。

定义和查询 hasAndBelongsToMany

现在你已经掌握了比较简单的关联,让我们转移到最后一个关联:hasAndBelongsToMany ( HABTM).最后一个是最难的,它会让你团团转,但是它也是其中最有用的一个。如果你有2model,而这2model又一个join表联系在一起,HABTM关联这个时候是有用的。Join表抓住了互相关联的各自的行。

hasMany hasAndBelongsToMany的区别是:对于hasMany,关联的model是不共享的。这对我们接下来的工作是非常有用:关联PostTag model。当一个Tag属于一个Post时,我们不会让他使用完,我们会继续将他和其他Post关联。

为了实现它,我们需要为这个关系建立正确的表。当然你需要为你的Tag model转被一个“tags“表,Post准备一个”posts“表,但是你也需要为此关联创建一个join表。HABTM join表的命名规则是[复数的model1]_[复数的model2],这里,model名按字母排序。

HABTM Join : model样例以及他们的Join

1.    Posts Tags: posts_tags

2.    MonkeysIceCubes: ice_cubes_monkeys

3.    CategoriesArticles: articles_categories

HABTM Join表至少需要包含2个他们连接的model的外键。在我们的例子中,"post_id" "tag_id"就是我们所需要的。

下面导出的SQL看起来是为我们的Posts HABTM Tags实例准备的哟:

--

-- Table structure for table `posts`

--

 

CREATE TABLE `posts` (

  `id` int(10) unsigned NOT NULL auto_increment,

  `user_id` int(10) default NULL,

  `title` varchar(50) default NULL,

  `body` text,

  `created` datetime default NULL,

  `modified` datetime default NULL,

  `status` tinyint(1) NOT NULL default '0',

  PRIMARY KEY  (`id`)

) TYPE=MyISAM;

 

-- --------------------------------------------------------

 

--

-- Table structure for table `posts_tags`

--

 

CREATE TABLE `posts_tags` (

  `post_id` int(10) unsigned NOT NULL default '0',

  `tag_id` int(10) unsigned NOT NULL default '0',

  PRIMARY KEY  (`post_id`,`tag_id`)

) TYPE=MyISAM;

 

-- --------------------------------------------------------

 

--

-- Table structure for table `tags`

--

 

CREATE TABLE `tags` (

  `id` int(10) unsigned NOT NULL auto_increment,

  `tag` varchar(100) default NULL,

  PRIMARY KEY  (`id`)

) TYPE=MyISAM;

随着我们的表已创建,让我们在Post model里定义关联:

/app/models/post.php hasAndBelongsToMany

<?php

class Post extends AppModel

{

    var $name = 'Post';

    var $hasAndBelongsToMany = array('Tag' =>

                               array('className'    => 'Tag',

                                     'joinTable'    => 'posts_tags',

                                     'foreignKey'   => 'post_id',

                                     'associationForeignKey'=> 'tag_id',

                                     'conditions'   => '',

                                     'order'        => '',

                                     'limit'        => '',

                                     'uniq'         => true,

                                     'finderQuery'  => '',

                                     'deleteQuery'  => '',

                               )

                               );

}

?>

$hasAndBelongsToMany数组是Cake用来创建PostTag model之间的关联的。数组中的每个键允许你更进一步配置关联:

1.    className (必须): 你想要关联的model的类名。例如:

在我们的例子中,我们想指定‘Tagmodel 类名。

2.    joinTable:它是为没有遵循Cake命名规则的数据库而准备的。如果你的表不是【复数的model1_【复数的model2】的词语顺序,你可以在这里指定你的表名。

3.    foreignKey: 指向当前modelJOIN表的外键名。

在这里,你正在操作一个没有遵循Cake命名规则的数据库。

4.    associationForeignKey:指向关联model的外键名。

5.    conditions:定义关系的SQL条件语句

我们可以使用它告诉Cake仅关联一个通过验证的Tag。为了实现它,将键值设置为" Tag.approved = 1"或者其他类似的。

6.    order:关联model的顺序。

例如,如果你想让你的关联model以某顺序排列,为此键设置一个SQL排序谓语:" Tag.tag DESC "

7.    limit:  你想让Cake获取关联model的最大数

用来限制获取关联的Tag数。

8.    uniq:如果设置为true,重复的关联对象会被访问者和查询方法忽略。

一般来说,如果关联不同,将他设置为true"Awesomeness"Tag仅可以分配到Post "Cake Model Associations"一次,并且在结果数组中仅出现一次。

9.    finderQuery:指定一个完整的SQL语句获取关联。

对于复杂的依赖多个表的关联,这是一种优秀的方法。如果Cake的自动关联不工作,在这里,你可以定义它。

10.deleteQuery:一完整的SQL语句,用来删除HABTM model之间的关联。

如果你不愿意按Cake的方法完成删除操作或者你的创建是以某种方法自己定义的,你可以在这里提供你自己的查询来改变本方法完成操作任务。

现在,当执行Post modelfind()findAll()调用时,在这里我们也应该可以看到我们关联的Tag model

$post = $this->Post->read(null, '2');

print_r($post);

 

//输出:

Array

(

    [Post] => Array

        (

            [id] => 2

            [user_id] => 25

            [title] => Cake Model Associations

            [body] => Time saving, easy, and powerful.

            [created] => 2006-04-15 09:33:24

            [modified] => 2006-04-15 09:33:24

            [status] => 1

        )

 

    [Tag] => Array

        (

            [0] => Array

                (

                    [id] => 247

                    [tag] => CakePHP

                )

 

            [1] => Array

                (

                    [id] => 256

                    [tag] => Powerful Software

                )

        )

)

保存相关的Model数据

当你操作关联的model时,需要记住的一件重要事情是,保存model数据应该一直由相应的Cake model来完成。如果正在保存一个新的Post以及它的Comment(留言)的话,那么在保存的过程中,你需要使用PostComment model

如果在系统中不存在一个关联的model(例如,你想同时保存一个新的Post和一个相关的Comment),你需要首先保存主要的,或者说是父model。为了知道它如何进行,让我们想象一下,在我们的PostsController中有一个动作(action),它保存新的Post和相关Comment。下面的动作实例假设你已经发布一个PostComment

/app/controllers/posts_controller.php (部分)

function add()

{

    if (!empty($this->data))

    {

        //We can save the Post data:

        //it should be in $this->data['Post']

       

        $this->Post->save($this->data);

 

        //Now, we'll need to save the Comment data

        //But first, we need to know the ID for the

        //Post we just saved...

 

        $post_id = $this->Post->getLastInsertId();

 

        //Now we add this information to the save data

        //and save the comment.

 

        $this->data['Comment']['post_id'] = $post_id;

 

        //Because our Post hasMany Comments, we can access

        //the Comment model through the Post model:

 

        $this->Post->Comment->save($this->data);

 

    }

}

尽管如此,如果父Model在系统中已经存在(例如,加一个Comment到一个已存在的Post),在保存之前你需要知道父ModelID。你可以将此ID作为URL参数,或者作为表单中的一个隐藏元素。

/app/controllers/posts_controller.php (部分)

//Here's how it would look if the URL param is used...

function addComment($post_id)

{

    if (!empty($this->data))

    {

        //You might want to make the $post_id data more safe,

        //but this will suffice for a working example..

 

        $this->data['Comment']['post_id'] = $post_id;

 

        //Because our Post hasMany Comments, we can access

        //the Comment model through the Post model:

 

        $this->Post->Comment->save($this->data);

    }

}

如果ID作为一个表单的隐藏元素传递的话,你可能想命名这个字段(如果你正在使用HtmlHelper),以至在发出的数据里它会中止,在这里,它应该为:

如果PostID是在 $post['Post']['id']...

<?php echo $html->hidden('Comment/post_id', array('value' => $post['Post']['id'])); ?>

按这种方式,父Post modelID可以在$this->data['Comment']['post_id']中访问,并且都为$this->Post->Comment->save($this->data)所准备。

如果你正保存多个子model,这些相同的基本技术都会工作,仅需要将那些save()调用放在一个循环(记住使用Model::Create()清除model信息哟)中。

总之,如果你正保存关联的数据(belongsTo, hasOne, hasMany relations关系),重要的一点是得到父modelID,并将他保存到子model

保存 hasAndBelongsToMany 关系

保存hasOne, belongsTo, 以及hasMany的关联model是非常简单的:你仅需要将外键设置为关联modelID。一旦完成,你也仅需要调用modelsave()方法,这样所有的东东都会正确的关联了。

但是对于hasAndBelongsToMany,就有点晦涩了,但是我们已经特意让它尽可能简单。继续我们的例子,我们需要一些表单,这些表单将Tag关联到Post。现在让我们创建一个表单,此表单创建post,以及将他们和一个存在的tag列表关联。

事实上,你可能喜欢创建一个表单来创建新的tag,并将他们在空中关联-但是出于简单,我们仅需要为你说明如何将他们关联,并让你得到它。

Cake中,当你正保存它自己的modeltag名(如果你在使用HTML helper)应该是'Model/field_name'.让我们从创建post的表单部分开始吧:

创建post/app/views/posts/add.thtml 表单

<h1>Write a New Post</h1>

  <table>  

      <tr>   

         <td>Title:</td> 

         <td><?php echo  html->input('Post/title')?></td>

      </tr>

      <tr>       

         <td>Body:<td>

         <td><?php echo $html->textarea('Post/title')?></td>

     </tr>

     <tr>

        <td colspan="2">

        <?php echo $html->hidden('Post/user_id', array('value'=>$this->controller->Session->read('User.id')))?>

        <?php echo $html->hidden('Post/status' , array('value'=>'0'))?>

        <?php echo $html->submit('Save Post')?>

        </td>

       </tr>

 </table>

当前用的表单仅创建Post记录。让我们增加一些代码使你将给定的Post绑定到一个或多个Tag

/app/views/posts/add.thtml (增加的Tag 关联代码)

<h1>Write a New Post</h1>

    <table>

        <tr>

            <td>Title:</td>

            <td><?php echo $html->input('Post/title')?></td>

        </tr>

        <tr>

            <td>Body:</td>

            <td><?php echo $html->textarea('Post/title')?></td>

        </tr>

       

        <tr>

            <td>Related Tags:</td>

            <td><?php echo $html->selectTag('Tag/Tag', $tags, null, array('multiple' => 'multiple')) ?>

            </td>

        </tr>

       

        <tr>

            <td colspan="2">

                <?php echo $html->hidden('Post/user_id', array('value'=>$this->controller->Session->read('User.id')))?>

                <?php echo $html->hidden('Post/status' , array('value'=>'0'))?>

                <?php echo $html->submit('Save Post')?>

            </td>

        </tr>

</table>

对于一个调用,为了让controller$this->Post->save()保存新Post和它关联的Tag之间的联系,字段名必须是"Tag/Tag"形式(已显示的名字属性看起来是'data[ModelName][ModelName][]'),已提交的数据必须为一个ID,或者关联记录的ID数组。因为我们在这里正使用多个选择,所以提交的Tag/Tag数据应该为ID数组。

这里的$tags变量仅是一个数组,里面的键是可能的TagID,值为多选元素里的显示的Tag名。

使用bindModel() unbindModel()在空中改变关联

当创建应用程序时,你偶尔可能希望为一些特别的情况改变model关联信息。如果model文件里的关联设置给你太多(或不够)信息,你可以使用2model函数为下一次的查找绑定和解除绑定关联。

让我们建立一些model,以至我们可以看看bindModel()unbindModel()是如何工作的。我们将从2model开始:

leader.php follower.php

<?php

 

class Leader extends AppModel

{

    var $name = 'Leader';

 

    var $hasMany = array(

        'Follower' => array(

            'className' => 'Follower',

            'order'     => 'Follower.rank'

        )

    );

}

 

?>

 

<?php

 

class Follower extends AppModel

{

    var $name = 'Follower';

}

 

?>

现在,在LeadersController中,我们可以使用Leader Model中的find()方法准备一个Leader以及它的关联Follower。从上面可以看到,Leader model的关联数组定义了一个"Leader hasMany Followers"的关系。出于描述的目的,让我们使用unbindModel()去除那个关联中间controller

leaders_controller.php (部分)

function someAction()

{

    //This fetches Leaders, and their associated Followers

    $this->Leader->findAll();

 

    //Let's remove the hasMany...

    $this->Leader->unbindModel(array('hasMany' => array('Follower')));

   

    //Now a using a find function will return Leaders, with no Followers

    $this->Leader->findAll();

 

    //NOTE: unbindModel only affects the very next find function.

    //An additional find call will use the configured association information.

 

    //We've already used findAll() after unbindModel(), so this will fetch

    //Leaders with associated Followers once again...

    $this->Leader->findAll();

}

unbindModel()函数对其他关联类似:仅需要改变关联类型名以及model 类名。unbindModel()的基本使用方法如下:

普通的 unbindModel() 实例

$this->Model->unbindModel(array('associationType' => array('associatedModelClassName')));

既然我们成功移除了空中的一个关联,还是让我们增加一个吧。还没有PrincipleLeader需要一些关联的Principle。我们的Principle model的文件还不太丰富,除了$name变量语句。让我们把这些Principle关联到Leader吧(而且为了跟踪查找函数哟):

leaders_controller.php (部分)

funciton anotherAction()

{

    //There is no Leader hasMany Principles in the leader.php model file, so

    //a find here, only fetches Leaders.

    $this->Leader->findAll();

 

    //Let's use bindModel() to add a new association to the Principle model:

    $this->Leader->bindModel(

        array('hasMany' => array(

                'Principle' => array(

                    'className' => 'Principle'

                )

            )

        )

    );

 

    //Now that we're associated correctly, we can use a single find function

    //to fetch Leaders with their associated principles:

    $this->Leader->findAll();

}

bindModel()函数可以便于创建一个新的关联,但是如果你想改变给定关联的排列顺序或则其他参数,它也会有用。

你已经有了它。bindModel的基本使用是封装一个普通的关联数组,在此数组里,它的键会在你试图创建的关联后命名:

普通 bindModel() 实例

$this->Model->bindModel(

        array('associationName' => array(

                'associatedModelClassName' => array(

                    // normal association keys go here...

                )

            )

        )

    );

请注意,你的表需要正确的键(或者正确配置的关联数组)来绑定model



[1] 需要说明的是,并不是所有的数据库都支持事务,所以使用此功能时,数据库必须支持事务。

Last Updated:2006年12月1日

posted @ 2006-11-16 11:23  张太国  阅读(9258)  评论(2编辑  收藏  举报