用户名 密码 记住我 还未注册?

MediaWiki - ORM对象关系映射

[首页]

ORM对象关系映射

Wikipedia,自由的百科全书

首页 | 最近更改 | 编辑本页 | 修订历史 | 切换到MediaWiki模式

可打印版 | 免责声明 | Privacy policy

目录

使用Frameworks库

在上一章讲解了如何开发数据对象,为了广泛的适应各种需求,XOOPS系统提供的数据对象机制并没有涉及实际的SQL语句,需要由模块开发者自行构造SQL语句。而从大量的模块开发实践来看,模块中所使用的大多数SQL语句都具有一些共同特征,这些共同特征被称之为ORM(Object-Relation Mapping,对象关系映射)机制。

说明:对象关系映射(Object Relational Mapping,简称ORM),是一种用于实现从对象数据到关系数据的存储映射的技术。
面向对象是从软件工程基本原则(如耦合、聚合、封装)的基础上发展起来的,而关系数据库则是从数学理论发展而来的,两套理论存在显著的区别。为了解决这个不匹配的现象,对象关系映射技术应运而生。

--摘自zh.wikipedia.org

在XOOPS内核的基础上,可以使用Frameworks库,Frameworks库提供广泛的模块开发所需的库函数支持,其中包括较为完善的ORM实现,基于Frameworks库的ORM技术开发模块可以大大节约模块的开发时间,同时还能减少编写SQL语句疏忽导致的安全风险。本节将继续前述章节的实例讲解如何使用Frameworks库的ORM技术。本例的文件夹结构如下:

/modules/ormframeworks
	/class
	/contact.php
	/images
	/logo.png
	/sql
	/mysql.sql
	/templates
	/ormframeworks_index.html
	/index.php
	/xoops_version.php

其中xoops_version.php的内容如下:

/modules/ormframeworks/xoops_version.php
<?php
$modversion['name'] = "ORM对象关系映射 - Frameworks";
$modversion['version'] = 0.01;
$modversion['description'] = "演示如何使用Frameworks库";
$modversion['author'] = <<<AUTHOR
胡争辉 QQ: 443089607 QQMail: [email protected] GTalk: huzhengh
GMail: [email protected] Skype: huzhenghui"
AUTHOR;
$modversion['credits'] = "";
$modversion['license'] = "版权所有";
$modversion['image'] = "images/logo.png";
$modversion['dirname'] = "ormframeworks";
$modversion["hasMain"] = 1;
$modversion["sqlfile"]["mysql"] = "sql/mysql.sql";
$modversion["tables"][] = "ormframeworks_contact";
$modversion["templates"][0]["file"] = "ormframeworks_index.html";
$modversion["templates"][0]["description"] = "Template for index.php";
?>

为便于对照学习,数据结构与上例类似,mysql.sql的内容如下:

