Yii DAO는 거의 모든 데이터베이스 관련 작업을 처리할 수 있지만 일반적인 CRUD를 수행하기 위해 무언가를 작성하는 데 90%의 시간을 소비할 가능성이 높습니다. (생성, 읽기, 업데이트 및 삭제를 위한 SQL 문) 작업입니다. 그리고 코드가 SQL 문과 혼합되면 유지 관리가 어려워집니다. 이러한 문제를 해결하기 위해 Active Record를 사용할 수 있습니다.
AR(Active Record)은 인기 있는 ORM(객체 관계형 매핑) 기술입니다. 각 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 속성을 0보다 큰 값으로 구성하여 테이블 구조 캐싱을 활성화해야 합니다.
AR 지원은 DBMS에 의해 제한됩니다. 현재는 다음 DBMS만 지원됩니다.
MySQL 4.1 或更高版本 PostgreSQL 7.3 或更高版本 SQLite 2 和 3 Microsoft SQL Server 2000 或更高版本 Oracle
참고: Microsoft SQL Server는 버전 1.0.4부터 지원됩니다. Oracle은 버전 1.0부터 지원됩니다. 5 .
db 이외의 애플리케이션 구성 요소를 사용하거나 AR을 사용하여 여러 데이터베이스를 처리하려는 경우 CActiveRecord::getDbConnection()을 재정의해야 합니다. CActiveRecord 클래스는 모든 AR 클래스의 기본 클래스입니다.
팁: AR로 여러 데이터베이스를 사용하는 방법에는 두 가지가 있습니다. 데이터베이스 구조가 다른 경우 다양한 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 클래스를 하나의 디렉토리로 가져오는 대신 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이 tbl_post 테이블의 열이고 CActiveRecord가 PHP의 __get() 매직 메소드를 통해 액세스 가능한 속성으로 만들기 때문입니다. 존재하지 않는 열에 동일한 방식으로 액세스하려고 하면 예외가 발생합니다.
정보: 이 가이드에서는 테이블 이름과 열 이름 모두에 소문자를 사용합니다. 이는 서로 다른 DBMS가 대소문자를 다르게 처리하기 때문입니다. 예를 들어 PostgreSQL은 기본적으로 열 이름의 대소문자를 구분하지 않으며 쿼리 조건에서 대소문자가 혼합된 열 이름을 따옴표로 묶어야 합니다. 소문자를 사용하면 이 문제를 방지하는 데 도움이 됩니다.
AR은 테이블에 잘 정의된 기본 키를 사용합니다. 테이블에 기본 키가 없는 경우 해당 AR 클래스에서 기본 키() 메서드를 다음과 같이 재정의하여 기본 키 역할을 하는 열을 지정해야 합니다.
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中文网(www.php.cn)!