Zend Framework的DB处理

阅读更多
http://bbs.php.cn/thread-16360-1-1.html 除了MVC外,ZF中另外一个使用的比较充分的模块就是DB类库了。ZF的DB类库对数据库层进行了一个比较好的封装,将基本的数据库操作都转变为面向对象的操作。这里这些东西不再细讲,说另外一件事情。 当时看ZF的中文版手册,发现其中中英文混杂,并不是完整的中文版。正好赶上PHPChina总部负责《PHPer》杂志的刘昊同志约稿,一时想不到好题目,就花了一晚上时间将ZF手册中的9.8章节 Zend_Db_Table Relati****hips翻译出来上交了。感觉随意拼凑的东西,对不住刘昊老弟的期盼,下次一定好好写点东西。该文已经发表在《PHPer》第七期上,顺带做个广告,欢迎大家到http://www.phpchina.com/phper下载。 翻译完后,在网上查到ZF的中文版是由厦门的Haohappy负责率领一个公益的团队进行汉化工作的,就又将译文寄给他,希望能有所帮助。从Haohappy的回信里更稍微了解了一些汉化方面的规范性的东西。由于申请汉化的SVN帐号过程稍显复杂,一时顾不上就给忘了。抽空还是上传上去,以后希望大家有空也都多多参与这样的公益事业~ 另外一个值得一提的事情是,那晚我是10点多分别给刘昊和Haohappy发的邮件,本想第二天能收到回信就不赖,没料到0点刚过回信就都到了,呵呵,一伙夜猫子。 闲言少叙。将译文在这儿也帖出来以备大家批评指证。 使用Zend_Db类库进行多表关系处理 1. 介绍 关系型数据库中表与表之间总存在着各种各样的关系。在数据库中通过参照完整性约束可以设定一个表中的某个字段与别的表中的一个或多个字段建立联系。 Zend_Db_Table_Row类中内置了一些方法能够查询到其他表中的相关信息。 2. 定义关系 为了讲解清楚表间关系,使用下图所示的bug跟踪数据库例子。 这四张表主要用来保存软件开发项目过程中bug的跟踪信息。Accounts里存的是bug相关的人员信息;Products保存发生bug的产品;Bugs表保存bug的所有信息,包括当前bug的状态、谁发现的bug、谁被指定修改bug及由谁来检查;BugsProducts表保存bug和产品之间的关系,这是一个多对多的关系,因为一个bug可能涉及到多个产品,一个产品当然也可能有多个bug。 下面就是基于上述四张表而定义出的PHP类: class Accounts extends Zend_Db_Table_Abstract { protected $_name = 'accounts'; protected $_dependentTables = array('Bugs'); } class Products extends Zend_Db_Table_Abstract { protected $_name = 'products'; protected $_dependentTables = array('BugsProducts'); } class Bugs extends Zend_Db_Table_Abstract { protected $_name = 'bugs'; protected $_dependentTables = array('BugsProducts'); protected $_referenceMap = array( 'Reporter' => array( 'columns' => 'reported_by', 'refTableClass' => 'Accounts', 'refColumns' => 'account_name' ), 'Engineer' => array( 'columns' => 'assigned_to', 'refTableClass' => 'Accounts', 'refColumns' => 'account_name' ), 'Verifier' => array( 'columns' => array('verified_by'), 'refTableClass' => 'Accounts', 'refColumns' => array('account_name') ) ); } class BugsProducts extends Zend_Db_Table_Abstract { protected $_name = 'bugs_products'; protected $_referenceMap = array( 'Bug' => array( 'columns' => array('bug_id'), 'refTableClass' => 'Bugs', 'refColumns' => array('bug_id') ), 'Product' => array( 'columns' => array('product_id'), 'refTableClass' => 'Products', 'refColumns' => array('product_id') ) ); } 如果想使用Zend_Db_Table来模拟实现数据库的级联更新、删除操作,就在类中为父表声明一个$_dependentTables数组,在里面保存每个依赖于该表的类名即可。注意是表对应的类名,而不是实际的表名。 注意:如果要使用关系数据库中的参照完整性约束来实现级联操作,就不用声明$_dependentTables属性了。 为每个依赖表声明一个$_referenceMap数组,这是一个描述引用“规则”的关联数组。每个引用规则都需要说明在关系中哪个表是父表,也列出了依赖表中的哪些字段引用到了父表的哪些字段。 规则的key是一个用于在$_referenceMap 数组中确定索引的字符串,该键可以标识每个引用关系,所以可以给键起一个有意义的名字,最好起成能够成为PHP的方法名称的一部分的名字,原因后面会讲到。 在上面的PHP代码例程中,Bugs表类定义的规则的键分别是:'Reporter', 'Engineer','Verifier'和 'Product'。 规则数组中每个元素的值也是一个关联数组,该数组中的元素主要有: columns => 依赖表中外键字段名称字符串或者字符串数组。通常情况下就一个字段,但也有些表有多字段的键。 refTableClass => 父表的类名。要使用类名,而不是表的实际名字。通常依赖表只有一个引用指向父表,但也有些表对同一张父表有多个引用。在示例数据库中,bugs表到products表有一个引用,而到accounts表却有三个引用。将每个引用分别放在$_referenceMap数组的不同的元素中。 refColumns =>父表主键字段的名称字符串或字符串数组。一般情况下都是单个列,但有些表也可能有多个字段的联合主键。如果引用用到了多列联合主键,’columns’元素中列的顺序一定要和’refColumns’元素的列顺序一致。该元素是可选的,如果不指定refColumns的话,默认就使用父表的主键列。 onDelete => 设置父表中某条记录被删除时,执行什么样的操作。参考第6节得到更多的信息。 onUpdate => 设置父表中某条记录被更新时,执行什么样的操作。参考第6节得到更多的信息。 3. 获取一个依赖结果集 如果已经有一个父表的查询结果Row对象,就可以得到依赖表中与它相关的记录了,方法如下: $row->findDependentRowset($table, [$rule]); 该方法返回一个Zend_Db_Table_Rowset_Abstract对象,包含有指定依赖表中与该$row对象关联的所有记录的集合。 第一个参数$table是依赖表的类名字符串,也可以是依赖表的一个对象。 例 1. 获取一个依赖结果集 下例从Accounts表得到一个Row对象,然后找到由该用户提交的所有bugs对象: $accountsTable = new Accounts(); $accountsRowset = $accountsTable->find(1234); $user1234 = $accountsRowset->current(); $bugsReportedByUser = $user1234->findDependentRowset('Bugs'); 第二个参数$rule是可选的,它是依赖表中$_referenceMap数组中的一个规则键。如果不指定规则,就使用指向父表的第一个规则。如果想使用其他规则,就必须指定该参数。 上例中没有指定规则键,所以默认使用的就是符合该父表的第一个规则'Reporter'。 例 2. 指定规则获取结果集 下例将从Accounts表得到一个用户对象,然后得到该用户修改的所有的Bugs对象。相关的规则键就是'Engineer'。 $accountsTable = new Accounts(); $accountsRowset = $accountsTable->find(1234); $user1234 = $accountsRowset->current(); $bugsAssignedToUser = $user1234->findDependentRowset('Bugs', 'Engineer'); 还可以通过一种特殊的机制,所谓“魔术方法”来完成同样的操作。如果按照下述模式调用方法,也可以让Zend_Db_Table_Row_Abstract完成findDependentRowset(' ', '')的调用。 $row->find () $row->find By() 上述模式中, 和 分别是相关的依赖表的表名,和依赖规则键。 注意:有些应用程序框架,比如Ruby on Rails,使用一种“变形”机制,允许通过标识符的拼写动态改变依赖。Zend_Db_Table_Row比较简洁,不提供变形机制。上述方法调用中,表名和规则键名必须严格符合类名、键名的拼写。 例 3. 使用魔术方法获取结果集 下例使用了魔术方法的调用,实现的功能与上例相同。 $accountsTable = new Accounts(); $accountsRowset = $accountsTable->find(1234); $user1234 = $accountsRowset->current(); // Use the default reference rule $bugsReportedBy = $user1234->findBugs(); // Specify the reference rule $bugsAssignedTo = $user1234->findBugsByEngineer(); 4. 获取一个父记录 如果已经有了依赖表查询的结果行对象,也可以得到它所依赖的父表的相关对象。方法如下: $row->findParentRow($table, [$rule]); 一般一条依赖表记录只指向一条父表记录,所以该方法只返回一个Row对象,而不是RowSet结果集对象。 第一个参数$table父表的类名字符串,也可以使用父表的一个对象。 例 4. 获取父记录 下例从Bugs表中得到一条记录(比如状态为‘NEW’的一个bug),然后从Accounts表中找到报告该bug的用户。 $bugsTable = new Bugs(); $bugsRowset = $bugsTable->fetchAll('bug_status = ?', 'NEW'); $bug1 = $bugsRowset->current(); $reporter = $bug1->findParentRow('Accounts'); 第二个参数$rule是可选的,它是依赖表中$_referenceMap数组定义的一个规则的键名字符串。如果不指定,就使用指向父表的第一个键。如果不想用第一个键,就必须指定该参数。 上例没有指定规则,所以默认使用的是第一个规则,即'Reporter'。 例 5. 指定规则获取父记录 下例从Bugs表中得到一条记录,然后找到Accounts表中被指定来修复该bug的工程师。规则键字符串是'Engineer'。 $bugsTable = new Bugs(); $bugsRowset = $bugsTable->fetchAll('bug_status = ?', 'NEW'); $bug1 = $bugsRowset->current(); $engineer = $bug1->findParentRow('Accounts', 'Engineer'); 同样,也可以通过调用“魔术方法”来实现上述功能。下述模式也可以使Zend_Db_Table_Row_Abstract调用findParentRow(' ', '')方法: $row->findParent () $row->findParent By() 上述模式中, 和 分别对应父表类名和依赖表中相关规则的键名。 注意:魔术方法中的表名和规则名必须严格与类名、规则键名拼写一致。 例 6. 使用魔术方法获取父记录 下例使用魔术方法,完成与上例相同的功能: $bugsTable = new Bugs(); $bugsRowset = $bugsTable->fetchAll('bug_status = ?', 'NEW'); $bug1 = $bugsRowset->current(); // Use the default reference rule $reporter = $bug1->findParentAccounts(); // Specify the reference rule $engineer = $bug1->findParentAccountsByEngineer(); 5. 通过多对多的关系获取结果集 如果已经有了一个Row对象,它所属的表存在一个多对多的关系(后例中称为“源表”),也可以通过一个交叉表得到与它相关的其他表中的记录(后例中称为“目标表”)。使用下述方法: $row->findManyToManyRowset($table, $intersectionTable, [$rule1, [$rule2]]); 该方法从$table中返回一个满足多对多关系的结果集对象。先从源表中的行记录对象$row找到交叉表中的相关记录,这些记录与目标表相关联。 第一个参数$table是多对多关系中的目标表的类名,也可以是该表的一个对象。 第二个参数$intersectionTable是可以表述多对多关系的那个交叉表的类名。也可以是该表的一个对象。 例 7. 用多对多方法得到结果集 下例从源表Bugs中得到一条记录,然后找到与该bug相关的Products表中的所有记录。 $bugsTable = new Bugs(); $bugsRowset = $bugsTable->find(1234); $bug1234 = $bugsRowset->current(); $productsRowset = $bug1234->findManyToManyRowset('Products', 'BugsProducts'); 第三个和第四个参数$rule1、$rule2是可选的,它们是交叉表中$_referenceMap数组定义的规则键名字符串。 $rule1是从交叉表到源表的关系规则名。本例中,它指从BugsProducts到Bugs的关系。$rule2是从交叉表到目标表的关系规则名。本例中,它是从Bugs到Products的关系。 与找父记录、依赖记录一样,如果不指定规则,该方法就自动调用$_referenceMap数组中符合关系的第一个规则。如果不想使用第一个规则,就必须指定它们。 上例中没有指定规则,所以默认使用的就是第一个匹配的规则,即$rule1 是'Reporter',$rule2是'Product'。 例8. 指定规则用多对多方法得到结果集 下例从源表Bugs得到一条记录,再找到与该bug相关的所有的Products记录。 $bugsTable = new Bugs(); $bugsRowset = $bugsTable->find(1234); $bug1234 = $bugsRowset->current(); $productsRowset = $bug1234->findManyToManyRowset('Products', 'BugsProducts', 'Bug'); 同样,也可以使用“魔术方法”完成相同的功能。下述模式也可以使得Zend_Db_Table_Row_Abstract调用findManyToManyRowset(' ', '', '', '')方法: $row->find Via() $row->find ViaBy() $row->find ViaByAnd() 上述模式中, 和分别是目标表和交叉表的类名。和分别是交叉表指向源表和目标表的规则名。 注意:拼写必须严格一致。 例 9. 使用魔术多对多方法获取结果集 下例完成与上例同样的功能。 $bugsTable = new Bugs(); $bugsRowset = $bugsTable->find(1234); $bug1234 = $bugsRowset->current(); // Use the default reference rule $products = $bug1234->findProductsViaBugsProducts(); // Specify the reference rule $products = $bug1234->findProductsViaBugsProductsByBug(); 6. 级联写操作 注意:仅当在数据库不支持参照完整性约束的时候才使用Zend_Db_Table提供的级联操作功能。例如,如果使用MySQL的MyISAM存储引擎,或者SQLite,这些数据库不支持DRI(declarative referential integrity),这时候使用Zend_Db_Table的级联操作将很有帮助。如果数据库本身就实现了DRI,那最好还是在数据库中声明这些约束,而不是使用Zend_Db_Table,数据库本身的DRI在性能、完整性和一致性方面好于Zend_Db_Table。更重要的是,不要在数据库和Zend_Db_Table中同时声明级联操作。 当对父表的一条记录进行更新或删除操作的时候,可以声明一个级联操作让它同时影响到依赖表。 例 10. 级联删除 下例删除掉Products表中的一行,通过相关的配置会自动会删除掉Bugs表中相关产品的所有bug记录。 $productsTable = new Products(); $productsRowset = $productsTable->find(1234); $product1234 = $productsRowset->current(); $product1234->delete(); // Automatically cascades to Bugs table // and deletes dependent rows. 同样,如果更新了父表中的某个主键的值,肯定希望与它外键相关的依赖表中的记录也自动更新成新值,这种引用能够让它们保持一致。 通常并不需要对那些通过序列或者其他机制产生的主键值进行更新。但如果使用的是一个自然键偶尔可能会改变其值,这时就可能会用到级联更新操作。 要在Zend_Db_Table中声明一个级联关系,需要编辑$_referenceMap的规则,设置关联数组的'onDelete'和'onUpdate'键的值为'cascade'(或者使用常数self::CASCADE)。这样,在父表的某条记录被删除、或者主键被修改之前,依赖表中的相关记录都会被先删除或更新。 例 11. 声明级联操作 下例中,如果Products中的某行被删除,Bugs表中该产品的相关bug记录也都会被删除,因为关系映射中的'onDelete'元素的值被设置为self::CASCADE。 但如果父表中的某个主键被更新,不会产生级联操作影响到依赖表,因为'onUpdate'元素的值被设置为self::RESTRICT。也可以通过设置其值为self::NO_ACTION,或者省略掉'onUpdate'项来达到同样的效果。 class BugsProducts extends Zend_Db_Table_Abstract { ... protected $_referenceMap = array( 'Product' => array( 'columns' => array('product_id'), 'refTableClass' => 'Products', 'refColumns' => array('product_id'), 'onDelete' => self::CASCADE, 'onUpdate' => self::RESTRICT ), ... ); } 6.1. 关于级联操作 Zend_Db_Table中的级联操作不是原子操作。 这就意味着如果数据库实现了参照完整性约束,由Zend_Db_Table触发的级联更新操作将与约束产生抵触,产生参照完整性违例。仅当数据库没有强制实行参照完整性约束时才能使用Zend_Db_Table的级联更新功能。 级联删除操作引发参照完整性违例的问题较小,可以在删除父表的记录之前,用非原子操作完成依赖表中的数据删除操作。 然而,不管是更新还是删除,用非原子操作更改数据库都有可能产生危险,即其他用户可能会看到数据处于不一致的状态。例如,如果删除一条记录和它所有的依赖记录,中间就有很小的机会使得其他数据库客户端程序能够在删除掉依赖记录后、没删父记录之前查询到数据库。这个客户端程序将能看到没有依赖数据的父数据,并以为这就是数据的真实状态。没有办法让这个客户端知道它查询的数据是个变化中的过程。 非原子操作的问题可以通过使用事务隔离这一过程而减轻,但有些数据库产品并不支持事务,或者允许客户端读那些还没有提交的“脏”数据。 Zend_Db_Table 中的级联操作只能由Zend_Db_Table 调用。 当执行Row类的save()或者delete()方法时,在Zend_Db_Table中定义的级联删除和更新操作将能被触发执行。但如果通过其他接口,比如一个查询工具或者其他应用程序,来执行更新删除操作,将不会触发级联操作。甚至当用Zend_Db_Adapter类的update()和delete()方法时也不会触发。 没有级联新增操作。 目前不支持级联插入。必须先在父表中插入一条记录,再在它的依赖表中插入其他记录

你可能感兴趣的:(Zend,PHP,Rails,SVN,SQLite)