/modules/ormframeworks/sql/mysql.sql
CREATE TABLE `ormframeworks_contact` (
  /* 详见源文件 */
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;
INSERT INTO `ormframeworks_contact` (`id`, `firstname`, `lastname`, `QQ`, `QQMail`, `GTalk`, `GMail`, `Skype`) VALUES (1, '争辉', '胡', '443089607', '[email protected]', 'huzhengh', '[email protected]', 'huzhenghui');

将数据对象更改为基于Frameworks库的ORM技术时,不会影响原有的页面,index.php文件源代码如下:

/modules/ormframeworks/index.php
<?php
require_once dirname(__FILE__)."/../../mainfile.php";
$contacthandler = xoops_getmodulehandler("contact", "ormframeworks");
/* @var $contacthandler OrmframeworksContactHandler */
$xoopsOption["template_main"] = "ormframeworks_index.html";
include XOOPS_ROOT_PATH."/header.php";
/* @var $xoopsTpl XoopsTpl */
$contacts = $contacthandler->getAll();
$xoopsTpl->assign_by_ref("contacts", $contacts);
include XOOPS_ROOT_PATH."/footer.php";
?>

也不会影响模板,ormframeworks_index.html文件的源代码如下:

/modules/ormframeworks/templates/ormframeworks_index.html
<{strip}>
	<table>
        <!-- 详见源文件 -->
	</table>
<{/strip}>

修改范围主要涉及数据对象文件中数据访问句柄,contact.php文件的源代码如下:

/modules/ormframeworks/class/contact.php
<?php
if (false === defined("XOOPS_ROOT_PATH")) {
    exit();
}
if (false === defined("FRAMEWORKS_ART_FUNCTIONS_INI")) {
    require_once XOOPS_ROOT_PATH.'/Frameworks/art/functions.ini.php';
}
load_object();
class OrmframeworksContact extends ArtObject {
    function OrmframeworksContact() {
        $this->ArtObject("ormframeworks_contact");
        /* 详见源文件 */
    }
}
class OrmframeworksContactHandler extends ArtObjectHandler {
    function OrmframeworksContactHandler ($db) {
        $this->ArtObjectHandler($db, "ormframeworks_contact",
            "OrmframeworksContact", "id");
        return;
    }
}
?>

首先检查是否定义了XOOPS_ROOT_PATH常量。

if (false === defined("XOOPS_ROOT_PATH")) {
    exit();
}

在XOOPS系统中,所有的PHP文件都应当经过mainfile.php进行安全检查。因此直接被用户访问的PHP文件应当在开始处包含mainfile.php文件,不直接被用户访问的PHP文件应当通过检查XOOPS_ROOT_PATH常量保证已包含过mainfile.php文件。然后包含Frameworks库的初始化文件。

if (false === defined("FRAMEWORKS_ART_FUNCTIONS_INI")) {
    require_once XOOPS_ROOT_PATH.'/Frameworks/art/functions.ini.php';
}

基于Frameworks库的程序都应该包含Frameworks库的初始化文件。由于本文件需要使用Frameworks库的ORM类,所以调用加载ORM类的函数。

load_object();

该函数定义在functions.ini.php文件中。

/Frameworks/art/functions.ini.php
function load_object() {}

将数据对象修改为基于Frameworks库时,仅需少量的改动,首先数据对象应继承自ArtObject类。

class OrmframeworksContact extends ArtObject {}

ArtObject类定义在Frameworks库的object.php文件中。

/Frameworks/art/object.php
class ArtObject extends _XoopsPersistableObject {}

然后在数据对象的构造函数中初始化数据表名称。

        $this->ArtObject("ormframeworks_contact");

这样就完成了数据对象的改造,初始化字段的代码保持不变。下面改造数据访问句柄,类似的基于Frameworks库的数据访问句柄应继承自ArtObjectHandler类。

class OrmframeworksContactHandler extends ArtObjectHandler {}

ArtObjectHandler类定义在Frameworks库的object.php文件中。

/Frameworks/art/object.php
class ArtObjectHandler extends _XoopsPersistableObjectHandler {}

基于Frameworks库的数据访问句柄需要初始化数据库相关信息,因此定义数据访问句柄的构造函数。

    function OrmframeworksContactHandler ($db) {}

其中$db参数是XOOPS系统的数据库对象,然后在数据访问句柄的构造函数中调用ArtObjectHandler类的构造函数初始化相关信息。

$this->ArtObjectHandler($db, "ormframeworks_contact", "OrmframeworksContact", "id");

ArtObjectHandler的构造函数定义在Frameworks库的object.php文件中。

/Frameworks/art/object.php
function ArtObjectHandler(&$db, $table = "", $className = "", $keyName = "", $identifierName = false) {}

其中$db参数是数据库链接,$table参数是数据表的名称,$className参数是数据访问句柄对应的数据对象的类名,$keyName参数是数据表主键的名称,$identifierName参数用于getList函数。本例中$table的值是ormframeworks_contact,$className的值是OrmframeworksContact,$keyName的值是id。通过对比可以发现,数据访问句柄并没有实现getAll函数,而在index.php中却调用了数据访问句柄的getAll函数,该函数继承自Frameworks库的_XoopsPersistableObjectHandler类,定义如下。

/Frameworks/art/object.persistable.php
    function &getAll($criteria = null, $tags = null, $asObject = true) {}

在不传递参数时,getAll()函数将以数据对象的形式返回数据表中所有的数据。从本例可以看出,基于Frameworks库不再需要编写常用的数据访问函数。大大减轻了开发工作量。

条件组合

在上一章讲解了如何使用条件对象,但是在实际应用中,常常会遇到多个条件组合在一起的情况,为了便于组合各种条件,XOOPS系统提供了条件组合对象CriteriaCompo,通过CriteriaCompo对象可以自由的组合Criteria对象,组合的方式既可以是AND,也可以是OR,进一步还可以组合其他的CriteriaCompo对象构造嵌套的SQL条件。CriteriaCompo对象与Criteria对象的区别在于CriteriaCompo对象能组合其他的CriteriaCompo对象或Criteria对象,而Criteria对象不能组合其他的对象。同时,CriteriaCompo对象和Criteria对象都支持设置排序字段、排序顺序、分组字段、开始行数、最多返回行数等信息,但是CriteriaCompo对象和Criteria对象并不处理这些信息,仅维护相关的信息,以便于构造SQL语句时按需使用,与上例类似,Frameworks库也按照大多数SQL语句的共同特征提供了对这些信息的处理过程。接下来本节将讲解CriteriaCompo对象以及Frameworks库对CriteriaCompo对象的支持。本例的文件夹结构如下:

/modules/ormcriteria
	/class
	/section.php
	/images
	/logo.png
	/sql
	/mysql.sql
	/templates
	/ormcriteria_index.html
	/index.php
	/xoops_version.php

其中xoops_version.php的内容如下:

/modules/ormcriteria/xoops_version.php
<?php
$modversion['name'] = "ORM对象关系映射 - 条件组合";
$modversion['version'] = 0.01;
$modversion['description'] = "演示如何使用条件组合";
$modversion['author'] = <<<AUTHOR
胡争辉 QQ: 443089607 QQMail: [email protected] GTalk: huzhengh
GMail: [email protected] Skype: huzhenghui"
AUTHOR;
$modversion['credits'] = "";
$modversion['license'] = "版权所有";
$modversion['image'] = "images/logo.png";
$modversion['dirname'] = "ormcriteria";
$modversion["hasMain"] = 1;
$modversion["sqlfile"]["mysql"] = "sql/mysql.sql";
$modversion["tables"][] = "ormcriteria_section";
$modversion["templates"][0]["file"] = "ormcriteria_index.html";
$modversion["templates"][0]["description"] = "Template for index.php";
?>

本例的mysql.sql的内容如下:

/modules/ormcriteria/sql/mysql.sql
CREATE TABLE `ormcriteria_section` (
  `id` int(11) NOT NULL auto_increment,
  `caption` text collate utf8_bin NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=10 ;
INSERT INTO `ormcriteria_section` (`id`, `caption`) VALUES (1, 'xoops_version.php');
INSERT INTO `ormcriteria_section` (`id`, `caption`) VALUES (2, '模块普通页面');
INSERT INTO `ormcriteria_section` (`id`, `caption`) VALUES (3, '模块管理页面');
INSERT INTO `ormcriteria_section` (`id`, `caption`) VALUES (4, '数据表');
INSERT INTO `ormcriteria_section` (`id`, `caption`) VALUES (5, '数据对象');
INSERT INTO `ormcriteria_section` (`id`, `caption`) VALUES (6, '条件对象');
INSERT INTO `ormcriteria_section` (`id`, `caption`) VALUES (7, '表单对象');
INSERT INTO `ormcriteria_section` (`id`, `caption`) VALUES (8, '模板');
INSERT INTO `ormcriteria_section` (`id`, `caption`) VALUES (9, '区块');

本例的数据对象及数据访问句柄的结构与上例类似,section.php文件的源代码如下:

/modules/ormcriteria/class/section.php
<?php
/* 详见源代码 */
class OrmcriteriaSection extends ArtObject {
    function OrmcriteriaSection () {
        $this->ArtObject("ormcriteria_section");
        $this->initVar("id", XOBJ_DTYPE_INT);
        $this->initVar("caption", XOBJ_DTYPE_TXTBOX);
        return;
    }
}
class OrmcriteriaSectionHandler extends ArtObjecthandler {
    function OrmcriteriaSectionHandler($db) {
        $this->ArtObjectHandler($db, "ormcriteria_section",
            "OrmcriteriaSection", "id");
        return;
    }
}
?>

由于Frameworks库对于数据对象和数据访问句柄已提供常用的功能,通常直接继承即可满足要求,在页面中只需要直接调用,index.php页面的源代码如下:

/modules/ormcriteria/index.php
<?php
require_once dirname(__FILE__)."/../../mainfile.php";
$sectionhandler = xoops_getmodulehandler("section", "ormcriteria");
/* @var $sectionhandler OrmcriteriaSectionHandler */
$xoopsOption["template_main"] = "ormcriteria_index.html";
include XOOPS_ROOT_PATH."/header.php";
/* @var $xoopsTpl XoopsTpl */
$allcriteria = new CriteriaCompo();
if (true === isset($_GET["id"])) {
    if (true === is_array($_GET["id"])) {
        $myts = MyTextSanitizer::getInstance();
        foreach ($_GET["id"] as $id) {
            $allcriteria->add(new Criteria("id", $myts->addSlashes($id)), "OR");
        }
    }
}
$allcriteria->setSort("id");
$allcriteria->setOrder("ASC");
$allsections = $sectionhandler->getAll($allcriteria);
$xoopsTpl->assign_by_ref("allsections", $allsections);
include XOOPS_ROOT_PATH."/footer.php";
?>

首先获取数据访问句柄。

$sectionhandler = xoops_getmodulehandler("section", "ormcriteria");

然后设置页面所使用的模板。

$xoopsOption["template_main"] = "ormcriteria_index.html";

在构造组合条件之前,先创建CriteriaCompo对象。

$allcriteria = new CriteriaCompo();

CriteriaCompo类与Criteria类同样定义在criteria.php文件中。

/class/criteria.php
class CriteriaCompo extends CriteriaElement {}

然后判断是否提交了id参数。

if (true === isset($_GET["id"])) {

如果提交了id参数,再判断id参数是否是数组。

    if (true === is_array($_GET["id"])) {

当id参数是数组时,循环访问id参数数组中的数据项。

        foreach ($_GET["id"] as $id) {

将每个数据项创建一个Criteria对象并加入到组合条件中。

            $allcriteria->add(new Criteria("id", $myts->addSlashes($id)), "OR");

add函数是CriteriaCompo类的成员函数,用于向CriteriaCompo对象中添加CriteriaCompo对象或添加Criteria对象。

/class/criteria.php
    function &add(&$criteriaElement, $condition='AND') {}

参数$criteriaElement是被添加的CriteriaCompo对象或Criteria对象,参数$condition是组合的条件,默认值是AND,代表“与”关系,本例中用OR,代表“或”关系。

添加了条件之后,设置CriteriaCompo对象的排序字段为id。

$allcriteria->setSort("id");

然后设置CriteriaCompo对象的排序方式为顺序。

$allcriteria->setOrder("ASC");

在构造了CriteriaCompo对象之后,调用数据访问句柄的getAll函数即可按条件查询出所需的对象数组。

$allsections = $sectionhandler->getAll($allcriteria);

_XoopsPersistableObjectHandler类的getAll函数的第一个参数是条件对象,其中处理了组合条件以及排序字段、排序方式等信息,所以继承自ArtObjectHandler的数据访问句柄中直接调用getAll函数即可。最后将对象数组传递给模板。

$xoopsTpl->assign_by_ref("allsections", $allsections);

在完成了逻辑部分之后,下面制作本例所使用的模板,源代码如下:

/modules/ormcriteria/templates/ormcriteria_index.html
<{strip}>
    <form>
	    <table>
	        <tr>
	            <th>
	            </th>
	            <th>
	                id
	            </th>
	            <th>
	                Caption
	            </th>
	        </tr>
		    <{foreach item="sectionitem" from=$allsections}>
		        <tr>
		           <td>
		               <input type="checkbox" name="id[]" value="<{$sectionitem->getVar("id")}>" />
		           </td>
		           <td>
		               <{$sectionitem->getVar("id")}>
		           </td>
		           <td>
		               <{$sectionitem->getVar("caption")}>
		           </td>
		        </tr>
		    <{/foreach}>
	    </table>
	    <input type="submit" />
    </form>
<{/strip}>

在模板中循环访问对象数组,每个对象显示一个复选框,效果如图2-1条件组合全部对象所示。

图2-1条件组合全部对象
Enlarge
图2-1条件组合全部对象

单击“提交查询”按钮后,将仅显示所选择的对象。如图2-2条件组合查询结果所示。

图2-2条件组合查询结果
Enlarge
图2-2条件组合查询结果

通过本例可以看出借助于CriteriaCompo对象,可以自由处理不同的业务逻辑,而不需要为每个业务逻辑分别开发相应的功能。

处理转义

在上一节的例子中,使用了MyTextSanitizer类对参数进行转义。相关代码如下:

/modules/ormcriteria/index.php
        $myts = MyTextSanitizer::getInstance();
        foreach ($_GET["id"] as $id) {
            $allcriteria->add(new Criteria("id", $myts->addSlashes($id)), "OR");
        }

转义的原因是消除magic_quotes_gpc设置对参数的影响。

说明:magic_quotes_gpc设置参见[这里]

因此所有的输入参数在使用前,都应该消除该设置对参数的影响。XOOPS系统针对不同的使用方式提供了addSlashes和stripSlashesGPC两个函数,这两个函数都定义在MyTextSanitizer类中。

/class/module.textsanitizer.php
class MyTextSanitizer {
	function addSlashes($text) {}
	function stripSlashesGPC($text) {}
}

如果输入参数用于构造条件,则需要调用addSlashes函数,如上例所示。

/modules/ormcriteria/index.php
            $allcriteria->add(new Criteria("id", $myts->addSlashes($id)), "OR");

addSlashes函数保证该参数仅被转义一次,构造出的条件对象不仅符合SQL中参数的要求,还可以避免SQL注入攻击。

如果输入参数需要还原成原值,则需要调用stripSlashesGPC函数。stripSlashesGPC函数保证该参数还原成用户输入的原值。接下来本例讲解如何在XOOPS系统中处理转义。本例的文件夹结构如下:

/modules/ormmyts
	/class
	/section.php
	/images
	/logo.png
	/sql
	/mysql.sql
	/templates
	/ormmyts_index.html
	/index.php
	/xoops_version.php

其中xoops_version.php的内容如下:

/modules/ormmyts/xoops_version.php
<?php
$modversion['name'] = "ORM对象关系映射 - 处理转义";
$modversion['version'] = 0.01;
$modversion['description'] = "演示如何使用转义";
$modversion['author'] = <<<AUTHOR
胡争辉 QQ: 443089607 QQMail: [email protected] GTalk: huzhengh
GMail: [email protected] Skype: huzhenghui"
AUTHOR;
$modversion['credits'] = "";
$modversion['license'] = "版权所有";
$modversion['image'] = "images/logo.png";
$modversion['dirname'] = "ormmyts";
$modversion["hasMain"] = 1;
$modversion["sqlfile"]["mysql"] = "sql/mysql.sql";
$modversion["tables"][] = "ormmyts_section";
$modversion["templates"][0]["file"] = "ormmyts_index.html";
$modversion["templates"][0]["description"] = "Template for index.php";
?>

为便于对照学习,本例的数据结构与上例类似,mysql.sql的内容如下:

/modules/ormmyts/sql/mysql.sql
CREATE TABLE `ormmyts_section` (
  /* 详见源代码 */
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=10 ;
-- 详见源代码

本例的数据对象及数据访问句柄结构与上例类似,section.php文件的源代码如下:

/modules/ormmyts/class/section.php
<?php
/* 详见源代码 */
class OrmmytsSection extends ArtObject {
    function OrmmytsSection () {
        $this->ArtObject("ormmyts_section");
        /* 详见源代码 */
    }
}
class OrmmytsSectionHandler extends ArtObjecthandler {
    function OrmmytsSectionHandler($db) {
        $this->ArtObjectHandler($db, "ormmyts_section",
            "OrmmytsSection", "id");
        return;
    }
}
?>

本例的模板去掉了复选框改为文本框,源代码如下:

/modules/ormmyts/templates/ormmyts_index.html
<{strip}>
    <table>
        <tr>
            <!-- 详见源代码 -->
        </tr>
	    <{foreach item="sectionitem" from=$allsections}>
	        <tr>
                <!-- 详见源代码 -->
	        </tr>
	    <{/foreach}>
    </table>
    <form>
        <input name="id" />
	    <input type="submit" />
    </form>
<{/strip}>

修改后的界面效果如图2-3处理转义全部结果所示。

图2-3处理转义全部结果
Enlarge
图2-3处理转义全部结果

本例中将参数id由复选框改成了文本框,修改相应的处理过程,源代码如下:

/modules/ormmyts/index.php
<?php
require_once dirname(__FILE__)."/../../mainfile.php";
$sectionhandler = xoops_getmodulehandler("section", "ormmyts");
/* @var $sectionhandler OrmmytsSectionHandler */
$xoopsOption["template_main"] = "ormmyts_index.html";
/* 详见源代码 */
if (true === isset($_GET["id"])) {
    $myts = MyTextSanitizer::getInstance();
    /* @var $myts MyTextSanitizer */
    $ids = explode(",", $myts->stripSlashesGPC($_GET["id"]));
    if (true === is_array($ids)) {
        foreach ($ids as $id) {
            if ("" !== trim($id)) {
                $allcriteria->add(new Criteria("id", addSlashes($id)), "OR");
            }
        }
    }
}
/* 详见源代码 */
?>

在处理输入参数时,首先用逗号分割输入参数。

    $ids = explode(",", $myts->stripSlashesGPC($_GET["id"]));

此处调用MyTextSanitizer类的stripSlashesGPC函数将输入参数id转化为原始数据。由于原始数据中没有对字符转义,因此在设置条件时,又需要强制对参数转义。

                $allcriteria->add(new Criteria("id", addSlashes($id)), "OR");

本例中用PHP提供的addSlashes函数转义,而上例使用MyTextSanitizer类的addSlashes函数转义,区别在于MyTextSanitizer类的addSlashes函数用于对输入参数直接转义,因此会判断输入参数是否已被转义过,如果被转义过则不再转义,避免两次转义。而PHP的addSlashes函数只转义,不进行额外的判断,因此对于原始数据转义需要使用PHP提供的addSlashes函数,而对输入数据转义需要使用MyTextSanitizer类的addSlashes函数。运行的效果如图2-4处理转义查询结果所示。

图2-4处理转义查询结果
Enlarge
图2-4处理转义查询结果

如果没有采用正确的转义方式,不仅有可能无法正确的得到查询结果,而且可能遇到SQL注入能安全性隐患。

数据查询

上一节讲解了如何处理用户输入数据的转义,并构造查询条件通过getAll函数得到查询结果。getAll函数提供了条件组合、排序字段、排序方式、初始行数和查询行数的封装。由于查询是最常用的数据库操作,在实际应用中会有各种各样的需求,getAll函数并不能满足全面的要求,在Frameworks库中还提供了其他几种数据查询函数。本例中将演示这些函数的使用。

注意:getAll与下面介绍的几个函数相比,具备性能高,适应范围广的优势,缺点在于传入的参数有时需要进行额外的设置。

本例的文件夹结构如下:

/modules/ormrender
	/class
	/section.php
	/images
	/logo.png
	/sql
	/mysql.sql
	/templates
	/ormrender_ids.html
	/ormrender_index.html
	/ormrender_limit.html
	/ormrender_list.html
	/ids.php
	/index.php
	/limit.php
	/list.php
	/xoops_version.php

其中xoops_version.php的内容如下:

/modules/ormrender/xoops_version.php
<?php
$modversion['name'] = "ORM对象关系映射 - 查找";
$modversion['version'] = 0.01;
$modversion['description'] = "演示多种面向对象的查找方式";
$modversion['author'] = <<<AUTHOR
胡争辉 QQ: 443089607 QQMail: [email protected] GTalk: huzhengh
GMail: [email protected] Skype: huzhenghui"
AUTHOR;
$modversion['credits'] = "";
$modversion['license'] = "版权所有";
$modversion['image'] = "images/logo.png";
$modversion['dirname'] = "ormrender";
$modversion["hasMain"] = 1;
$modversion["sub"][0]["name"] = "getByLimit";
$modversion["sub"][0]["url"] = "limit.php";
$modversion["sub"][1]["name"] = "getIds";
$modversion["sub"][1]["url"] = "ids.php";
$modversion["sub"][2]["name"] = "getList";
$modversion["sub"][2]["url"] = "list.php";
$modversion["sqlfile"]["mysql"] = "sql/mysql.sql";
$modversion["tables"][] = "ormrender_section";
$modversion["templates"][0]["file"] = "ormrender_index.html";
$modversion["templates"][0]["description"] = "Template for index.php";
$modversion["templates"][1]["file"] = "ormrender_limit.html";
$modversion["templates"][1]["description"] = "Template for limit.php";
$modversion["templates"][2]["file"] = "ormrender_ids.html";
$modversion["templates"][2]["description"] = "Template for ids.php";
$modversion["templates"][3]["file"] = "ormrender_list.html";
$modversion["templates"][3]["description"] = "Template for list.php";
?>

本例的四个页面分别演示四个函数的使用方法,为便于对照学习,本例的四个函数使用同一个数据表,与上例的数据结构类似,mysql.sql的内容如下:

/modules/ormrender/sql/mysql.sql
CREATE TABLE `ormrender_section` (
/* 详见源代码 */
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=10 ;
-- 详见源代码

虽然本例演示四种不同的数据查询方式,但是数据对象和数据访问句柄不需要额外的编码,与上例类似,section.php文件的源代码如下:

/modules/ormrender/class/section.php
<?php
/* 详见源代码 */
class OrmrenderSection extends ArtObject {
    function OrmrenderSection () {
        $this->ArtObject("ormrender_section");
        /* 详见源代码 */
    }
}
class OrmrenderSectionHandler extends ArtObjecthandler {
    function OrmrenderSectionHandler($db) {
        $this->ArtObjectHandler($db, "ormrender_section",
            "OrmrenderSection", "id", "caption");
        return;
    }
}
?>

这里使用了ArtObjectHandler函数的第五个参数$identifierName,该参数用于指定能代表对象的标识字段,与主键不同,该字段不保证唯一,但应便于阅读。

getObjects函数是ArtObjectHandler类中动态调用ArtObjectRenderHandler类的getObjects函数实现,定义如下:

/Frameworks/art/object.render.php
    function &getObjects($criteria = null, $id_as_key = false, $as_object = true) {}

其中$criteria参数查询的条件对象,$id_as_key参数为false时,返回的数组使用序数作为键名,值为true时,使用数据的主键作为键名。在面向对象编程时,也经常会使用数据的主键作为数组的键名,此时就可以使用getObjects函数。示例页面如下:

/modules/ormrender/index.php
<?php
require_once dirname(__FILE__)."/../../mainfile.php";
$sectionhandler = xoops_getmodulehandler("section", "ormrender");
/* @var $sectionhandler OrmrenderSectionHandler */
$xoopsOption["template_main"] = "ormrender_index.html";
include XOOPS_ROOT_PATH."/header.php";
/* @var $xoopsTpl XoopsTpl */
$allsections = $sectionhandler->getObjects(null, true);
$xoopsTpl->assign_by_ref("allsections", $allsections);
include XOOPS_ROOT_PATH."/footer.php";
?>

相应的模板如下:

/modules/ormrender/templates/ormrender_index.html
<{strip}>
    <table>
        <tr>
            <th>
                Key
            </th>
            <!-- 详见源代码 -->
            <th>
                Caption
            </th>
        </tr>
	    <{foreach item="sectionitem" key="key" from=$allsections}>
	        <tr>
                <td>
                    <{$key}>
                </td>
	            <td>
                    <{$allsections.$key->getVar("caption")}>
	            </td>
	        </tr>
	    <{/foreach}>
    </table>
<{/strip}>

从上面可以看出,通过getObjects函数获取以主键为键名的数组,在模板中不仅能够直接访问键名,而且还可以通过键名获取键值,而不必通过循环得到的变量。

                    <{$allsections.$key->getVar("caption")}>

与getAll函数相比,getObjects的效率相对低些,运行效果如图2-5 getObjects效果所示。

图2-5 getObjects效果
Enlarge
图2-5 getObjects效果

getByLimit函数是ArtObjectHandler类中动态调用ArtObjectRenderHandler类的getByLimit函数实现,定义如下:

/Frameworks/art/object.render.php
function &getByLimit($limit=0, $start = 0, $criteria = null, $tags = null, $asObject=true) {}

其中$limit参数限制查询结果中的行数,$start参数设置查询的起始行数。当查询结果较多时,可以直接使用getByLimit函数限制查询结果的范围,在内部getByLimi函数调用getAll函数实现,查询的结果是以主键为键名的对象数组。示例页面如下:

/modules/ormrender/limit.php
<?php
/* 详见源代码 */
$xoopsOption["template_main"] = "ormrender_limit.html";
include XOOPS_ROOT_PATH."/header.php";
/* @var $xoopsTpl XoopsTpl */
$sections1 = $sectionhandler->getByLimit(3, 0);
$xoopsTpl->assign_by_ref("sections1", $sections1);
$sections2 = $sectionhandler->getByLimit(3, 3);
$xoopsTpl->assign_by_ref("sections2", $sections2);
$sections3 = $sectionhandler->getByLimit(3, 6);
$xoopsTpl->assign_by_ref("sections3", $sections3);
include XOOPS_ROOT_PATH."/footer.php";
?>

相应的模板如下:

/modules/ormrender/templates/ormrender_limit.html
<{strip}>
    <table>
        <tr>
            <!-- 详见源代码 -->
        </tr>
        <tr>
            <th colspan="2">
                Sections1
            </th>
        </tr>
	    <{foreach item="sectionitem" key="key" from=$sections1}>
            <!-- 详见源代码 -->
	    <{/foreach}>
        <tr>
            <th colspan="2">
                Sections2
            </th>
        </tr>
        <{foreach item="sectionitem" key="key" from=$sections2}>
            <!-- 详见源代码 -->
        <{/foreach}>
        <tr>
            <th colspan="2">
                Sections3
            </th>
        </tr>
        <{foreach item="sectionitem" key="key" from=$sections3}>
            <!-- 详见源代码 -->
        <{/foreach}>
    </table>
<{/strip}>

从上面可以看出,通过getByLimit函数可以限定查询范围,而且获取以主键为键名的数组,与getAll函数相比,相对方便些。运行效果如图2-6 getByLimit效果所示。

图2-6 getByLimit效果
Enlarge
图2-6 getByLimit效果

getIds函数是ArtObjectHandler类中动态调用ArtObjectRenderHandler类的getIds函数实现,定义如下:

/Frameworks/art/object.render.php
    function &getIds($criteria = null) {}

其中$criteria参数是查询的条件,查询的结果是以主键为键值的数组。示例页面如下:

/modules/ormrender/ids.php
<?php
/* 详见源代码 */
$xoopsOption["template_main"] = "ormrender_ids.html";
/* 详见源代码 */
$allsections = $sectionhandler->getIds();
/* 详见源代码 */
?>

与index.php相比仅是函数不同。相应的模板如下:

/modules/ormrender/templates/ormrender_ids.html
<{strip}>
    <table>
        <tr>
            <th>
                id
            </th>
        </tr>
        <{foreach item="id" from=$allsections}>
	        <tr>
	            <td>
                    <{$id}>
	            </td>
	        </tr>
	    <{/foreach}>
    </table>
<{/strip}>

从上面可以看出,通过getIds可以按条件仅获取主键,与getAll函数相比,getIds函数的功能相对单一。运行效果如图2-7 getIds效果所示。

图2-7 getIds效果
Enlarge
图2-7 getIds效果

getList函数是ArtObjectHandler类中动态调用ArtObjectRenderHandler类的getList函数实现,定义如下:

/Frameworks/art/object.render.php
    function getList($criteria = null, $limit = 0, $start = 0) {}

其中$criteria参数是查询的条件,$limit参数限制查询结果中的行数,$start参数设置查询的起始行数,查询的结果是以主键为键名,以$identifierName对应的字段为键值的数组。示例页面如下:

/modules/ormrender/list.php
<?php
/* 详见源代码 */
$xoopsOption["template_main"] = "ormrender_list.html";
/* 详见源代码 */
$allsections = $sectionhandler->getList();
/* 详见源代码 */
?>

与index.php相比仅是函数不同,相应的模板如下:

/modules/ormrender/templates/ormrender_list.html
<{strip}>
    <table>
        <tr>
            <th>
                key
            </th>
            <th>
                identifier
            </th>
        </tr>
        <{foreach item="identifier" key="key" from=$allsections}>
	        <tr>
	            <td>
	               <{$key}>
	            </td>
	            <td>
                    <{$identifier}>
	            </td>
	        </tr>
	    <{/foreach}>
    </table>
<{/strip}>

从上面可以看出,通过getList可以按条件和行数范围获取主键以及指定的标识列,与getAll函数相比,getList函数的功能相对简单。运行的效果如图2-8 getList效果所示。

图2-8 getList效果
Enlarge
图2-8 getList效果

分页

在上例的limit.php页面中演示了限制行数的查询方式,在实际应用中,很少有直接指定起始行数和查询行数的情况,通常是用于分页显示查询结果的情况。在实际应用中,当查询结果的行数较多时,通常会采用分页策略。为了便于开发,XOOPS系统提供了XoopsPageNav类用于显示分页导航。在Frameworks库中还提供了查询总行数的函数,以便于计算分页的总页数,接下来本例将讲解如何开发分页导航。本例的文件夹结构如下:

/modules/ormstats
	/class
	/section.php
	/images
	/logo.png
	/sql
	/mysql.sql
	/templates
	/ormstats_index.html
	/index.php
	/xoops_version.php

其中xoops_version.php的内容如下:

/modules/ormstats/xoops_version.php
<?php
$modversion['name'] = "ORM对象关系映射 - 分页";
$modversion['version'] = 0.01;
$modversion['description'] = "演示分页显示数据";
$modversion['author'] = <<<AUTHOR
胡争辉 QQ: 443089607 QQMail: [email protected] GTalk: huzhengh
GMail: [email protected] Skype: huzhenghui"
AUTHOR;
$modversion['credits'] = "";
$modversion['license'] = "版权所有";
$modversion['image'] = "images/logo.png";
$modversion['dirname'] = "ormstats";
$modversion["hasMain"] = 1;
$modversion["sqlfile"]["mysql"] = "sql/mysql.sql";
$modversion["tables"][] = "ormstats_section";
$modversion["templates"][0]["file"] = "ormstats_index.html";
$modversion["templates"][0]["description"] = "Template for index.php";
?>

为便于对照学习,本例采用上例中介绍的getByLimit函数,数据结构也类似,mysql.sql的内容如下:

/modules/ormstats/sql/mysql.sql
CREATE TABLE `ormstats_section` (
/* 详见源代码 */
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=10 ;
-- 详见源代码

本例的数据对象和数据访问句柄也不需要额外的编码,与上例类似,section.php文件的源代码如下:

/modules/ormstats/class/section.php
<?php
/* 详见源代码 */
class OrmstatsSection extends ArtObject {
    function OrmstatsSection () {
        $this->ArtObject("ormstats_section");
        /* 详见源代码 */
    }
}
class OrmstatsSectionHandler extends ArtObjecthandler {
    function OrmstatsSectionHandler($db) {
        $this->ArtObjectHandler($db, "ormstats_section",
            "OrmstatsSection", "id", "caption");
        return;
    }
}
?>

分页的逻辑都在index.php中处理,源代码如下:

/modules/ormstats/index.php
<?php
require_once dirname(__FILE__)."/../../mainfile.php";
include_once XOOPS_ROOT_PATH.'/class/pagenav.php';
$sectionhandler = xoops_getmodulehandler("section", "ormstats");
/* @var $sectionhandler OrmstatsSectionHandler */
$xoopsOption["template_main"] = "ormstats_index.html";
include XOOPS_ROOT_PATH."/header.php";
/* @var $xoopsTpl XoopsTpl */
if (true === isset($_GET["start"])) {
    $start = intval($_GET["start"]);
} else {
    $start = 0;
}
$limit = 3;
$total = intval($sectionhandler->getCount());
$pagenav = new XoopsPageNav($total, $limit, $start);
$xoopsTpl->assign_by_ref("pagenav", $pagenav->renderNav());
$xoopsTpl->assign_by_ref("pagenavimage", $pagenav->renderImageNav());
$xoopsTpl->assign_by_ref("pagenavselect", $pagenav->renderSelect());
$allsections = $sectionhandler->getByLimit($limit, $start);
$xoopsTpl->assign_by_ref("allsections", $allsections);
include XOOPS_ROOT_PATH."/footer.php";
?>

开发分页导航需要使用XoopsPageNav类,在使用前需要包含相应的头文件。

include_once XOOPS_ROOT_PATH.'/class/pagenav.php';

分页导航XoopsPageNav类的构造函数如下:

/class/pagenav.php
	function XoopsPageNav($total_items, $items_perpage, $current_start, $start_name="start", $extra_arg="") {}

其中$total_items参数是数据总数,$items_perpage参数是每页显示数据数,$current_start参数是当前显示的数据开始位置。这三个参数的来源各不相同,其中$total_items来自于数据库中保存的数据行数。$items_perpage来源于本页面对于显示数据数的约定。而$current_start则是访问者按分页导航时浏览到的页面。因此获取$current_start参数就需要先判断是否输入了当前的行数。

if (true === isset($_GET["start"])) {

如果输入了当前的行数,说明是通过分页导航浏览到的,就按该变量获取相应的行数。

    $start = intval($_GET["start"]);

如果没有输入当前的行数,说明是初次访问,则为行数设置初始值,该值通常是0。

    $start = 0;

这样就计算出了$current_start参数。

说明:用于传递当前行数的start,对应于XoopsPageNav类的构造函数的$start_name参数,如果页面中start参数有其他的含义,为避免冲突,需将$current_start参数设置为其他值。

由于分页导航是按照$total_items / $items_perpage计算页数,所以$items_perpage通常是固定值,本例中约定每页显示三行数据。

$limit = 3;

接下来就是获取$total_items参数的值。Frameworks库提供了getCount函数可以直接查询数据行数。

$total = intval($sectionhandler->getCount());

getCount函数是ArtObjectHandler类动态调用ArtObjectStatsHandler类的getCount函数实现的,定义如下:

/Frameworks/art/object.stats.php
    function getCount($criteria = null) {}

其中$criteria参数是查询条件,如果未设置该参数,则是查询表中所有的数据行数。为分页导航准备好参数后,就可以创建分页导航对象了。

$pagenav = new XoopsPageNav($total, $limit, $start);

分页导航提供三种效果:文本链接、图片链接和下拉列表框三种显示效果,每种显示效果都是通过函数输出HTML,直接赋值为模板变量即可。

$xoopsTpl->assign_by_ref("pagenav", $pagenav->renderNav());
$xoopsTpl->assign_by_ref("pagenavimage", $pagenav->renderImageNav());
$xoopsTpl->assign_by_ref("pagenavselect", $pagenav->renderSelect());

其中文本链接为renderNav函数,图片链接为renderImageNav函数,下拉列表框为renderSelect函数,这三个函数都是XoopsPageNav类的成员函数,定义如下:

/class/pagenav.php
	function renderNav($offset = 4) {}
	function renderImageNav($offset = 4) {}
	function renderSelect($showbutton = false) {}

设置了分页导航后,就可以按照上例中方法按起始行数和查询行数获取相应的对象数组了。

$allsections = $sectionhandler->getByLimit($limit, $start);

其中getByLimit函数的两个参数与XoopsPageNav类的构造函数的参数是相同的,结合getCount参数可以发现,借助于Frameworks库,可以直接支持分页导航。对应的模板中除了显示数据之外,只需要显示已设置分页导航变量即可。源代码如下:

/modules/ormstats/templates/ormstats_index.html
<{strip}>
    <{$pagenav}>
    <{$pagenavimage}>
    <{$pagenavselect}>
    <table>
        <!-- 详见源代码 -->
    </table>
<{/strip}>

效果如图2-9分页导航所示。

图2-9分页导航
Enlarge
图2-9分页导航

图中第一行是文字链接分页导航,第二行是图片链接分页导航,第三行是下拉列表框导航。如果页数较多,文字链接和图片链接有可能长度超过页面布局中预想的宽度,此时可以借助于renderNav函数和renderImageNav函数的offset参数控制显示的链接数量。下拉列表框导航是借助于JavaScript实现的,如果浏览器禁止了JavaScript,则需要设置renderSelect函数的$showbutton参数控制显示分页导航按钮,用户可以单击按钮来翻页。效果如图2-10分页导航的选项所示。

图2-10分页导航的选项
Enlarge
图2-10分页导航的选项

renderNav函数和renderImageNav函数的offset参数是指当前页的前后连续显示的页数,例如图中设置了$offset参数的值为4,当前页是第4页,所以在前面显示第1页、第2页和第3页,包括当前页共有4页。在后面显示第5页、第6页和第7页,包括当前页共有4页,由于总页数超过7页,所以显示省略号,并显示最后一页。renderSelect的$showbutton参数设置为true时,下拉列表框的右侧将显示“确定”按钮。

数据库安全

前面几个例子讲解了XOOPS系统以及Frameworks库对查询的支持,数据操作除了查询外,还包括增加、删除和修改等三种常用的操作。与查询相比,增加、删除和修改操作会修改数据库的内容,所以需要额外的安全检查,这些安全检查从以下三点考虑。

一,由于HTTP协议不保持TCP链接,因此这些安全检查应当针对用户采用浏览器操作时的情况,而不考虑采用特定工具模拟HTTP协议的情况。

二,参照W3C制订的标准,浏览器应当提供避免重复提交POST请求的机制,因此对于对于应当通过POST请求进行的增加、删除和修改操作,需要在非POST请求的情况下避免增加、删除和修改操作。

三,通过POST请求进行的增加、删除和修改操作,应当检查是否是通过本站提交的表单,避免处理站外提交表单的情况。

基于以上三点考虑,XOOPS系统提供了相应的数据库安全机制,接下来本例将讲解如何使用XOOPS系统提供的安全机制。本例的文件夹结构如下:

/modules/ormproxy
	/class
	/section.php
	/images
	/logo.png
	/sql
	/mysql.sql
	/templates
	/ormproxy_index.html
	/index.php
	/xoops_version.php

其中xoops_version.php的内容如下:

/modules/ormproxy/xoops_version.php
<?php
$modversion['name'] = "ORM对象关系映射 - 数据库安全";
$modversion['version'] = 0.01;
$modversion['description'] = "演示数据库的安全访问机制";
$modversion['author'] = <<<AUTHOR
胡争辉 QQ: 443089607 QQMail: [email protected] GTalk: huzhengh
GMail: [email protected] Skype: huzhenghui"
AUTHOR;
$modversion['credits'] = "";
$modversion['license'] = "版权所有";
$modversion['image'] = "images/logo.png";
$modversion['dirname'] = "ormproxy";
$modversion["hasMain"] = 1;
$modversion["sqlfile"]["mysql"] = "sql/mysql.sql";
$modversion["tables"][] = "ormproxy_section";
$modversion["templates"][0]["file"] = "ormproxy_index.html";
$modversion["templates"][0]["description"] = "Template for index.php";
?>

为便于对照学习,本例采用上例中的数据结构,但不包括数据,mysql.sql的内容如下:

/modules/ormproxy/sql/mysql.sql
CREATE TABLE `ormproxy_section` (
/* 详见源代码 */
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=1;

本例的数据对象和数据访问句柄也不需要额外的编码,与上例类似,section.php文件的源代码如下:

/modules/ormproxy/class/section.php
<?php
/* 详见源代码 */
class OrmproxySection extends ArtObject {
    function OrmproxySection () {
        $this->ArtObject("ormproxy_section");
        /* 详见源代码 */
    }
}
class OrmproxySectionHandler extends ArtObjectHandler {
    function OrmproxySectionHandler($db) {
        $this->ArtObjectHandler($db, "ormproxy_section",
            "OrmProxySection", "id", "caption");
        return;
    }
}
?>

在index.php中增加显示表单以及插入数据的处理过程,源代码如下:

/modules/ormproxy/index.php
<?php
require_once dirname(__FILE__)."/../../mainfile.php";
$myts = MyTextSanitizer::getInstance();
/* @var $myts MyTextSanitizer */
if (true === isset($_REQUEST["submit"])) {
    $xoopsDB->query("INSERT INTO ".$xoopsDB->prefix("ormproxy_section").
        " (caption) VALUES ('".$myts->addSlashes($_REQUEST["section"])."')");
}
$sectionhandler = xoops_getmodulehandler("section", "ormproxy");
/* @var $sectionhandler OrmproxySectionHandler */
$xoopsOption["template_main"] = "ormproxy_index.html";
include XOOPS_ROOT_PATH."/header.php";
/* @var $xoopsTpl XoopsTpl */
include_once XOOPS_ROOT_PATH."/class/xoopsformloader.php";
$section_form = new XoopsThemeForm("Create Section", "section_form", "");
$section_form->addElement(new XoopsFormText("Section:", "section", 20, 20));
$section_form->addElement(new XoopsFormButton("", "submit", _SUBMIT, "submit"));
$xoopsTpl->assign_by_ref("section_form", $section_form->render());
/* 详见源代码 */
include XOOPS_ROOT_PATH."/footer.php";
?>

检查是否提交了submit参数来判断是否是一个需要插入数据的请求。

if (true === isset($_REQUEST["submit"])) {

如果是一个插入数据的请求,则构造并执行插入数据的SQL语句。

    $xoopsDB->query("INSERT INTO ".$xoopsDB->prefix("ormproxy_section").
        " (caption) VALUES ('".$myts->addSlashes($_REQUEST["section"])."')");

提交数据的表单与前面讲解的表单类似,对应的模板需要增加显示表单的代码,源代码如下:

/modules/ormproxy/templates/ormproxy_index.html
<{strip}>
    <{$section_form}>
    <table>
        <!-- 详见源代码 -->
    </table>
<{/strip}>

效果如图2-11安全提交表单所示。

图2-11安全提交表单
Enlarge
图2-11安全提交表单

在Section文本框中输入文字,然后单击“提交”按钮就可以提交表单。提交的内容将显示在下侧的列表中。

本例从代码上看并没有特别的安全检查,这是因为XOOPS系统已封装必要的安全检查,从而提供透明的操作。如果把表单的方法设置为GET。

$section_form = new XoopsThemeForm("Create Section", "section_form", "", "GET");

访问时将看到同样的界面,界面的下侧也会显示已提交的数据列表,但是提交的数据却不会被加入到列表中。这就是因为XOOPS系统进行了相应的安全检查,限制了GET方法仅能执行查询数据的操作。类似的,如果表单来自于XOOPS系统之外的网页,也会被XOOPS系统限制仅能执行查询数据的操作。

在某些业务逻辑中,如果不希望被XOOPS系统的安全检查限制执行SQL语句,则可以用queryF函数替代query函数,例如本例中如果希望能用GET方法提交表单,则进行如下修改。

    $xoopsDB->queryF("INSERT INTO ".$xoopsDB->prefix("ormproxy_section").
        " (caption) VALUES ('".$myts->addSlashes($_REQUEST["section"])."')");

queryF函数会强制执行SQL语句,而不受XOOPS系统的安全检查限制。queryF函数的定义如下:

/class/database/mysqldatabase.php
    function queryF($sql, $limit=0, $start=0) {}

数据操作

上例讲解了如何基于XOOPS系统的安全机制编写向数据表插入数据的程序。基于XOOPS系统开发数据操作程序通过已封装的函数可以有这样一些便捷:

  1. query函数已封装了安全机制
  2. prefix函数为表名增加前缀
  3. addSlashes函数自动处理magic_quote_gpc选项

虽然通过这些封装函数已能大幅度提升开发效率,但仍有不尽如人意的地方:

  1. 需要事先考虑并选择query函数还是queryF函数
  2. SQL语句需要拼写
  3. 在XoopsObject对象中已保存有字段信息,但是SQL语句中还需要重复一遍

而在Frameworks库中,又进一步将这些函数封装成对象,可以直接以对象的方式操作数据。接下来本例将在上例的基础上,讲解基于Frameworks库如何开发增加、删除和修改数据记录程序。本例的文件夹结构如下:

/modules/ormwrite
	/class
	/section.php
	/images
	/logo.png
	/sql
	/mysql.sql
	/templates
	/ormwrite_index.html
	/create.php
	/delete.php
	/edit.php
	/index.php
	/insert.php
	/save.php
	/xoops_version.php

其中xoops_version.php的内容如下:

/modules/ormwrite/xoops_version.php
<?php
$modversion['name'] = "ORM对象关系映射 - 增加,删除和修改";
$modversion['version'] = 0.01;
$modversion['description'] = "演示数据库的增加,删除和修改";
$modversion['author'] = <<<AUTHOR
胡争辉 QQ: 443089607 QQMail: [email protected] GTalk: huzhengh
GMail: [email protected] Skype: huzhenghui"
AUTHOR;
$modversion['credits'] = "";
$modversion['license'] = "版权所有";
$modversion['image'] = "images/logo.png";
$modversion['dirname'] = "ormwrite";
$modversion["hasMain"] = 1;
$modversion["sub"][0]["name"] = "新增";
$modversion["sub"][0]["url"] = "create.php";
$modversion["sqlfile"]["mysql"] = "sql/mysql.sql";
$modversion["tables"][] = "ormwrite_section";
$modversion["templates"][0]["file"] = "ormwrite_index.html";
$modversion["templates"][0]["description"] = "Template for index.php";
?>

为便于对照学习,本例采用上例中的数据结构。mysql.sql的内容如下:

/modules/ormwrite/sql/mysql.sql
CREATE TABLE `ormwrite_section` (
/* 详见源代码 */
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=1;

本例的数据对象和数据访问句柄也不需要额外的编码,与上例类似,section.php文件的源代码如下:

/modules/ormwrite/class/section.php
<?php
/* 详见源代码 */
class OrmwriteSection extends ArtObject {
    function OrmwriteSection () {
        $this->ArtObject("ormwrite_section");
        /* 详见源代码 */
    }
}
class OrmwriteSectionHandler extends ArtObjectHandler {
    function OrmwriteSectionHandler($db) {
        $this->ArtObjectHandler($db, "ormwrite_section",
            "OrmWriteSection", "id", "caption");
        return;
    }
}
?>

在index.php中显示全部的数据项,和“数据查询”小节中的index.php类似,源代码如下:

/modules/ormwrite/index.php
<?php
require_once dirname(__FILE__)."/../../mainfile.php";
$sectionhandler = xoops_getmodulehandler("section", "ormwrite");
/* @var $sectionhandler OrmwriteSectionHandler */
$xoopsOption["template_main"] = "ormwrite_index.html";
/* 详见源代码 */
?>

对应的ormwrite_index.html模板源代码如下;

/modules/ormwrite/templates/ormwrite_index.html
<{strip}>
    <table>
        <tr>
            <!-- 详见源代码 -->
            <th>
                Delete
            </th>
        </tr>
	    <{foreach item="sectionitem" key="key" from=$allsections}>
	        <tr>
                <td>
                    <a href="edit.php?id=<{$key}>">
                        <{$key}>
                    </a>
                </td>
	            <td>
                    <a href="edit.php?id=<{$key}>">
                        <{$allsections.$key->getVar("caption")}>
                    </a>
	            </td>
	            <td>
                    <a href="delete.php?id=<{$key}>">
                        Delete
                    </a>
	            </td>
	        </tr>
	    <{/foreach}>
    </table>
<{/strip}>

在模板中为Key列和Caption列增加了链接到编辑页面的链接,然后增加了一列链接到删除页面。效果如图2-12数据操作图2-12数据操作--列表所示。

图2-12数据操作--列表
Enlarge
图2-12数据操作--列表

其中Key列和Caption列都有到编辑页面的链接,而Delete列有到删除页面的链接。

说明:从代码中可以看出,修改的范围都是在模板中,而没有修改index.php文件,这体现了XOOPS系统将逻辑与展现分离的优势。

本例中新增功能设计成了一个菜单项,单击该菜单项进入create.php页面,源代码如下:

/modules/ormwrite/create.php
<?php
require_once dirname(__FILE__)."/../../mainfile.php";
include XOOPS_ROOT_PATH."/header.php";
include_once XOOPS_ROOT_PATH."/class/xoopsformloader.php";
$create_form = new XoopsThemeForm("Create Section", "create_form", "insert.php");
$create_form->addElement(new XoopsFormText("Section", "section", 20, 20));
$create_form->addElement(new XoopsFormButton("", "submit", _SUBMIT, "submit"));
$create_form->display();
include XOOPS_ROOT_PATH."/footer.php";
?>

由于本页面仅显示一个表单,所以没有使用表单。页面的效果如图2-13数据操作--新增所示。

图2-13数据操作--新增
Enlarge
图2-13数据操作--新增

在Section文本框中输入要增加的名称,然后单击“提交”按钮,表单被将提交到insert.php页面。insert.php页面的源代码如下:

/modules/ormwrite/insert.php
<?php
require_once dirname(__FILE__)."/../../mainfile.php";
$sectionhandler = xoops_getmodulehandler("section", "ormwrite");
/* @var $sectionhandler OrmwriteSectionHandler */
$section = $sectionhandler->create();
$section->setVar("caption", $_POST["section"]);
$sectionhandler->insert($section);
redirect_header("index.php");
?>

在创建了数据操作句柄之后,调用create函数创建数据对象。

$section = $sectionhandler->create();

其中create函数定义在_XoopsPersistableObjectHandler类中,用于创建一个新的数据对象。

/Frameworks/art/object.persistable.php
    function &create($isNew = true) {}

然后调用setVar函数设置caption字段的值。

$section->setVar("caption", $_POST["section"]);

setVar函数定义在XoopsObject类中,用于设置数据对象的字段值。

/Kernel/object.php
    function setVar($key, $value, $not_gpc = false) {}

其中$key参数是字段的名称,$value参数是字段的值。接下来调用insert函数保存数据对象。

$sectionhandler->insert($section);

insert函数是ArtObjectHandler类动态调用ArtObjectWriteHandler类的insert函数。定义如下:


/Frameworks/art/object.write.php
    function insert(&$object, $force = true) {}

其中$object参数是数据对象,$force参数用于处理数据库安全策略,默认值是true,表示调用queryF函数,如果设置为false,则调用query,可以借助XOOPS系统自身的数据安全策略。通过以上三个步骤就完成了上例中向数据库插入数据的过程。下面对照讲解。

一,在setVar函数中会自动处理magic_quote_gpc,因此不需要像上例调用$myts->addSlashes($_REQUEST["section"]。

二,数据调用句柄的构造函数中包含了表名,所以不需要像上例中调用$xoopsDB->prefix("ormproxy_section")。

三,insert函数中自动构造SQL语句并执行query函数,所以不需手工构造SQL语句及调用query函数。

构造的SQL语句如下所示。

INSERT INTO xoops_ormwrite_section (caption) VALUES ('xoops_version.php');

插入数据后调用redirect_header函数重定向到列表页。就可以在列表页上看到刚插入的数据了。

在列表页面的模板中,为Key列和Caption列都增加了到edit.php页面的链接,增加了数据项之后,就可以在列表页单击这些链接进入到edit.php页面。源代码如下:

/modules/ormwrite/edit.php
<?php
require_once dirname(__FILE__)."/../../mainfile.php";
$myts = MyTextSanitizer::getInstance();
/* @var $myts MyTextSanitizer */
$sectionhandler = xoops_getmodulehandler("section", "ormwrite");
/* @var $sectionhandler OrmwriteSectionHandler */
if (true === isset($_GET["id"])) {
    $section = $sectionhandler->get($myts->stripSlashesGPC($_GET["id"]));
}
/* @var $section OrmwriteSection */
if (false === isset($section)) {
    redirect_header("index.php");
}
include XOOPS_ROOT_PATH."/header.php";
include_once XOOPS_ROOT_PATH."/class/xoopsformloader.php";
$edit_form = new XoopsThemeForm("Edit Section", "edit_form", "save.php");
$edit_form->addElement(new XoopsFormHidden("id", $section->getVar("id")));
$edit_form->addElement(new XoopsFormLabel("Id", $section->getVar("id")));
$edit_form->addElement(new XoopsFormText("Section", "section", 20, 20,
    $section->getVar("caption")));
$edit_form->addElement(new XoopsFormButton("", "submit", _SUBMIT, "submit"));
$edit_form->display();
include XOOPS_ROOT_PATH."/footer.php";
?>

编辑页面中首先需要判断是否设置了待编辑的数据的id,然后调用数据访问句柄的get函数获取数据对象。get函数定义在_XoopsPersistableObjectHandler类中。

/Frameworks/art/object.persistable.php
    function &get($id = null, $tags = null) {}

其中$id参数是主键的值,get函数将按传入的参数作为主键的键值查询数据库并返回数据对象。Frameworks库将自动完成对值转义、构造SQL语句、执行和转换成数据对象的工作。例如本例中id是1时,将得到如下的SQL语句。

SELECT * FROM xoops_ormwrite_section WHERE id = '1';

如果数据库中没有相应的记录,则get函数将返回NULL,此时应当跳转到列表页面。如果返回数据对象,则显示页面并显示表单。由于该对象已有主键,所以应当在表单中包含主键,而且该主键不允许被用户直接编辑,通常采用隐藏域。

$edit_form->addElement(new XoopsFormHidden("id", $section->getVar("id")));
<?pre>

XoopsFormHidden是隐藏域类,定义在formhidden.php中。

<pre>
/class/xoopsform/formhidden.php
class XoopsFormHidden extends XoopsFormElement {}

XoopsFormHidden类继承自XoopsFormElement类,XoopsFormHidden类的构造函数如下:

/class/xoopsform/formhidden.php
	function XoopsFormHidden($name, $value) {}

其中$name参数是隐藏域的名称,$value参数是隐藏域的值。表单通过隐藏域就维护了数据对象的主键,另一方面,为便于用户识别,也应当显示该主键。

$edit_form->addElement(new XoopsFormLabel("Id", $section->getVar("id")));

XoopsFormLabel是表单标签类,定义在formlabel.php中。

/class/xoopsform/formlabel.php
class XoopsFormLabel extends XoopsFormElement {}

XoopsFormLabel类继承自XoopsFormElement类,XoopsFormHidden类的构造函数如下:

/class/xoopsform/formlabel.php
	function XoopsFormLabel($caption="", $value="", $name=""){}

其中$caption参数是标签的名称,$value参数是标签的内容。表单中通过标签可以显示提示信息,与前面讲解的XoopsFormText类、XoopsFormButton类、XoopsFormHidden类不同的是XoopsFormLabel类并不实际对应于HTML中的表单元素。而只是普通的页面元素,这体现了XOOPS系统的面向对象的便捷之处。显示的页面如图2-14数据编辑--编辑所示。

图2-14数据编辑--编辑
Enlarge
图2-14数据编辑--编辑

编辑内容后,单击“提交”按钮,数据将提交到save.php页面中。

/modules/ormwrite/save.php
<?php
require_once dirname(__FILE__)."/../../mainfile.php";
$myts = MyTextSanitizer::getInstance();
/* @var $myts MyTextSanitizer */
$sectionhandler = xoops_getmodulehandler("section", "ormwrite");
/* @var $sectionhandler OrmwriteSectionHandler */
if (true === isset($_POST["id"])) {
    $section = $sectionhandler->get($myts->stripSlashesGPC($_POST["id"]));
}
/* @var $section OrmwriteSection */
if (false === isset($section)) {
    redirect_header("index.php");
}
$section->setVar("caption", $_POST["section"]);
$sectionhandler->insert($section);
redirect_header("index.php");
?>

save.php页面的功能类似于将edit.php和insert.php两个页面组合起来。前半部分像edit.php页面一样,根据输入的id获取数据对象。后半部分和insert.php页面一样,将输入的参数设置给数据对象,然后用数据访问句柄更新。生成的SQL语句如下所示。

UPDATE xoops_ormwrite_section SET caption = 'xoops_version.php' WHERE id = '1';

值得注意的是,对于新对象和已存在的对象,都是调用insert函数,开发人员不需要进行额外的判断,Frameworks库中已封装好相应的判断了。

说明:本例为了便于演示,所以将新建数据和更新数据分开编辑,在实际开发中,借助于Frameworks库的封装,可以将新建数据和更新数据两个过程合并在一起。请读者自行练习。

更新完成后调用redirect_header函数跳转到列表页面,可以看到更新后的内容。在列表页面的Delete列提供了到删除页面的链接。删除页面的源代码如下:

/modules/ormwrite/delete.php
<?php
require_once dirname(__FILE__)."/../../mainfile.php";
$myts = MyTextSanitizer::getInstance();
/* @var $myts MyTextSanitizer */
$sectionhandler = xoops_getmodulehandler("section", "ormwrite");
/* @var $sectionhandler OrmwriteSectionHandler */
if (true === isset($_GET["id"])) {
    $section = $sectionhandler->get($myts->stripSlashesGPC($_GET["id"]));
}
/* @var $section OrmwriteSection */
if (false === isset($section)) {
    redirect_header("index.php");
}
$sectionhandler->delete($section, true);
redirect_header("index.php");
?>

删除页面的前半部分与编辑页面和保存页面类似,都是依据输入参数选择出相应的数据对象,并判断数据对象是否存在。然后调用delete函数删除数据对象。

$sectionhandler->delete($section, true);

delete函数是ArtObjectHandler类动态调用ArtObjectWriteHandler类的delete函数,定义如下:

/Frameworks/art/object.write.php
    function delete(&$obj, $force = false) {}

其中$object参数是数据对象,$force参数用于处理数据库安全策略,默认值是true,表示调用queryF函数,如果设置为false,则调用query,可以借助XOOPS系统自身的数据安全策略。如果传入的id值为1,则构造SQL语句如下:

DELETE FROM xoops_ormwrite_section WHERE id = '1';

本例中$force参数没有使用默认值false,而是设置为true。这是因为从列表页面到删除页面使用链接,而链接是GET请求,所以设置$force参数的值为true来强制执行。在删除后重定向列表页面,可以看到该数据项已被删除。

模拟外键

在MySQL的数据库中,当使用REFERENCES tbl_name(col_name)子句定义列时可以使用外部关键字,但是仅有InnoDB类型的表有实际的处理,只作为备忘录或注释来提醒,目前正定义的列指向另一个表中的一个列,适用于表MyISAM和BerkeleyDB。Frameworks库提供了模拟外键的功能,虽然与InnoDB在数据库引擎中实现外键有一定差距,但是功能上要优于MyISAM或BerkeleyDB,本例将演示Frameworks库模拟外键的功能,文件夹结构如下:

/modules/ormjoint
	/class
	/chapter.php
	/section.php
	/images
	/logo.png
	/sql
	/mysql.sql
	/templates
	/ormjoint_chapter.html
	/ormjoint_index.html
	/chapter.php
	/index.php
	/xoops_version.php

其中xoops_version.php的内容如下;

/modules/ormjoint/xoops_version.php
<?php
$modversion['name'] = "ORM对象关系映射 - 模拟外键";
$modversion['version'] = 0.01;
$modversion['description'] = "演示模拟数据库外键的查询";
$modversion['author'] = <<<AUTHOR
胡争辉 QQ: 443089607 QQMail: [email protected] GTalk: huzhengh
GMail: [email protected] Skype: huzhenghui"
AUTHOR;
$modversion['credits'] = "";
$modversion['license'] = "版权所有";
$modversion['image'] = "images/logo.png";
$modversion['dirname'] = "ormjoint";
$modversion["hasMain"] = 1;
$modversion["sqlfile"]["mysql"] = "sql/mysql.sql";
$modversion["tables"][] = "ormjoint_chapter";
$modversion["tables"][] = "ormjoint_section";
$modversion["templates"][0]["file"] = "ormjoint_index.html";
$modversion["templates"][0]["description"] = "Template for index.php";
$modversion["templates"][1]["file"] = "ormjoint_chapter.html";
$modversion["templates"][1]["description"] = "Template for chapter.php";
?>
由于外键涉及两个数据表,所以本例的数据结构包括两个数据表。mysql.sql的内容如下:
/modules/ormjoint/sql/mysql.sql
CREATE TABLE `ormjoint_chapter` (
  `id` int(11) NOT NULL auto_increment,
  `caption` text NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=3 ;
INSERT INTO `ormjoint_chapter` (`id`, `caption`) VALUES (1, 'XOOPS模块开发');
INSERT INTO `ormjoint_chapter` (`id`, `caption`) VALUES (2, 'ORM对象关系映射');
CREATE TABLE `ormjoint_section` (
  `id` int(11) NOT NULL auto_increment,
  `chapter_id` int(11) NOT NULL,
  `section_order` int(11) NOT NULL,
  `caption` text NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=18 ;
INSERT INTO `ormjoint_section` (`id`, `chapter_id`, `section_order`, `caption`) VALUES (1, 1, 1, 'xoops_version.php');
INSERT INTO `ormjoint_section` (`id`, `chapter_id`, `section_order`, `caption`) VALUES (2, 1, 2, '模块普通页面');
INSERT INTO `ormjoint_section` (`id`, `chapter_id`, `section_order`, `caption`) VALUES (3, 1, 3, '模块管理页面');
INSERT INTO `ormjoint_section` (`id`, `chapter_id`, `section_order`, `caption`) VALUES (4, 1, 4, '数据表');
INSERT INTO `ormjoint_section` (`id`, `chapter_id`, `section_order`, `caption`) VALUES (5, 1, 5, '数据对象');
INSERT INTO `ormjoint_section` (`id`, `chapter_id`, `section_order`, `caption`) VALUES (6, 1, 6, '条件对象');
INSERT INTO `ormjoint_section` (`id`, `chapter_id`, `section_order`, `caption`) VALUES (7, 1, 7, '表单对象');
INSERT INTO `ormjoint_section` (`id`, `chapter_id`, `section_order`, `caption`) VALUES (8, 1, 8, '模板');
INSERT INTO `ormjoint_section` (`id`, `chapter_id`, `section_order`, `caption`) VALUES (9, 1, 9, '区块');
INSERT INTO `ormjoint_section` (`id`, `chapter_id`, `section_order`, `caption`) VALUES (10, 2, 1, '使用Frameworks库');
INSERT INTO `ormjoint_section` (`id`, `chapter_id`, `section_order`, `caption`) VALUES (11, 2, 2, '条件组合');
INSERT INTO `ormjoint_section` (`id`, `chapter_id`, `section_order`, `caption`) VALUES (12, 2, 3, '处理转义');
INSERT INTO `ormjoint_section` (`id`, `chapter_id`, `section_order`, `caption`) VALUES (13, 2, 4, '数据查询');
INSERT INTO `ormjoint_section` (`id`, `chapter_id`, `section_order`, `caption`) VALUES (14, 2, 5, '分页');
INSERT INTO `ormjoint_section` (`id`, `chapter_id`, `section_order`, `caption`) VALUES (15, 2, 6, '数据库安全');
INSERT INTO `ormjoint_section` (`id`, `chapter_id`, `section_order`, `caption`) VALUES (16, 2, 7, '数据操作');
INSERT INTO `ormjoint_section` (`id`, `chapter_id`, `section_order`, `caption`) VALUES (17, 2, 8, '模拟外键');

相应的本例中有两个数据对象和数据访问句柄,对应于ormjoint_chapter数据表的数据对象和数据访问句柄源代码如下:

/modules/ormjoint/class/chapter.php
<?php
/* 详见源代码 */
class OrmjointChapter extends ArtObject {
    function OrmjointChapter () {
        $this->ArtObject("ormjoint_chapter");
        $this->initVar("id", XOBJ_DTYPE_INT);
        $this->initVar("caption", XOBJ_DTYPE_TXTBOX);
        return;
    }
}
class OrmjointChapterHandler extends ArtObjecthandler {
    function OrmjointChapterHandler($db) {
        $this->ArtObjectHandler($db, "ormjoint_chapter",
            "OrmjointChapter", "id", "caption");
        return;
    }
}
?>

结构和前面类似,而对应于ormjoint_section数据表的数据对象和数据访问句柄则略有差异,源代码如下:

/modules/ormjoint/class/section.php
<?php
/* 详见源代码 */
class OrmjointSection extends ArtObject {
    function OrmjointSection () {
        $this->ArtObject("ormjoint_section");
        $this->initVar("id", XOBJ_DTYPE_INT);
        $this->initVar("chapter_id", XOBJ_DTYPE_INT);
        $this->initVar("section_order", XOBJ_DTYPE_INT);
        $this->initVar("caption", XOBJ_DTYPE_TXTBOX);
        return;
    }
}
class OrmjointSectionHandler extends ArtObjecthandler {
    function OrmjointSectionHandler($db) {
        $this->ArtObjectHandler($db, "ormjoint_section",
            "OrmjointSection", "id", "caption");
        $chapterhandler = xoops_getmodulehandler("chapter", "ormjoint");
        /* @var $chapterhandler OrmjointChapterHandler */
        $this->field_link = $chapterhandler->keyName;
        $this->field_object = "chapter_id";
        $this->keyName_link = $chapterhandler->keyName;
        $this->table_link = $chapterhandler->table;
        return;
    }
}
?>

由于ormjoint_chapter表的主键是ormjoint_section表的外键,所以在数据访问句柄的构造函数中先创建ormjoint_chapter表的数据访问句柄。

        $chapterhandler = xoops_getmodulehandler("chapter", "ormjoint");

然后设置field_link成员变量为父表的主键字段名。

        $this->field_link = $chapterhandler->keyName;

下面设置field_object成员变量为子表的外键字段名。

        $this->field_object = "chapter_id";

接下来设置keyName_link成员变量为父表用于分组的字段名。

        $this->keyName_link = $chapterhandler->keyName;

与外键相关的最后一个设置是table_link设置为父表的数据表表名。

        $this->table_link = $chapterhandler->table;

通过这四项设置就完成了外键相关的设置,Frameworks库中与外键相关的功能都会调用相应的设置,而不需要为每个功能单独设置,因此本例中外键在数据访问句柄中直接设置。下面首先演示如何借助Frameworks库查询父对象对应的子对象的数目,index.php的源代码如下:

/modules/ormjoint/index.php
<?php
require_once dirname(__FILE__)."/../../mainfile.php";
$chapterhandler = xoops_getmodulehandler("chapter", "ormjoint");
/* @var $chapterhandler OrmjointChapterHandler */
$sectionhandler = xoops_getmodulehandler("section", "ormjoint");
/* @var $sectionhandler OrmjointSectionHandler */
$xoopsOption["template_main"] = "ormjoint_index.html";
include XOOPS_ROOT_PATH."/header.php";
$xoopsTpl->assign_by_ref("chapters", $chapterhandler->getObjects(null, true));
$xoopsTpl->assign_by_ref("section_counts", $sectionhandler->getCountsByLink());
include XOOPS_ROOT_PATH."/footer.php";
?>

在创建两个数据访问句柄后,获取所有的父对象并设置到模板中。

$xoopsTpl->assign_by_ref("chapters", $chapterhandler->getObjects(null, true));

然后调用子对象数据访问句柄的getCountsByLink函数,按外键来对子对象中的数据分组统计。

$xoopsTpl->assign_by_ref("section_counts", $sectionhandler->getCountsByLink());

getCountsByLink函数是ArtObjectHandler类中动态调用ArtObjectJointHandler类的getCountsByLink函数,定义如下:

/Frameworks/art/object.joint.php
   	function getCountsByLink($criteria = null) {}

从页面的代码中会发现,两个数据访问句柄并没有直接互操作,这是因为在子表的数据访问句柄中定义了与父表的关系,所以不再需要额外的声明。本例中生成的SQL语句如下:

SELECT l.id, COUNT(*) FROM xoops_ormjoint_section AS o LEFT JOIN xoops_ormjoint_chapter AS l ON o.chapter_id = l.id GROUP BY l.id;

该SQL语句就是依赖于与外键相关的几个成员变量实现的,在Frameworks库模拟外键时,子表的表名用o代表,父表的表名用l代表。相应的数据在模板中组合即可,对应模板的源代码如下:

/modules/ormjoint/templates/ormjoint_index.html
<{strip}>
    <{foreach from=$chapters key="key" item="chapter"}>
        <div>
            <a href="chapter.php?id=<{$key}>">
	            <{$key}> 
	            <{$chapters.$key->getVar("caption")}> (
	            <{$section_counts.$key}>)
            </a>
        </div>
    <{/foreach}>
<{/strip}>

在模板中,首先对$chapters数组循环,显示主键的值。

	            <{$key}> 

然后显示每个对象的caption字段的值。

	            <{$chapters.$key->getVar("caption")}> (

接下来显示父对象对应于子对象的数量。

	            <{$section_counts.$key}>)

这里直接从getCountsByLink函数的返回值,按父对象的主键的值就获得了子对象的数量。效果如图2-15分组计算数量所示。

图2-15分组计算数量
Enlarge
图2-15分组计算数量

从图中可以看到每个父对象显示相应的子对象数量。本例为每个父对象都设置了链接到子对象列表页面的链接。

            <a href="chapter.php?id=<{$key}>">

链接到的chapter.php页面源代码如下。

/modules/ormjoint/hapter.php
<?php
require_once dirname(__FILE__)."/../../mainfile.php";
$myts = MyTextSanitizer::getInstance();
/* @var $myts MyTextSanitizer */
$chapterhandler = xoops_getmodulehandler("chapter", "ormjoint");
/* @var $chapterhandler OrmjointChapterHandler */
if (true === isset($_GET["id"])) {
    $chapter = $chapterhandler->get($myts->stripSlashesGPC($_GET["id"]));
    /* @var $chapter OrmjointChapter */
}
if (false === isset($chapter)) {
    redirect_header("index.php");
}
$sectionhandler = xoops_getmodulehandler("section", "ormjoint");
/* @var $sectionhandler OrmjointSectionHandler */
$xoopsOption["template_main"] = "ormjoint_chapter.html";
include XOOPS_ROOT_PATH."/header.php";
$xoopsTpl->assign_by_ref("chapter", $chapter);
$criteria = new Criteria("chapter_id", $chapter->getVar("id"));
$criteria->setSort("section_order");
$criteria->setOrder("ASC");
$sections = $sectionhandler->getByLink($criteria,
    array("o.caption", "o.section_order"));
$xoopsTpl->assign_by_ref("sections", $sections);
include XOOPS_ROOT_PATH."/footer.php";
?>

在页面中首先按照传入的id参数获取相应的$chapter对象,如果没有该对象则跳转到列表页面,这段逻辑与前例相同。如果有该对象,则把$chapter传入到模板中。

$xoopsTpl->assign_by_ref("chapter", $chapter);

接下来在ormjoint_section数据表中查询与$chapter对象对应的数据,首先创建查询对象。

$criteria = new Criteria("chapter_id", $chapter->getVar("id"));

然后设置排序的字段。

$criteria->setSort("section_order");

下面设置排序方式为顺序。

$criteria->setOrder("ASC");

构造条件对象后就可以调用数据访问句柄的getByLink函数查询。

$sections = $sectionhandler->getByLink($criteria, array("o.caption", "o.section_order"));

其中getByLink函数是ArtObjectHandler类中动态调用ArtObjectJointHandler类的getByLink函数,定义如下:

/Frameworks/art/object.joint.php
function &getByLink($criteria = null, $tags = null, $asObject = true, $field_link = null, $field_object = null) {}

其中$criteria参数是查询条件对象,$tags参数是期望查询的字段数组。本例中使用o.caption和o.section_order代表子表中的caption字段和section_order字段。例如传入参数id的值为1时,生成的SQL语句如下:

SELECT o.caption,o.section_order,o.id FROM xoops_ormjoint_section AS o LEFT JOIN xoops_ormjoint_chapter AS l ON o.chapter_id = l.id WHERE chapter_id = '1' ORDER BY section_order ASC;

查询的结果直接设置到模板中。

$xoopsTpl->assign_by_ref("sections", $sections);

对应的模板源代码如下:

/modules/ormjoint/templates/ormjoint_chapter.html
<{strip}>
    <fieldset>
	    <legend>
	        <{$chapter->getVar("id")}> 
	        <{$chapter->getVar("caption")}>
	    </legend>
	    <{foreach from=$sections item="section" key="key"}>
            <div>
                <{$chapter->getVar("id")}>.
                <{$section->getVar("section_order")}> 
                <{$section->getVar("caption")}>
            </div>
	    <{/foreach}>
    </fieldset>
<{/strip}>

模板中首先显示父对象,然后循环显示查询出的子对象数组,效果如图2-16查询子对象所示。

图2-16查询子对象
Enlarge
图2-16查询子对象

缓存

上例讲解了如何借助于Frameworks库模拟外键,模拟外键的优势是直接把数据库中的父表和子表转化成父对象和子对象,缺点是父对象和子对象要分别查询,如果层次关系比较复杂,则查询次数也将随之上升,这将直接影响网页的性能,此时就可以借助于Frameworks库的缓存机制将数据保存成为硬盘文件,直接从硬盘文件读取数据将提高网页的性能,降低数据库的压力,本例将演示如何使用Frameworks库的缓存函数库,文件夹结构如下;

/modules/ormcache
	/class
	/section.php
	/images
	/logo.png
	/sql
	/mysql.sql
	/templates
	/ormcache_index.html
	/index.php
	/xoops_version.php

其中xoops_version.php的内容如下:

/modules/ormcache/xoops_version.php
<?php
$modversion['name'] = "ORM对象关系映射 - 缓存";
$modversion['version'] = 0.01;
$modversion['description'] = "演示文件缓存数据库中的数据";
$modversion['author'] = <<<AUTHOR
胡争辉 QQ: 443089607 QQMail: [email protected] GTalk: huzhengh
GMail: [email protected] Skype: huzhenghui"
AUTHOR;
$modversion['credits'] = "";
$modversion['license'] = "版权所有";
$modversion['image'] = "images/logo.png";
$modversion['dirname'] = "ormcache";
$modversion["hasMain"] = 1;
$modversion["sqlfile"]["mysql"] = "sql/mysql.sql";
$modversion["tables"][] = "ormcache_section";
$modversion["templates"][0]["file"] = "ormcache_index.html";
$modversion["templates"][0]["description"] = "Template for index.php";
?>

本例数据结构与“分页”小节类似,mysql.sql的源代码如下:

/modules/ormcache/sql/mysql.sql
CREATE TABLE `ormcache_section` (
/* 详见源代码 */
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=10 ;
-- 详见源代码

对应的数据对象和数据访问句柄源代码如下:

/modules/ormcache/class/section.php
<?php
/* 详见源代码 */
class OrmcacheSection extends ArtObject {
    function OrmcacheSection () {
        $this->ArtObject("ormcache_section");
        /* 详见源代码 */
    }
}
class OrmcacheSectionHandler extends ArtObjecthandler {
    function OrmcacheSectionHandler($db) {
        $this->ArtObjectHandler($db, "ormcache_section",
            "OrmcacheSection", "id", "caption");
        return;
    }
}
?>

可以看出代码中并没有相应的缓存处理代码,这是因为缓存依赖于具体的业务逻辑,本例中在页面处理缓存的逻辑,源代码如下:

/modules/ormcache/index.php
<?php
require_once dirname(__FILE__)."/../../mainfile.php";
$sectionhandler = xoops_getmodulehandler("section", "ormcache");
/* @var $sectionhandler OrmcacheSectionHandler */
load_functions("cache");
$sectionscache = mod_loadCacheFile("sectionscache");
if (false === is_array($sectionscache)) {
    $criteria = new CriteriaCompo();
    $criteria->setSort("id");
    $criteria->setOrder("ASC");
    $sectionscache = $sectionhandler->getAll($criteria, null, false);
    mod_createCacheFile($sectionscache, "sectionscache");
}
foreach ($sectionscache as $key => $value) {
    $section = $sectionhandler->create(false);
    /* @var $section OrmcacheSection */
    $section->assignVars($value);
    $allsections[$key] = $section;
    unset($section);
}
$xoopsOption["template_main"] = "ormcache_index.html";
include XOOPS_ROOT_PATH."/header.php";
$xoopsTpl->assign_by_ref("allsections", $allsections);
include XOOPS_ROOT_PATH."/footer.php";
?>

为了使用Frameworks库中的缓存函数,首先需要加载缓存函数。

load_functions("cache");

load_functions函数是Frameworks库提供用于动态加载函数,定义在Frameworks库的初始化文件中。

/Frameworks/art/functions.ini.php
function load_functions($group = "", $dirname = "art") {}

其中参数$group是需要加载的函数组的名称,例如本例使用缓存函数,所以值为cache,加载了函数之后,就尝试加载缓存数据。

$sectionscache = mod_loadCacheFile("sectionscache");

mod_loadCacheFile函数用于加载保存在硬盘上的缓存数据。定义如下:

/Frameworks/art/functions.cache.php
function &mod_loadCacheFile($name, $dirname = null) {}

其中参数$name是缓存的名称,加载缓存数据时需要和保存缓存数据时使用相同的名称,本例中使用的名称为sectionscache,然后判断缓存数据是否有效。

if (false === is_array($sectionscache)) {

如果缓存数据无效,则构造查询条件,然后查询数据。

    $sectionscache = $sectionhandler->getAll($criteria, null, false);

getAll函数的第三个参数代表函数的返回值,为true时,返回值为数据对象数组,为false时,返回值为数据记录值数组,数据记录值数组便于缓存,接下来就缓存所缓取的数据。

    mod_createCacheFile($sectionscache, "sectionscache");

mod_createCacheFile函数用于将数据保存到硬盘上,定义如下:

/Frameworks/art/functions.cache.php
function mod_createCacheFile($data, $name = null, $dirname = null) {}

其中参数$data是需要缓存的数据,参数$name是缓存的名称,这个名称对应于mod_loadCacheFile的参数$name,再次访问页面时就会直接调用缓存中的数据,而不是访问数据库,生存的数据缓存文件如下所示:

/cache/ormcache_sectionscache.php
<?php
return array (
  1 => 
  array (
    'id' => '1',
    'caption' => 'xoops_version.php',
  ),
  2 => 
  array (
    'id' => '2',
    'caption' => '模块普通页面',
  ),
  3 => 
  array (
    'id' => '3',
    'caption' => '模块管理页面',
  ),
  4 => 
  array (
    'id' => '4',
    'caption' => '数据表',
  ),
  5 => 
  array (
    'id' => '5',
    'caption' => '数据对象',
  ),
  6 => 
  array (
    'id' => '6',
    'caption' => '条件对象',
  ),
  7 => 
  array (
    'id' => '7',
    'caption' => '表单对象',
  ),
  8 => 
  array (
    'id' => '8',
    'caption' => '模板',
  ),
  9 => 
  array (
    'id' => '9',
    'caption' => '区块',
  ),
);
?>

在处理数据缓存之后,接下来将数据转换为对象数据,遍历数组中所有的元素,每个元素都对应于一条数据记录。

foreach ($sectionscache as $key => $value) {

对于每条数据记录创建一个空数据对象。

    $section = $sectionhandler->create(false);

create函数的参数$isNew默认值是true,代表创建一个新对象,如果设置为false,则创建一个空数据对象,以便于填充数据。

    $section->assignVars($value);

assignVars函数定义在XoopsObject类中。

/kernel/object.php
    function assignVars($var_arr) {}

参数$var_arr是一个数组,数组的键名是数据的字段名,键值是数据的字段值。填充数据后组成对象数组。

    $allsections[$key] = $section;

由于PHP采用引用处理对象赋值,所以最后应销毁对象引用。

    unset($section);

这样就完成了从数组数据到对象数组的转换。用于显示的模板和“数据查询”小节相似。

/modules/ormcache/templates/ormcache_index.html
<{strip}>
    <table>
        <!-- 详见源代码 -->
    </table>
<{/strip}>

效果也一样如图 2 5 getObjects效果所示,从此可以看出,实现缓存仅影响页面,既不影响数据对象和数据访问句柄,也不影响模板,仅需要在页面中处理。

来自"http://xoops.org.cn/modules/mediawiki/index.php/ORM%E5%AF%B9%E8%B1%A1%E5%85%B3%E7%B3%BB%E6%98%A0%E5%B0%84"

浏览统计:4,912 次。 最后修订:02:10 2009年4月19日 © XOOPS China.