Yii DAO はデータベース関連のほぼすべてのタスクを処理できますが、通常の CRUD (作成、読み取り、更新、削除) 操作を実行するいくつかの SQL ステートメントの作成に時間の 90% を費やすことになるでしょう。 また、コードに SQL ステートメントが混在すると、メンテナンスが困難になります。これらの問題を解決するには、Active Record を使用できます。
アクティブ レコード (AR) は、人気のあるオブジェクト リレーショナル マッピング (ORM) テクノロジーです。 各 AR クラスはデータ テーブル (またはビュー) を表し、AR インスタンスはクラスの属性として AR クラスに反映されます。 一般的な CRUD 操作は AR メソッドとして実装されます。したがって、よりオブジェクト指向的な方法でデータにアクセスできます。 たとえば、次のコードを使用して、tbl_post テーブルに新しい行を挿入できます。
$post=new Post; $post->title='sample post'; $post->content='post body content'; $post->save();
以下では、AR を設定し、それを介して CRUD 操作を実行する方法を説明します。次のセクションでは、AR を使用してデータベースの関係を処理する方法を説明します。 わかりやすくするために、このセクションでは例として次のデータ テーブルを使用します。 MySQL データベースを使用している場合は、以下の SQL の AUTOINCREMENT を AUTO_INCREMENT に置き換える必要があることに注意してください。
CREATE TABLE tbl_post ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, title VARCHAR(128) NOT NULL, content TEXT NOT NULL, create_time INTEGER NOT NULL );
注: AR は、データベース関連のすべてのタスクを解決することを目的としたものではありません。その最適なアプリケーションは、データ テーブルを PHP 構造にモデル化し、複雑な SQL ステートメントを含まないクエリを実行することです。 複雑なクエリシナリオの場合は、Yii DAO を使用する必要があります。
AR はデータベース関連の操作を実行するためにデータベース接続に依存します。デフォルトでは、db アプリケーション コンポーネントが必要な CDbConnection データベース接続インスタンスを提供すると想定します。次のアプリケーション構成は例を示しています:
return array( 'components'=>array( 'db'=>array( 'class'=>'system.db.CDbConnection', 'connectionString'=>'sqlite:path/to/dbfile', // 开启表结构缓存(schema caching)提高性能 // 'schemaCachingDuration'=>3600, ), ), );
ヒント: Active Record はテーブルのメタデータに依存して列情報を決定するため、メタデータの読み取りと解析には時間がかかります。 データベースのテーブル構造がほとんど変更されない場合は、CDbConnection::schemaCachingDuration プロパティをゼロより大きい値に構成して、テーブル構造のキャッシュを有効にする必要があります。
AR のサポートは DBMS によって制限されています。現在、次の DBMS のみがサポートされています:
MySQL 4.1 或更高版本 PostgreSQL 7.3 或更高版本 SQLite 2 和 3 Microsoft SQL Server 2000 或更高版本 Oracle
注: Microsoft SQL Server はバージョン 1.0.4 からサポートされています。
db 以外のアプリケーション コンポーネントを使用する場合、または AR を使用して複数のデータベースを処理する場合は、CActiveRecord::getDbConnection() をオーバーライドする必要があります。 CActiveRecord クラスは、すべての AR クラスの基本クラスです。
ヒント: AR を通じて複数のデータベースを使用するには 2 つの方法があります。データベース構造が異なる場合は、異なる getDbConnection() を実装する異なる AR 基本クラスを作成できます。それ以外の場合は、静的変数 CActiveRecord::db を動的に変更することをお勧めします。
データ テーブルにアクセスするには、まず CActiveRecord を統合して AR クラスを定義する必要があります。 各 AR クラスは個別のデータ テーブルを表し、AR インスタンスはそのテーブル内の行を表します。 次の例は、tbl_post テーブルを表す AR クラスの最も単純なコードを示しています:
class Post extends CActiveRecord { public static function model($className=__CLASS__) { return parent::model($className); } public function tableName() { return 'tbl_post'; } }
ヒント: AR クラスは複数の場所で参照されることが多いため、AR クラスを 1 つずつインポートする代わりに、AR クラスを含むディレクトリ全体をインポートできます。 たとえば、すべての AR クラス ファイルが protected/models ディレクトリにある場合、アプリケーションを次のように構成できます:
return array( 'import'=>array( 'application.models.*', ), );
デフォルトでは、AR クラスの名前はデータ テーブルの名前と同じです。異なる場合は、tableName() メソッドをオーバーライドします。 model() メソッドは、AR クラスごとにそのように宣言されます (後で説明します)。
情報: バージョン 1.1.0 で導入されたテーブル プレフィックス機能を使用するには、AR クラスの tableName() メソッドを次のようにオーバーライドできます
public function tableName() { return '{{post}}'; } (然后在congfig下的配置文件中指定db组件的prefix属性)
つまり、二重中括弧で囲まれたプレフィックスなしのテーブル名を返します。完全なテーブル名ではなく。
データテーブル行の列の値は、対応する AR インスタンスのプロパティとしてアクセスできます。たとえば、次のコードはタイトル列 (プロパティ) を設定します。
$post=new Post; $post->title='a sample post';
Post クラスでプロパティ title を明示的に定義することはありませんが、上記のコードを通じてアクセスできます。 これは、title が tbl_post テーブルの列であり、CActiveRecord によって PHP の __get() マジック メソッドを介してアクセス可能なプロパティになっているためです。 存在しない列に同じ方法でアクセスしようとすると、例外がスローされます。
情報: このガイドでは、テーブル名と列名の両方に小文字を使用します。 これは、DBMS が異なれば大文字と小文字の処理が異なるためです。 たとえば、PostgreSQL はデフォルトでは列名の大文字と小文字を区別しないため、クエリ条件では大文字と小文字が混在する列名を引用符で囲む必要があります。 小文字を使用すると、この問題を回避できます。
AR は、テーブル内の明確に定義された主キーに依存します。 テーブルに主キーがない場合は、次のように対応する AR クラスの PrimaryKey() メソッドをオーバーライドして、どの列が主キーとして機能するかを指定する必要があります。
public function primaryKey() { return 'id'; // 对于复合主键,要返回一个类似如下的数组 // return array('pk1', 'pk2'); }
データテーブルに新しい行を挿入するには、対応する AR クラスのインスタンスを作成し、テーブルの列に関連するプロパティを設定してから、save() メソッドを呼び出す必要があります。挿入を完了します:
$post=new Post; $post->title='sample post'; $post->content='content for the sample post'; $post->create_time=time(); $post->save();
如果表的主键是自增的,在插入完成后,AR 实例将包含一个更新的主键。在上面的例子中, id 属性将反映出新插入帖子的主键值,即使我们从未显式地改变它。
如果一个列在表结构中使用了静态默认值(例如一个字符串,一个数字)定义。则 AR 实例中相应的属性将在此实例创建时自动含有此默认值。改变此默认值的一个方式就是在 AR 类中显示定义此属性:
class Post extends CActiveRecord { public $title='please enter a title'; ...... } $post=new Post; echo $post->title; // 这儿将显示: please enter a title
记录在保存(插入或更新)到数据库之前,其属性可以赋值为 CDbExpression 类型。 例如,为保存一个由 MySQL 的 NOW() 函数返回的时间戳,我们可以使用如下代码:
$post=new Post; $post->create_time=new CDbExpression('NOW()'); // $post->create_time='NOW()'; 不会起作用,因为 // 'NOW()' 将会被作为一个字符串处理。 $post->save();
提示: 由于 AR 允许我们无需写一大堆 SQL 语句就能执行数据库操作, 我们经常会想知道 AR 在背后到底执行了什么 SQL 语句。这可以通过开启 Yii 的 日志功能 实现。例如,我们在应用配置中开启了 CWebLogRoute ,我们将会在每个网页的最后看到执行过的 SQL 语句。
我们可以在应用配置中设置 CDbConnection::enableParamLogging 为 true ,这样绑定在 SQL 语句中的参数值也会被记录。
要读取数据表中的数据,我们可以通过如下方式调用 find 系列方法中的一种:
// 查找满足指定条件的结果中的第一行 $post=Post::model()->find($condition,$params); // 查找具有指定主键值的那一行 $post=Post::model()->findByPk($postID,$condition,$params); // 查找具有指定属性值的行 $post=Post::model()->findByAttributes($attributes,$condition,$params); // 通过指定的 SQL 语句查找结果中的第一行 $post=Post::model()->findBySql($sql,$params);
如上所示,我们通过 Post::model() 调用 find 方法。 请记住,静态方法 model() 是每个 AR 类所必须的。 此方法返回在对象上下文中的一个用于访问类级别方法(类似于静态类方法的东西)的 AR 实例。
如果 find 方法找到了一个满足查询条件的行,它将返回一个 Post 实例,实例的属性含有数据表行中相应列的值。 然后我们就可以像读取普通对象的属性那样读取载入的值,例如 echo $post->title;。
如果使用给定的查询条件在数据库中没有找到任何东西, find 方法将返回 null 。
调用 find 时,我们使用 $condition 和 $params 指定查询条件。此处 $condition 可以是 SQL 语句中的 WHERE 字符串,$params 则是一个参数数组,其中的值应绑定到 $condation 中的占位符。例如:
// 查找 postID=10 的那一行 $post=Post::model()->find('postID=:postID', array(':postID'=>10));
注意: 在上面的例子中,我们可能需要在特定的 DBMS 中将 postID 列的引用进行转义。 例如,如果我们使用 PostgreSQL,我们必须将此表达式写为 "postID"=:postID,因为 PostgreSQL 在默认情况下对列名大小写不敏感。
我们也可以使用 $condition 指定更复杂的查询条件。 不使用字符串,我们可以让 $condition 成为一个 CDbCriteria 的实例,它允许我们指定不限于 WHERE 的条件。 例如:
$criteria=new CDbCriteria; $criteria->select='title'; // 只选择 'title' 列 $criteria->condition='postID=:postID'; $criteria->params=array(':postID'=>10); $post=Post::model()->find($criteria); // $params 不需要了
注意,当使用 CDbCriteria 作为查询条件时,$params 参数不再需要了,因为它可以在 CDbCriteria 中指定,就像上面那样。
一种替代 CDbCriteria 的方法是给 find 方法传递一个数组。 数组的键和值各自对应标准(criterion)的属性名和值,上面的例子可以重写为如下:
$post=Post::model()->find(array( 'select'=>'title', 'condition'=>'postID=:postID', 'params'=>array(':postID'=>10), ));
信息: 当一个查询条件是关于按指定的值匹配几个列时,我们可以使用 findByAttributes()。我们使 $attributes 参数是一个以列名做索引的值的数组。在一些框架中,此任务可以通过调用类似 findByNameAndTitle 的方法实现。虽然此方法看起来很诱人, 但它常常引起混淆,冲突和比如列名大小写敏感的问题。
当有多行数据匹配指定的查询条件时,我们可以通过下面的 findAll 方法将他们全部带回。 每个都有其各自的 find 方法,就像我们已经讲过的那样。
// 查找满足指定条件的所有行 $posts=Post::model()->findAll($condition,$params); // 查找带有指定主键的所有行 $posts=Post::model()->findAllByPk($postIDs,$condition,$params); // 查找带有指定属性值的所有行 $posts=Post::model()->findAllByAttributes($attributes,$condition,$params); // 通过指定的SQL语句查找所有行 $posts=Post::model()->findAllBySql($sql,$params);
如果没有任何东西符合查询条件,findAll 将返回一个空数组。这跟 find 不同,find 会在没有找到什么东西时返回 null。
除了上面讲述的 find 和 findAll 方法,为了方便,(Yii)还提供了如下方法:
// 获取满足指定条件的行数 $n=Post::model()->count($condition,$params); // 通过指定的 SQL 获取结果行数 $n=Post::model()->countBySql($sql,$params); // 检查是否至少有一行复合指定的条件 $exists=Post::model()->exists($condition,$params);
在 AR 实例填充了列的值之后,我们可以改变它们并把它们存回数据表。
$post=Post::model()->findByPk(10); $post->title='new post title'; $post->save(); // 将更改保存到数据库
正如我们可以看到的,我们使用同样的 save() 方法执行插入和更新操作。 如果一个 AR 实例是使用 new 操作符创建的,调用 save() 将会向数据表中插入一行新数据; 如果 AR 实例是某个 find 或 findAll 方法的结果,调用 save() 将更新表中现有的行。 实际上,我们是使用 CActiveRecord::isNewRecord 说明一个 AR 实例是不是新的。
直接更新数据表中的一行或多行而不首先载入也是可行的。 AR 提供了如下方便的类级别方法实现此目的:
// 更新符合指定条件的行 Post::model()->updateAll($attributes,$condition,$params); // 更新符合指定条件和主键的行 Post::model()->updateByPk($pk,$attributes,$condition,$params); // 更新满足指定条件的行的计数列 Post::model()->updateCounters($counters,$condition,$params);
在上面的代码中, $attributes 是一个含有以 列名作索引的列值的数组; $counters 是一个由列名索引的可增加的值的数组;$condition 和 $params 在前面的段落中已有描述。
如果一个 AR 实例被一行数据填充,我们也可以删除此行数据。
$post=Post::model()->findByPk(10); // 假设有一个帖子,其 ID 为 10 $post->delete(); // 从数据表中删除此行
注意,删除之后, AR 实例仍然不变,但数据表中相应的行已经没了。
使用下面的类级别代码,可以无需首先加载行就可以删除它。
// 删除符合指定条件的行 Post::model()->deleteAll($condition,$params); // 删除符合指定条件和主键的行 Post::model()->deleteByPk($pk,$condition,$params);
当插入或更新一行时,我们常常需要检查列的值是否符合相应的规则。 如果列的值是由最终用户提供的,这一点就更加重要。总体来说,我们永远不能相信任何来自客户端的数据。
当调用 save() 时, AR 会自动执行数据验证。 验证是基于在 AR 类的 rules() 方法中指定的规则进行的。下面是保存记录时所需的典型的工作流。
if($post->save()) { // 数据有效且成功插入/更新 } else { // 数据无效,调用 getErrors() 提取错误信息 }
当要插入或更新的数据由最终用户在一个 HTML 表单中提交时,我们需要将其赋给相应的 AR 属性。 我们可以通过类似如下的方式实现:
$post->title=$_POST['title']; $post->content=$_POST['content']; $post->save();
如果有很多列,我们可以看到一个用于这种复制的很长的列表。 这可以通过使用如下所示的 attributes 属性简化操作。 更多信息可以在 安全的特性赋值 一节和 创建动作 一节找到。
// 假设 $_POST['Post'] 是一个以列名索引列值为值的数组 $post->attributes=$_POST['Post']; $post->save();
类似于表记录,AR 实例由其主键值来识别。 因此,要对比两个 AR 实例,假设它们属于相同的 AR 类, 我们只需要对比它们的主键值。 然而,一个更简单的方式是调用 CActiveRecord::equals()。
信息: 不同于 AR 在其他框架的执行, Yii 在其 AR 中支持多个主键. 一个复合主键由两个或更多字段构成。相应地, 主键值在 Yii 中表现为一个数组. primaryKey 属性给出了一个 AR 实例的主键值。
CActiveRecord 提供了几个占位符方法,它们可以在子类中被覆盖以自定义其工作流。
beforeValidate 和 afterValidate:这两个将在验证 AR 实例之前和之后被调用。 beforeSave 和 afterSave: 这两个将在保存 AR 实例之前和之后被调用。 beforeDelete 和 afterDelete: 这两个将在一个 AR 实例被删除之前和之后被调用。 afterConstruct: 这个将在每个使用 new 操作符创建 AR 实例后被调用。 beforeFind: 这个将在一个 AR 查找器被用于执行查询(例如 find(), findAll())之前被调用。 1.0.9 版本开始可用。 afterFind: 这个将在每个 AR 实例作为一个查询结果创建时被调用。
每个 AR 实例都含有一个属性名叫 dbConnection ,是一个 CDbConnection 的实例,这样我们可以在需要时配合 AR 使用由 Yii DAO 提供的 事务 功能:
$model=Post::model(); $transaction=$model->dbConnection->beginTransaction(); try { // 查找和保存是可能由另一个请求干预的两个步骤 // 这样我们使用一个事务以确保其一致性和完整性 $post=$model->findByPk(10); $post->title='new post title'; $post->save(); $transaction->commit(); }catch(Exception $e){ $transaction->rollBack(); }
Note: 对命名范围的支持从版本 1.0.5 开始。 命名范围的最初想法来源于 Ruby on Rails.
命名范围(named scope) 表示一个 命名的(named) 查询规则,它可以和其他命名范围联合使用并应用于 Active Record 查询。
命名范围主要是在 CActiveRecord::scopes() 方法中以名字-规则对的方式声明。 如下代码在 Post 模型类中声明了两个命名范围, published 和 recently。
class Post extends CActiveRecord { ...... public function scopes() { return array( 'published'=>array( 'condition'=>'status=1', ), 'recently'=>array( 'order'=>'create_time DESC', 'limit'=>5, ), ); } }
每个命名范围声明为一个可用于初始化 CDbCriteria 实例的数组。 例如,recently 命名范围指定 order 属性为 create_time DESC , limit 属性为 5。他们翻译为查询规则后就会返回最近的5篇帖子。
命名范围多用作 find 方法调用的修改器。 几个命名范围可以链到一起形成一个更有约束性的查询结果集。例如, 要找到最近发布的帖子, 我们可以使用如下代码:
$posts=Post::model()->published()->recently()->findAll();
总体来说,命名范围必须出现在一个 find 方法调用的左边。 它们中的每一个都提供一个查询规则,并联合到其他规则, 包括传递给 find 方法调用的那一个。 最终结果就像给一个查询添加了一系列过滤器。
注意: 命名范围只能用于类级别方法。也就是说,此方法必须使用 ClassName::model() 调用。
命名范围可以参数化。例如, 我们想自定义 recently 命名范围中指定的帖子数量,要实现此目的,不是在CActiveRecord::scopes 方法中声明命名范围, 而是需要定义一个名字和此命名范围的名字相同的方法:
public function recently($limit=5) { $this->getDbCriteria()->mergeWith(array( 'order'=>'create_time DESC', 'limit'=>$limit, )); return $this; }
然后,我们就可以使用如下语句获取3条最近发布的帖子。
$posts=Post::model()->published()->recently(3)->findAll();
上面的代码中,如果我们没有提供参数 3,我们将默认获取 5 条最近发布的帖子。
模型类可以有一个默认范围,它将应用于所有 (包括相关的那些) 关于此模型的查询。例如,一个支持多种语言的网站可能只想显示当前用户所指定的语言的内容。 因为可能会有很多关于此网站内容的查询, 我们可以定义一个默认范围以解决此问题。 为实现此目的,我们覆盖 CActiveRecord::defaultScope 方法如下:
class Content extends CActiveRecord { public function defaultScope() { return array( 'condition'=>"language='".Yii::app()->language."'", ); } }
现在,如果下面的方法被调用,将会自动使用上面定义的查询规则:
$contents=Content::model()->findAll();
注意,默认的命名范围只会应用于 SELECT 查询。INSERT, UPDATE 和 DELETE 查询将被忽略。
<p> Author: qiang.xue Translators: riverlet, dongbeta ID: $Id$ </p>
以上就是Yii框架官方指南系列25——使用数据库:Active Record的内容,更多相关内容请关注PHP中文网(m.sbmmt.com)!