Xoops ORM对象关系映射2

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

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

数据库安全[ ]

前面几个例子讲解了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: hu_zhenghui@qq.com GTalk: huzhengh
GMail: huzhengh@gmail.com 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安全提交表单所示。

Xop98.png

在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: hu_zhenghui@qq.com GTalk: huzhengh
GMail: huzhengh@gmail.com 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数据操作--列表所示。

Xop99.png

其中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数据操作--新增所示。

Xop100.png

在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")));

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

/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数据编辑--编辑所示。

Xop101.png

编辑内容后,单击“提交”按钮,数据将提交到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: hu_zhenghui@qq.com GTalk: huzhengh
GMail: huzhengh@gmail.com 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分组计算数量所示。

Xop102.png

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

<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查询子对象所示。

Xop103.png


缓存[ ]

上例讲解了如何借助于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: hu_zhenghui@qq.com GTalk: huzhengh
GMail: huzhengh@gmail.com 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效果所示,从此可以看出,实现缓存仅影响页面,既不影响数据对象和数据访问句柄,也不影响模板,仅需要在页面中处理。