Xoops ORM对象关系映射1

来自站长百科
跳转至: 导航、​ 搜索

导航: 上一页 | 首页 | DedeCMS | 帝国CMS | Drupal | PHPCMS | PHP168 | Joomla | PowerEasy | SupeSite

使用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: hu_zhenghui@qq.com GTalk: huzhengh
GMail: huzhengh@gmail.com 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', 'hu_zhenghui@qq.com', 'huzhengh', 'huzhengh@gmail.com', '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: hu_zhenghui@qq.com GTalk: huzhengh
GMail: huzhengh@gmail.com 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条件组合全部对象所示。

Xop88.png

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

Xop89.png

通过本例可以看出借助于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: hu_zhenghui@qq.com GTalk: huzhengh
GMail: huzhengh@gmail.com 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处理转义全部结果所示。

Xop90.png

本例中将参数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处理转义查询结果所示。

Xop91.png

如果没有采用正确的转义方式,不仅有可能无法正确的得到查询结果,而且可能遇到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: hu_zhenghui@qq.com GTalk: huzhengh
GMail: huzhengh@gmail.com 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效果所示。

Xop92.png

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效果所示。

Xop93.png

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效果所示。

Xop94.png

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效果所示。

Xop95.png


分页[ ]

在上例的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: hu_zhenghui@qq.com GTalk: huzhengh
GMail: huzhengh@gmail.com 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分页导航所示。

Xop96.png

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

Xop97.png

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