编写你的业务逻辑与实现逻辑验证 (phpsa系列教程之三)
大师兄(teacherli(-at-)gmail.com) 2005-12-18
上一次我们把在线报名的界面做完了, 而实际的功能我们留下没讲, 这一讲我们来看看如何在doAction()方法中调用业务逻辑类来完成我们的用户在线报名注册.
PHPSA中以模块做为编程单位, 每一模块中又可以按照层次可分为forms, actions, model, views, 分别代表显示层, 控制层与逻辑层, 从文件夹的名称上, 我们可以看了在PHPSA中推荐在每一模块中只实现一个业务逻辑类, 通过这一个公用的业务逻辑类, 完成模块中所有Action的相关逻辑操作.
首先来看看这个例子中我们主要用到的数据表定义:
class SignupBusinessService extends BaseBusinessService {...}.
来看看signupBusinessService.class.php文件的代码:
回过头来看看我们的signupAction.class.php文件中的doView()函数, doView()函数用来显示一个供用户填写信息的界面, 在这个界面中,需要从数据库里将所有专业提取出来后供用户进行选择, 由数据库里提取专业列表就由此SignupBusinessService类的getSpecList()来完成, 看我们修改后的doView()函数:
来看我们的SignupBusinessService类的doAction()方法, 当用户提交表单后经过SignupForm类验证无误后交由此方法进行执行具体的入库操作, 现在我们又有一个要求:要求用户提供的注册信息中的身份证号码不能重复, 也就是一个用户不可进行多次报名, 很显然, 这要涉及到操作数据库, 在Form中的字段验证就没有办法进行处理这样的验证, 因而我们在这里引用了"逻辑验证"方式.
"逻辑验证"指的是在数据入库前由业务逻辑模块对数据字段的有效性进行检验的方式, 来看我们在SignupBusinessService类中定义的要进行逻辑校验的方法代码:
接下来调用业务逻辑处理类的insertSignupInfo()函数执行对用户表单信息的入库操作, 如果成功的插入, 返回给Application类一个Forward类, 此类负责对URL进行重定义, 重定向过程中调用另一个页面, 并在页面中显示Forward类的构造函数的第二个参数中定义的字串. 如果想直接跳转, 请返回Redirect($url)类, $url为返回的action字串.
看完逻辑代码, 大家就可能明白为什么我要新设计一个Form类, 此类封装POST表单字段, 在Action类与BusinessService类之间扮演着DTO的角色, 因此, 在有POST进行提交的表单中, 大家一定要记的重新生成一个Form子类, 否则无法在Action与BusinessService类之间进行数据传送. 当然,有人说我可以传$_POST? 但考虑数据封装及可能进行的表单验证, 请大家重新生成一个Form的子类做作为DTO进行数据传递.
现在来总结一下module, Action, Form, BusinessService类分别在程序中的作用:
module: 指的是程序的功能单位, 在进行程序设计时,要程序按功能划分为不同的子程序, 这里子程序的概念就是这里module的概念, 代表组成程序的功能单位.
Action: 在module中实现某一功能的最小程序单元, 它实现了显示界面、执行操作两种方法, 在实际的程序中我们只需要重新实现这两个方法来实现相应的功能.记住:在Action担当的控制器(Controller)的功能, 它通过调用BusinessService中的公用接口函数来完成界面的显示及数据的提交.
Form: 在有POST表单字段提交的程序中担任着:1.表单字段服务器端校验 2.作为DTO在Action与BusinessService之间传递表单数据.
BusinessService:用来处理模块中所有的业务逻辑, 通过公用接口的方式为Action类提供函数调用来实现逻辑处理, 所有的与数据库进行交互的活动都应封装在些类中.
程序的执行过程请参照phpsa framework.doc文档.
好了, 关于业务逻辑及进行逻辑验证这部分就讲到这里了... 学习与使用过程中有任何问题,欢迎发邮件与我共同讨论.
大师兄(teacherli(-at-)gmail.com) 2005-12-18
上一次我们把在线报名的界面做完了, 而实际的功能我们留下没讲, 这一讲我们来看看如何在doAction()方法中调用业务逻辑类来完成我们的用户在线报名注册.
PHPSA中以模块做为编程单位, 每一模块中又可以按照层次可分为forms, actions, model, views, 分别代表显示层, 控制层与逻辑层, 从文件夹的名称上, 我们可以看了在PHPSA中推荐在每一模块中只实现一个业务逻辑类, 通过这一个公用的业务逻辑类, 完成模块中所有Action的相关逻辑操作.
首先来看看这个例子中我们主要用到的数据表定义:
CODE:
<?php
/*==============================================================*/
/* Table: tb_signup_user */
/*==============================================================*/
create table tb_signup_user
(
i_id int unsigned not null auto_increment,
vc_user_name varchar(20),
i_sex tinyint,
vc_card_num varchar(18),
im_notion_id tinyint unsigned,
im_edu_id tinyint unsigned,
im_polity_id tinyint unsigned,
im_prov_id tinyint unsigned,
c_post_code char(6),
vc_address varchar(150),
vc_tel varchar(15),
vc_m_tel varchar(15),
vc_email varchar(50),
im_spec_id int unsigned,
primary key (i_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*==============================================================*/
/* Table: tb_spec */
/*==============================================================*/
create table tb_spec
(
i_id int unsigned not null auto_increment,
vc_name varchar(100),
im_college_id tinyint unsigned,
primary key (i_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
?>
以signup模块为代表,来看一下如何编写一个业务逻辑处理类. 在./modules/signup/model/目录中新建立一个类文件, 名称为: signupBusinessService.class.php, 这个文件的命名方式为: 模块名(小写字母开始) + "BusinessServic.class.php", 而类名则将模块名的首字母大小, 像这样:/*==============================================================*/
/* Table: tb_signup_user */
/*==============================================================*/
create table tb_signup_user
(
i_id int unsigned not null auto_increment,
vc_user_name varchar(20),
i_sex tinyint,
vc_card_num varchar(18),
im_notion_id tinyint unsigned,
im_edu_id tinyint unsigned,
im_polity_id tinyint unsigned,
im_prov_id tinyint unsigned,
c_post_code char(6),
vc_address varchar(150),
vc_tel varchar(15),
vc_m_tel varchar(15),
vc_email varchar(50),
im_spec_id int unsigned,
primary key (i_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*==============================================================*/
/* Table: tb_spec */
/*==============================================================*/
create table tb_spec
(
i_id int unsigned not null auto_increment,
vc_name varchar(100),
im_college_id tinyint unsigned,
primary key (i_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
?>
class SignupBusinessService extends BaseBusinessService {...}.
来看看signupBusinessService.class.php文件的代码:
CODE:
<?php
/**
* signup model
* @author teacherli 2005-12-17
*/
require_once (Config::APPLICATION_DIR . "/comm/model/baseBusinessService.class.php");
class SignupBusinessService extends BaseBusinessService {
/**
* return spec list
* @return 2 dim array
*/
public function getSpecList() {
}
/**
* insert signup info to database
* @param Form $signupInfo
* @return boolean
*/
public function insertSignupInfo($signupInfo) {
}
/**
* check if cardnum exist
* @param String $cardNum
* @return boolean if exist return true else return false
*/
public function checkCardNum($cardNum) {
}
}
?>
SignupBusinessService继承自BaseBusinessService类, 通过定义成员函数来实现逻辑功能的实现, 来看getSpecList()函数代码:/**
* signup model
* @author teacherli 2005-12-17
*/
require_once (Config::APPLICATION_DIR . "/comm/model/baseBusinessService.class.php");
class SignupBusinessService extends BaseBusinessService {
/**
* return spec list
* @return 2 dim array
*/
public function getSpecList() {
}
/**
* insert signup info to database
* @param Form $signupInfo
* @return boolean
*/
public function insertSignupInfo($signupInfo) {
}
/**
* check if cardnum exist
* @param String $cardNum
* @return boolean if exist return true else return false
*/
public function checkCardNum($cardNum) {
}
}
?>
CODE:
<?php
/**
* return spec list
* @return 2 dim array
*/
public function getSpecList() {
$strQuery = "SELECT i_id AS id, vc_name AS name " .
"FROM tb_spec";
return $this->db->getList($strQuery);
}
?>
函数定义了一个SQL查询语句, 通过组合的重新封装的adodb类按照二维数组的方式查询出所有的专业列表. adodb类经过重新封装后只能使用如下几个函数:/**
* return spec list
* @return 2 dim array
*/
public function getSpecList() {
$strQuery = "SELECT i_id AS id, vc_name AS name " .
"FROM tb_spec";
return $this->db->getList($strQuery);
}
?>
CODE:
<?php
6.1 DB(): 构造函数, 负责完成数据库连接
6.2 getOne($strQuery): 取得查询返回值第一行的第一列值
6.3 getList($strQuery, $errorMessage="can't call adodb's GetAll, check your sql sentence!"): 取得查询返回所有记录
6.4 getLimitList($strQuery, $pageNum, $pageCount, $errorMessage): 分页查询返回
$strQuery: 查询SQL语句
$pageNum: 页数
$pageCount: 每页记录数
6.5 getRow($strQuery): 返回查询第一行记录组成的数组
6.6 getLastInsertID(): 返回上次插入的自动增值类型记录值, 由于存在多并发下些记录访问的问题, 请谨慎使用此函数.
6.7 update($strQuery): 执行更新语句, 调用execute
6.8 modify($strQuery): 执行修改语句, 调用execute
6.9 delete($strQuery): 执行删除语句, 调用excute
6.10 insert($strQuery): 执行插入语句, 调用excute
6.11 quotes($var): 对指定变量进行quotes, 参数为字符串或数组
6.12 close(): 关闭数据库连接
?>
封装ADODB的主要目的就是为了明确逻辑功能, 降低程序复杂度. 6.1 DB(): 构造函数, 负责完成数据库连接
6.2 getOne($strQuery): 取得查询返回值第一行的第一列值
6.3 getList($strQuery, $errorMessage="can't call adodb's GetAll, check your sql sentence!"): 取得查询返回所有记录
6.4 getLimitList($strQuery, $pageNum, $pageCount, $errorMessage): 分页查询返回
$strQuery: 查询SQL语句
$pageNum: 页数
$pageCount: 每页记录数
6.5 getRow($strQuery): 返回查询第一行记录组成的数组
6.6 getLastInsertID(): 返回上次插入的自动增值类型记录值, 由于存在多并发下些记录访问的问题, 请谨慎使用此函数.
6.7 update($strQuery): 执行更新语句, 调用execute
6.8 modify($strQuery): 执行修改语句, 调用execute
6.9 delete($strQuery): 执行删除语句, 调用excute
6.10 insert($strQuery): 执行插入语句, 调用excute
6.11 quotes($var): 对指定变量进行quotes, 参数为字符串或数组
6.12 close(): 关闭数据库连接
?>
回过头来看看我们的signupAction.class.php文件中的doView()函数, doView()函数用来显示一个供用户填写信息的界面, 在这个界面中,需要从数据库里将所有专业提取出来后供用户进行选择, 由数据库里提取专业列表就由此SignupBusinessService类的getSpecList()来完成, 看我们修改后的doView()函数:
CODE:
<?php
/**
* implements doView
*/
public function doView() {
$this->view->setTplDir("./modules/signup/views");
$this->view->setTpl("signup.html");
//调用业务逻辑由数据库中提取专业例表
$specList = $this->signupBusinessService->getSpecList();
$this->view->assign("specList", $specList);
unset($specList);
$this->view->display();
}
?>
$specList为由数据库里提取一个二维数组, 然后交由view进行处理,显示在供用户进行选择专业的下拉列表中, $this->singupBusinessService在SignupBusinessService类的构造函数中进行实例化./**
* implements doView
*/
public function doView() {
$this->view->setTplDir("./modules/signup/views");
$this->view->setTpl("signup.html");
//调用业务逻辑由数据库中提取专业例表
$specList = $this->signupBusinessService->getSpecList();
$this->view->assign("specList", $specList);
unset($specList);
$this->view->display();
}
?>
来看我们的SignupBusinessService类的doAction()方法, 当用户提交表单后经过SignupForm类验证无误后交由此方法进行执行具体的入库操作, 现在我们又有一个要求:要求用户提供的注册信息中的身份证号码不能重复, 也就是一个用户不可进行多次报名, 很显然, 这要涉及到操作数据库, 在Form中的字段验证就没有办法进行处理这样的验证, 因而我们在这里引用了"逻辑验证"方式.
"逻辑验证"指的是在数据入库前由业务逻辑模块对数据字段的有效性进行检验的方式, 来看我们在SignupBusinessService类中定义的要进行逻辑校验的方法代码:
CODE:
<?php
/**
* check if cardnum exist
* @param String $cardNum
* @return boolean if exist return true else return false
*/
public function checkCardNum($cardNum) {
$strQuery = "SELECT COUNT(*) " .
"FROM tb_signup_user " .
"WHERE vc_card_num = '" . $cardNum . "'";
return (0 != $this->db->getOne($strQuery));
}
?>
函数接受一个$cardNum的参数, 交由SQL语句进行数据库验证, 如果数据库中已经存在这个身份证号码, 则返回true, 否则返回一个false值, 此函数由SignupAction类的doAction()方法进行调用, SignupAction类的doAction()方法代码如下所示:/**
* check if cardnum exist
* @param String $cardNum
* @return boolean if exist return true else return false
*/
public function checkCardNum($cardNum) {
$strQuery = "SELECT COUNT(*) " .
"FROM tb_signup_user " .
"WHERE vc_card_num = '" . $cardNum . "'";
return (0 != $this->db->getOne($strQuery));
}
?>
CODE:
<?php
/**
* implements doAction
*/
public function doAction() {
//执行逻辑验证
if ($signupBusinessService->checkCardNum($this->form->getCardNum())) {
$actionMessage = new ActionMessage();
$actionMessage->setErrorsValue("cardNumError", "对不起, 您的身份证号已经存在于数据库中, 请不要重复报名!");
return $actionMessage;
}
//执行插入
if ($this->signupBusinessService->insertSignupInfo($this->form)) {
return new Forward("signup!success", "报名资料已经提交, 请等待...");
}
}
?>
此方法由两部分组成, 第一部分为进行逻辑验证, 第二部分将用户填写的信息插入数据库, 先来分析在doAction()中执行的逻辑验证代码:/**
* implements doAction
*/
public function doAction() {
//执行逻辑验证
if ($signupBusinessService->checkCardNum($this->form->getCardNum())) {
$actionMessage = new ActionMessage();
$actionMessage->setErrorsValue("cardNumError", "对不起, 您的身份证号已经存在于数据库中, 请不要重复报名!");
return $actionMessage;
}
//执行插入
if ($this->signupBusinessService->insertSignupInfo($this->form)) {
return new Forward("signup!success", "报名资料已经提交, 请等待...");
}
}
?>
CODE:
<?php
if ($signupBusinessService->checkCardNum($this->form->getCardNum())) {
$actionMessage = new ActionMessage();
$actionMessage->setErrorsValue("cardNumError", "对不起, 您的身份证号已经存在于数据库中, 请不要重复报名!");
return $actionMessage;
}
?>
SignupBusinessServie类的checkCardNum()方法在上边我已经介绍过, 现在重点来介绍一个ActionMessage类, 按程序的流程, 如果发现在在数据库中存在传入的指定身份证号码, 则首先实例化一个ActionMessage类, 然后给调用setErrorsValue()方法对ActionMessage设定一条错误信息, 接着将这个ActionMessasge实例返回. Application类调用doAction()函数返回的这个ActionMessage类, 查找其中的错误信息后返回执行Action的doView()函数, 并将错误信息显示在用户界面上. 关于ActionMessage的详细信息, 请参阅PHPSA框架跌phpsa framework.doc文件.if ($signupBusinessService->checkCardNum($this->form->getCardNum())) {
$actionMessage = new ActionMessage();
$actionMessage->setErrorsValue("cardNumError", "对不起, 您的身份证号已经存在于数据库中, 请不要重复报名!");
return $actionMessage;
}
?>
接下来调用业务逻辑处理类的insertSignupInfo()函数执行对用户表单信息的入库操作, 如果成功的插入, 返回给Application类一个Forward类, 此类负责对URL进行重定义, 重定向过程中调用另一个页面, 并在页面中显示Forward类的构造函数的第二个参数中定义的字串. 如果想直接跳转, 请返回Redirect($url)类, $url为返回的action字串.
看完逻辑代码, 大家就可能明白为什么我要新设计一个Form类, 此类封装POST表单字段, 在Action类与BusinessService类之间扮演着DTO的角色, 因此, 在有POST进行提交的表单中, 大家一定要记的重新生成一个Form子类, 否则无法在Action与BusinessService类之间进行数据传送. 当然,有人说我可以传$_POST? 但考虑数据封装及可能进行的表单验证, 请大家重新生成一个Form的子类做作为DTO进行数据传递.
现在来总结一下module, Action, Form, BusinessService类分别在程序中的作用:
module: 指的是程序的功能单位, 在进行程序设计时,要程序按功能划分为不同的子程序, 这里子程序的概念就是这里module的概念, 代表组成程序的功能单位.
Action: 在module中实现某一功能的最小程序单元, 它实现了显示界面、执行操作两种方法, 在实际的程序中我们只需要重新实现这两个方法来实现相应的功能.记住:在Action担当的控制器(Controller)的功能, 它通过调用BusinessService中的公用接口函数来完成界面的显示及数据的提交.
Form: 在有POST表单字段提交的程序中担任着:1.表单字段服务器端校验 2.作为DTO在Action与BusinessService之间传递表单数据.
BusinessService:用来处理模块中所有的业务逻辑, 通过公用接口的方式为Action类提供函数调用来实现逻辑处理, 所有的与数据库进行交互的活动都应封装在些类中.
程序的执行过程请参照phpsa framework.doc文档.
好了, 关于业务逻辑及进行逻辑验证这部分就讲到这里了... 学习与使用过程中有任何问题,欢迎发邮件与我共同讨论.