<![CDATA[PHP博客,web开发]]> http://www.xingyl.com/ zh-cn Thu, 21 Aug 2008 10:37:37 GMT Thu, 21 Aug 2008 10:37:37 GMT http://www.xingyl.com/ <![CDATA[基于角色的权限系统]]> http://www.xingyl.com/?m=blog&id=186 Fri, 08 Aug 2008 03:19:45 GMT 不同角色之间的权限关系有两种

一。子集关系

上级拥有下级的所有权限,并且多一些专有权限。

二。交叉关系

A拥有B的部分权限,有自己的专属权限。
B也拥有A的部分权限,有自己的专属权限。

三。综合关系

实际上子集关系与交叉关系常常会混合,例如基本上是子集关系,但下级有某个特殊的功能,上级不拥有。

以前做的应用,不同角色间都是纯粹的子集关系,现在要做一个复杂权限关系的系统,所以整理一下设计思路。

表的设计

role: 角色
lv:   权限


字段  role    lv
1.     皇帝   10
2.     教宗   10
3.     领主   5
4.     牧师   4
5.     骑士   3
6.     平民   1
7。   奴隶   0


假设皇帝、领主、骑士、平民、奴隶是完全上下级关系,而教宗与皇帝是平级的交叉关系,即有些事情只有教宗能做,皇帝不能做,有些事情只有皇帝能做,教宗不能做.同样的魔法师与领主也呈现这种关系
简单的说,级别有两个体系

1.政治体系:皇帝、领主、骑士、平民、奴隶
2.宗教体系: 教宗、牧师

实例:

1.皇帝,教宗,领主通过
if(lv >= 5) echo 'pass';

2.骑士以上 lv>=3

3.教宗  role = 教宗

4.牧师与教宗 role = 牧师 AND role = '教宗'

5. 牧师、皇帝、教宗、领主  lv >= 4

^^^^^
虽然只要role字段就够了,但是有了lv辅助字段大部分情况下可以极大简化判断条件

如果不使用lv值,
第5个实例条件就得从 lv >=4 改成 role=皇帝 or role=教宗 or role=领主 or role=牧师
第2个实例就得从 lv >= 3 改为 role = 皇帝 or role = 教宗 or role = 领主 or role = 牧师 or role = 骑士  (或改为 role != 平民 and role !=奴隶)

实际代码

access方法:权限不足则自动转向到登录页或首页。参数无个数限制,无顺序,可用非操作符

user::access(5);  相当于 lv >= 5
user::access('教宗','牧师', '皇帝'); 不用解释吧?
user::access(1, '!牧师'); lv > 1 AND role != '牧师'
user::access('!奴隶')  排除奴隶,其他角色都通过

check方法:用法完全同access,但权限不足时只会返回exit('0'); 以适应ajax调用环境,

之所以不将access和check方法组合为一个方法(加一个参数识别要不通过后的操作),是为了简化使用方法。

]]>
<![CDATA[thinkPHP创始人]]> http://www.xingyl.com/?m=blog&id=185 Sat, 19 Jul 2008 16:16:08 GMT 老大约朋友一起出去吃饭,问我要不要去,本来周末有点事我不想去的,不过知道可以见到thinkPHP创始人后我去了。

thinkPHP是国内两大著名框架之一(还有一个是FleaPHP), 虽然我一直使用自己的框架,没怎么关注框架这一块,但对于thinkPHP还算有一定了解,尤其欣赏它的简洁(许多常用函数名就一个字母,哈哈,这点太合我胃口了)和CURD自动化操作,最近我一直在研究的数据库简化操作正是从中得到灵感。

虽然到现在为止我还没用过知名框架,不过我最佩服的就是开发这些框架的高手。低手改程序,中手开发项目,而写程序给别的程序员用的无疑是高手,他们每写一行代码都会思考是否有更好的办法,每写一个类、一个模块都要考虑别人用起来会习惯吗?是否可以更简洁高效?低级程序员学起来有多难?如果一个程序员时刻保持这种思维,那么就算现在水平差也会迅速提高吧。

thinkPHP的创始人刘晨(不肯定,但是这个读音)很亲和朴实,一点没有表现出某些“大师”的傲气,在场的还有一位是搞培训的,可惜他们三个一直讨论创业之类的话题,我也插不上嘴,对于框架的问题没有时间好好讨论,不得不说是个遗憾。

 

]]>
<![CDATA[自动化数据操作类model用法演示]]> http://www.xingyl.com/?m=blog&id=184 Thu, 17 Jul 2008 16:17:22 GMT 高度自动化数据操作类model用法演示

这两天没啥事干,只化了一天时间就把昨天设想的功能基本全实现了。

操作数据库有了封装类之后,确实方便了许多,但是身为懒人,显然不可能满足于此,我接下来的目标是把那些常用的简单透顶又只能每天重复的SQL操作简化到极致。经过了这段时间的构思与实践,完成了基本的添加修改与删除操作,并且读操作已经相当完善了,相信要实现同样的功能很难有更简洁的用法了。

一。使用之前。如果在我的框架里什么都不用做,否则需要做以下三步

1.首先要先建立db类实例为$db
2.载入filter过滤类
3.载入model类


二。创建类实例

1.直接使用.必须传入要操作的表名

PHP代码
  1. $u = new model('user');  

2.继承使用

PHP代码
  1. class user extends model  
  2. {  
  3.     //protected $_table = 'xy_user';  
  4.     //protected $_key = 'id';  
  5. }  
  6. $u = new user();  

这样会自动对“表前辍”加子类名做为要操作的表名,例如这里的user类默认对xy_user操作,或者将注释去掉,将$_table属性设为要操作的表名.$_key是主键名,默认为id

三。常用读操作示例

在建立实例之后可以使用get方法,以下列出部分用例

PHP代码
  1. $u->get();            //取全部  
  2. $u->get(5);           //读取主键值为5的记录(以下主键简称为id)  
  3. $u->get('id>5 AND id<10');    //读id大于5,小于10的记录  
  4. $u->get('id asc');        //按id正序排列,默认为按主键反序排  
  5. $u->get('id,title');      //要读的字段列表,默认为*  
  6. $u->get('0,10');      //limit限定,读取前10条记录  

只要定义了ID(第2行),读取的结果自动为一维关联数组,否则是二维数组
如果只读一个字段(第5行中读了两个字段), 则自动将结果弄成一维数组。如get('id')的结果可能是:array(2,3,4,5,25);
以上五种参数可以随意组合,并且不分顺序

PHP代码
  1. $u->get('id,title''id>10''0,10');  
  2. $u->get('0,10''id,title''id>10'); //这两个例子的结果是一样的,都是读id大于10,的前十条记录,并且只读id和title两个字段  
  3. $u->get('id asc, title desc''id,title''0,10''id>5 OR id<10'); //这样也可以,model能够理解你的意图。  

get方法的参数可以放在构造函数中,这将隐式调用get方法,以节省一行代码

PHP代码
  1. $u = new user('id asc, title desc''id,title''0,10''id>5 OR id<10');  
  2. //get方法如果是空参数,表示取所有记录,但构造函数的空参数不取记录,只是初始化类,如果也要取全部记录,要用'all'参数  

这与先建立实例,再get的效果完全相同,所以大部分的情况下都可以用一行代码完成读记录功能。但是要注意如果直接使用model类而不是继承的话,不支持用构造函数读记录的方式,老实的用get函数吧

PHP代码
  1. $u = new model('xy_user');  
  2. $u->get('id,title''0,10');  

读记录的的五种参数,除了一骨脑儿丢进构造函数或get方法之处,也有专心的方法负责

PHP代码
  1. $u->fieldList('id,title,type');  //字段列表  
  2. $u->where(5);            //读id为5的记录   $u->where('id>5 AND type=5');        //条件任意用AND OR组合  
  3. $u->order('addtime desc');       //按加入时间反序排  
  4. $u->order('id');         //默认按asc方式。注意如果在get或构造函数中设置排序,即使是asc顺序也要设成'id,asc'  
  5. $u->limit('0,10');  
  6. $u->limit('limit 5');  
  7. $u->limit(0,5);         //一看使知含义,参数随便多样,想出错也不容易  

上面使用了专门的方法设置SQL的各子句, 当参数全丢进get方法的时候如果出错,可以考虑用这种方法试试,注意这些方法设置应该在get方法之前才有效,因为get的时候已经执行select了。
另外,模仿jquery和thinkphp,一般的方法都可以串联操作,爱用就用吧

PHP代码
  1. $u->fieldList('id,title')->order('title')->limit(0,5)->where('id>10')->get();  

差点忘了说明类读取数据后怎么使用呢?

PHP代码
  1. A。单记录模式这么读  
  2.    $b = new blog(35);  
  3.    echo '日志标题为'.$b->title;  
  4. B。多记录模式,使用$b->data纯数组  
  5.    $b = new blog('all');  
  6.    foreach($b->data as $item) {  
  7.        echo '标题:'.$item['title'].'<br/>';  
  8.    }  
  9. C。给模板赋值,无论是单记录模式还是多记录模式,都可以直接把对象丢进去。因为这里的assign方法会将对象智能转换为$b->data数据。  
  10.    $this->assign('blog'$b);  
  11.   
  12. D。模板中使用完全不变,该怎么用就怎么用  

四。join操作

鉴于多表join参数设置复杂,估计用起来复杂度会超过直接写原生的sql语句,得不偿失,所以暂时只实现了两表join.要更复杂的实现,手写吧。
join由join()方法负责,参数不能像其他子句般丢给get方法或构造函数。

PHP代码
  1. $u = new blog();    //当然,要建立blog类并继承model  
  2. $u->join('xy_type''type''id');   //参数有顺序:join表, ON中的本表字段名, ON中的join表字段名  
  3. $u->get('xy_blog.id asc','xy_blog.id,xy_blog.title,xy_blog.type,xy_type.name','0,3');    //以id正序,取日志表前三条记录的id,title,type,并且去join表取得分类名name  

由于使用了join,字段名通常都要加上前辍,这样就使语句繁复不少,经过挣扎尝试,我实现了表前辍自动补齐功能,字段列表,条件,order子句中的字段名在JOIN中也能够不加表前辍,上例可以省略为

PHP代码
  1. $u = new blog();  
  2. $u->join('xy_type''type''id')->get('id asc','id,title,type,name','0,3');        //串联,并且字段名省略表前辍 

写这个自动补齐让我吐血三升,折腾了几个小时,感觉像是写一个简单的sql解析器。补齐的原则是(若己有表前辍就不补)本表中有此字段补本表名,否则补join表为字段前辍。基本和我们的意图相符,如果发现补错,就在参数里手工加表前辍吧。

五。删除操作

删除操作使用和get操作同样的解释引擎,所以get中能使用的条件参数del中也能使用

PHP代码
  1. $u = new blog(15);  
  2. $u->del();  //构造函数中已经弄成了id=15这个条件,所以这里删除的记录为15  
  3. $u->new blog('id>10');  
  4. $u->del('type=1');     //条件可叠加,这里相当于id>10 AND type=1  
  5. $u->new blog('id>10');  
  6. $u->del('all')->del(18);    //这里用del('all')清除id>10的原有条件,所以条件只剩下后面的id=18  

因为删除操作的特殊性,要删除全部请用del('all');, 而不是空参数,那容易因为参数传递失误引起记录全删,一失足成千古恨?

六。添加,修改操作

看看三种用法

 

PHP代码
  1. $u = new user(array('name'=>'白痴')); //传递数组,对象,会隐式调用save方法保存数据。至于是添加还是修改则由是否存在主键值智能判断 

  2. $u->save($_POST);               //这是很常见的用法,将表单提交的POST数组直接保存。当然,丢给构造函数效果相同 

  3. $u->new user(2);  
  4. $u->name = '星野天河';  
  5. $u->save(); //读取id为2的用户,并修改name字段值后保存  

save前会调用binde方法绑定、过滤数据到自身,然后再保存。过滤方法为asc(),如果对默认过滤方式不满意可在外面过滤好再丢进来

七。自省(调试自身)

PHP代码
  1. $u->debug();                    //会显示sql语句,及各子句,还有绑定的数据  

八。其它

还有一些方法是DB类的映射,以及__set,__get魔术方法,这是为了实现单记录模式下$u->name能输出name字段,$u->id能输出id字段,其实对象中并没有name成员,只是用魔术方法映射为$u->data['name'],这个魔术方法对自身也有效,因为在类中可以用$this->id代替$this->data['id']

总结:读操作比较完善了,别的周边方法会酌情增加,删除操作考虑增加触发器,比如删除日志的情况下,自动删除本日志的评论,当然这个触发器应该在子类中定义。

08-08-08补充:

现在加了两种简单的触发器,定义一个参数可以实现两表的联动删除,不过还不是很完善,照这个思路要实现多表联动效率会比较低

]]>
<![CDATA[数据自动化操作将要实现的功能]]> http://www.xingyl.com/?m=blog&id=182 Wed, 16 Jul 2008 14:08:01 GMT 前台粗糙写就的model类只实现的最基本的功能,但是我希望在不明显增加使用复杂度的情况下扩充功能。以前是边写边增加功能,这次试着先写出所有使用方法,再去一一实现。

model需要支持的常用操作.看几个关键方法

-------------------------------------------------------------
model的构造函数。为了使用简便,必须实现参数自动识别,以及参数无序性(需要多参数的情况下)

互斥型操作,不可组合
1.什么也不做    $blog = new blog();
2.保存数据    $blog = new blog($_POST);        //数组,数据对象或是model子类的实例。自动判断是添加还是修改

可组合型操作,下面的参数基本任意组合应用(4和5互斥)
3.按id取    $blog = new blog(56);
4.取全部    $blog = new blog('all');
5.条件        $blog = new blog('id>5');        //条件可组合如'id>5 AND iq > 100'
6.排序字段    $blog = new blog('addtime desc');    //同样可组合如'age desc, id' 为区分条件字符串,至少带一个desc或asc
7.取字段列    $blog = new blog('id,title,keyword');
8.LIMIT子句    $blog = new blog('0, 10');

以上八种参数可以明显区分开来,有的可任意组合例如:new blog('id>5', 'id,title', '0,10');,达到简化基本操作的目的。JOIN查询由于其复杂性(参数多,并且难以于别的参数轻易区分,这样不好实现参数无序特性),全权由别的方法解决,就不为难构造函数了.
当然这么多复杂操作实现代码不写在构造函数里,它只负责初始化并识别参数(其中读参数解析由get方法负责),然后转发给相应的方法处理具体的实现过程。
------------------------------------------------------------
关键的辅助方法
args        解析参数
binde        绑定数据(包含过滤)
fieldInfo    取表的字段信息
-------------------------------
读操作, 负责构造函数中读操作的具体实现。(除了1,2两种用法以外)
get
如果构造函数有3-8中的参数,会隐式调用get方法
$blog = new blog('all');
也可以显式调用
$blog = new blog();
$blog->get('all');    //也可以省略为get();
get方法中会调用args解析参数。
-------------------------------------------------------------
保存,修改操作,
save    保存数据
为构造函数的第2种参数服务,如果构造函数传进数据,则隐式调用save
$blog = new blog($_POST);
或者构造函数并没有绑定数据,显示地调用save保存
$blog = new blog();
$blog->save($_POST);
save方法会调用binde方法绑定数据
-------------------------------------------------------------
删除
del
参数用法基本同构造函数的3,4,5项
如果参数空,则删除己取得的记录,条件同构造函数。
$blog = new blog('mid=3');
$blog->del();
如果del带参数,由在己取的记录中再筛选。(参数叠加)
$blog = new blog('mid=2');
$blog->del('look_num > 50');    //相当于mid=2 AND look_num>50
--------------------------------------------------------------

由__set和__get实现数据映射
其它的周边操作考虑__call虚拟方法解决

]]>
<![CDATA[编码提速之旅——显示层]]> http://www.xingyl.com/?m=blog&id=181 Sat, 12 Jul 2008 14:59:51 GMT 友情提示:笔者是个话唠,不想锻炼耐性者可以直接跳过前五段

经历了今年二月开始至今“编码提速之旅——数据操作层”的进化后,保存修改数据这一步简单得恐怖,写普通的应用基本上都是简单的数行代码轻轻带过,只有复杂的情况下需要手写sql语句了。

于是问题来了,原来在做网站的时候,前后台(这里的前台指的是html,css,js层面, 后台指的是PHP层面)代码的比例相若,但现在后台编码进化后,前台代码的比例甚至达到90%。以前已经觉得写这些大同小异的表单(包含CSS,JS验证,AJAX提交)很恶心了,现在堪称“恨之入骨”。

必须封装前台代码,最好用一个类搞定表单的一切!尽管知道这不明智(其实失败过数次了),但我再忍不下去了。

在网上查了PEAR的quickForm资料,这个类可以生成表单且包揽前后台的验证。但一来必须装PEAR和quickForm,使用环境受限,并且感觉便捷程度不高,比手工写代码的方式省不了多少。而且我需要和jquery结合的ajax提交,为了长久考虑还是自己写吧。

 


 

胡扯完了,开始吧。实践了大约一周,雏形初步形成了,先看看使用方法

PHP代码
  1. $blog = new blog($_GET['id']);   
  2. $blog->getTag();    //去tag表取tag字段   
  3. $form = new form(720, $blog);   
  4. $form->text(array('title'=>'标题''keyword'=>'关键字''tags'=>'TAG'));   
  5. $form->select('type''分类'new type('`mod`=1'));   
  6. $form->textarea('description''摘要', 60);   
  7. $form->fck('content''内容');   
  8. $form->button();   
  9. $form->noEmpty('title''content');   
  10. echo $form->create();  

第1行根据$_GET['id']取得此篇日志信息,第2行取tag,因为日志表中没有tag字段,tag字段由tag关系表中拼出来。这两行是取得要一会编辑的数据。

第3行建立表单类,接受四个参数。1.表单要提交的url  2.表单宽  3.表单默认显示的数据(编辑时) 4.表单的id。 这四个参数全是可选,并且顺序任意。基本上1和4都不要写。

第4行建立三个单行输入框。键名必须和数据表中字段名相同

第5行建立分类下拉选择框,数据源从type表中来,并且只需要mod=1的分类。因为我不同模块的分类都放一个表里。

第6行建立textarea,第三个参数是高度

第7行建立FCK,可选参数有工具栏,高度

第8行建立按钮,默认是submit类型

如果在构造函数传入数据(以上的$blog类),下面的表单控件会自动填入相应的值,这也是为什么控件名要和字段名一样的原因。

第9行设定title和content字段不能为空。这个noEmpty函数的参数也是无序的,并且不限数量。

除了非空检查,还有mail邮箱检查函数,int整数检查函数,eq两个字段是否相等检查函数,其它的检查等用到了再说。

如果检查没通过,控件的边框会变红,并且出现淡入淡出的提示,检查通过边框恢复成绿色。

第10行输出表单,参数可以是1.'none',默认参数,保存成功只提示,不做别的处理, 2.'back', 保存后退到上一页 3.'reload': 保存后刷新  4.任意URL:保存后跳转

已经说明得很详细了,下面是form类的代码

PHP代码
  1. <?php   
  2. /**  
  3.  * 用model解决了PHP层面大半麻烦之后, 尽管可能不是明智之举,但我还是开始尝试简化HTML层面  
  4.  * 希望form类可以简化表单生成,JS验证,后台验证  
  5.  * 其中CSS和JS代码大部分在form.css和form.js中,少量需要变化的代码在类中生成,直接镶入HTML中  
  6.  */  
  7. class form   
  8. {   
  9.     public $saveUrl;    //ajax提交表单地址   
  10.     public $labelWidth=80;  //label宽   
  11.     public $formWidth;  //form宽   
  12.     public $formClass="xyForm";   
  13.     public $formId;   
  14.     public $action=";";  //表单的action属性   
  15.     public $data;       //编辑时表单里的数据   
  16.     private $header;    //表单头html   
  17.     private $footer;    //表单尾html   
  18.     private $main;      //表单html主体   
  19.     private $map=array();       //字段name与label的键值对(js验证的提示会用到,以避免手工输入提示的麻烦)   
  20.     private $noEmpty='{}';      //不能为空的字段列表,生成js对象   
  21.     private $email;   
  22.     /**  
  23.      * 构造函数.模拟jquery的参数无序性模式  
  24.      * @param url $saveUrl      ajax提交表单的地址  
  25.      * @param string $formId    表单id  
  26.      * @param int $formWidth    宽度(只接收整数)  
  27.      * @param array or object $data 表单绑定数据(编辑时)  
  28.      * 因为四个参数可以明显的区分开,因此采用参数无序模式  
  29.      * @example $form = new form($data, 500); 等同于 $form = new form(500, $data);  
  30.      * 一般表单宽度都需要设置, 编辑时还需要绑定数据, 其他两个参数基本上用默认值即可.  
  31.      */  
  32.     function __construct()   
  33.     {   
  34.         //配置参数默认值   
  35.         $this->saveUrl = ROOT.'?m='.MODULE.'&a=save';   
  36.            
  37.         $this->formId = MODULE.'Form';   
  38.         $this->formWidth = '600px';   
  39.         $this->data = (object) array();   
  40.         //设置新值   
  41.         $args = func_get_args();   
  42.         foreach ($args as $arg) {   
  43.             if(is_numeric($arg)) $this->formWidth = $arg.'px';   
  44.             if(is_array($arg)) $this->data = (object)$arg;   
  45.             if(is_object($arg)) $this->data = $arg;   
  46.             if(is_string($arg)) {   
  47.                 if(preg_match('#^[\w]+$#'$arg)) {   
  48.                     $this->formId = $arg;   
  49.                 }else{   
  50.                     $this->saveUrl = $arg;   
  51.                 }   
  52.             }   
  53.         }   
  54.         if($this->data->id) $this->hidden('id');    //有id情况下自动添加隐藏input   
  55.     }   
  56.     /**  
  57.      * 生成<input type="text" />  
  58.      * 例: text('title', '标题');  或者 text(array('title'=>'标题', 'keyword'=>'关键字'))  
  59.      * 只有text支持数组的$name一次创建多个,其他的类型这么做意义不大就算了  
  60.      */  
  61.     function text($name$label=' ')   
  62.     {   
  63.         if($label != ' '$label .= ':';   
  64.         if(is_string($name)) $name = array($name=>$label);   
  65.         foreach ($name as $k=>$v) {   
  66.             $this->map["$k"] = $v;   
  67.             $this->main .= "\t".'<li><label>'.$v.':</label><input type="text" value="'.$this->data->$k.'" name="'.$k.'" id="'.$k.'" class="text" /></li>'."\r\n";   
  68.         }   
  69.     }   
  70.     //例: password('password', '密码')   
  71.     function password($name$label=' ')   
  72.     {   
  73.         $this->map["$name"] = $label;   
  74.         $this->main .= "\t".'<li><label>'.$label.':</label><input type="password" name="'.$name.'" id="'.$name.'" class="text" /></li>'."\r\n";   
  75.     }   
  76.     //例: password('id')   
  77.     function hidden($name) {   
  78.         $this->main .= "\t".'<input type="hidden" value="'.$this->data->$name.'" name="'.$name.'" id="'.$name.'" />'."\r\n";   
  79.     }   
  80.     /**  
  81.      * 生成select  
  82.      *  
  83.      * @param str $name name,id,class属性值  
  84.      * @param str $label    标签文字  
  85.      * @param array $data   可能是一维数组、二维数组、纯数据对象、或是model对象实例  
  86.      * @param str $default  默认选中项(键值)  
  87.      * @param str $value    在$array数组中做为option值的键名  
  88.      * @param str $text 在$array数组中做为option文本的键名  
  89.      * @example select('type', '分类', $type->data, 5)  
  90.      */  
  91.     function select($name$label=' '$data$default=""$value='id'$text='name')   
  92.     {   
  93.         $this->map["$name"] = $label;   
  94.         $this->main .= "\t<li>\r\n\t<label>$label:</label>\r\n\t<select name=\"$name\" id=\"$name\" class=\"select\">\r\n";   
  95.         $array = $this->arrayFormat($data);   
  96.         $default = $default ? $default : $this->data->$name;   
  97.         foreach ($array as $item) {   
  98.             if($item["$value"] == $default$selected = 'selected="selected"';   
  99.             else $selected = '';   
  100.             $this->main .= "\t\t<option $selected value=\"{$item["$value"]}\">{$item["$text"]}</option>\r\n";   
  101.         }   
  102.         $this->main .= "\t</select>\r\n\t</li>\r\n";   
  103.     }   
  104.     private function arrayFormat($data)   
  105.     {   
  106.         if(is_array($data->data)) return $data->data;   
  107.         if(is_object($data)) return (array$data;   
  108.         if(is_array($data[0])) return $data;   
  109.         foreach ($data as $key=>$dir) {   
  110.             $skins[$key]["id"] = $skins[$key]['name'] = $dir;   
  111.         }   
  112.         return $skins;   
  113.     }   
  114.     //例 textarea('description', '简介');   
  115.     function textarea($name$label=' '$height=100)   
  116.     {   
  117.         $this->map["$name"] = $label;   
  118.         $this->textareaHeight = $height;   
  119.         $this->main .= "\t".'<li><label>'.$label.':</label><textarea name="'.$name.'" id="'.$name.'" class="textarea">'.$this->data->$name.'</textarea></li>'."\r\n";   
  120.     }   
  121.     //例 fck('content', '内容', 'Basic', 200)   
  122.     function fck($name$label=' '$barSet='Default'$height=300)   
  123.     {   
  124.         $this->map["$name"] = $label;   
  125.         $this->main .= "\t".'<li><label>'.$label.':</label>';   
  126.         $this->main .= load(array('mod'=>'fck''name'=>$name'width'=>($this->formWidth-$this->labelWidth-30).'px''height'=>$height'value'=>$this->data->$name'barSet'=>$barSet));   
  127.         $this->main .= "</li>\r\n";   
  128.     }   
  129.     //用法同select   
  130.     function radio($name$label=' '$array$default=""$value='id'$text='name')   
  131.     {   
  132.         $this->map["$name"] = $label;   
  133.         $this->main .= "\t<li><label>$label:</label>";   
  134.         $array = $this->arrayFormat($array);   
  135.         foreach ($array as $item) {   
  136.             if($item["$value"] == $default$checked = 'checked="checked"';   
  137.             else $checked = '';   
  138.             $this->main .= "<input name=\"$name\" type=\"radio\" id=\"$name\" class=\"$name\" value=\"{$item['id']}\" $checked />{$item['name']}  ";   
  139.         }   
  140.         $this->main .= "</li>\r\n";   
  141.     }   
  142.     //用法同select,但默认选中项为数组   
  143.     function checkbox($name$label=' '$array$checkedArray=array(), $value='id'$text='name')   
  144.     {   
  145.         $this->map["$name"] = $label;   
  146.         $this->main .= "\t<li><label>$label:</label>";   
  147.         $array = $this->arrayFormat($array);   
  148.         foreach ($array as $item) {   
  149.             if(in_array($item["$value"], $checkedArray)) $checked = 'checked="checked"';   
  150.             else $checked = '';   
  151.             $this->main .= "<input name=\"$name\" type=\"checkbox\" id=\"$name\" class=\"$name\" value=\"{$item['id']}\" $checked />{$item['name']}  ";   
  152.         }   
  153.         $this->main .= "</li>\r\n";   
  154.     }   
  155.     //例 file('img', '图片')   
  156.     function file($name$label=' ')   
  157.     {   
  158.         $this->map["$name"] = $label;   
  159.         $this->main .= "\t<li><label>$label:</label><input type=\"file\" name=\"$name\" class=\"$name\" id=\"$name\" /></li>\r\n";   
  160.     }   
  161.     //flash上传,有进度条,通用性不强,懒得配参数了   
  162.     function flashUpload($label=' ')   
  163.     {   
  164.         if($label != ' '$label .= ':';   
  165.         $this->main .= "\t<li><label>$label</label>".'<embed height="30" align="middle" width="420" type="application/x-shockwave-flash" quality="high"   
  166.         loop="false" play="true" src="./lib/upload.swf?savefile=save.php&maxsize=100240&bgcolor=4499EE&limit=mp3|wma"/>'."</li>\r\n";   
  167.     }   
  168.     //按钮 button()   
  169.     function button($text="保存"$type='submit')   
  170.     {   
  171.         $this->main .= "\t".'<li><button id="button" class="button" type="'.$type.'">'.$text.'</button></li>'."\r\n";   
  172.     }   
  173.     //生成css代码   
  174.     function css()   
  175.     {   
  176.         return "\r\n\t\t\t<link href=\"".ROOT."skins/form.css\" rel=\"stylesheet\" />  
  177.             <style>  
  178.                 #$this->formId {width: $this->formWidth;}  
  179.                 #$this->formId label {width: ".$this->labelWidth."px;}  
  180.                 #$this->formId .text, .xyForm textarea{width: ".($this->formWidth-$this->labelWidth-30)."px;}  
  181.                 #$this->formId .button {width: ".($this->formWidth-25)."px;}  
  182.                 #$this->formId .textarea{height: ".$this->textareaHeight."px;}  
  183.             </style>\r\n";   
  184.     }   
  185.     //生成js   
  186.     function js($action)   
  187.     {   
  188.         return "\t".'<script src="'.JS_DIR.'form.js" type="text/javascript"></script>  
  189.             <script type="text/javascript">  
  190.             $(function (){  
  191.                 '.$this->noEmpty.$this->email.$this->intObj.'  
  192.                 url = "'.$this->saveUrl.'";  
  193.                 $("#'.$this->formId.'").submit(function (){  
  194.                     '.$this->eq."submit($(this), '$action');  
  195.                 });  
  196.             });  
  197.             </script>";   
  198.     }   
  199.     //不能为空的字段 例: noEmpty('title', 'keyword');   
  200.     function noEmpty()   
  201.     {   
  202.         $fields = func_get_args();   
  203.         $str = 'empty = {';   
  204.         foreach ($this->map as $name=>$label) {   
  205.             if(!in_array($name$fields)) continue;   
  206.             $str .= $name.":\"$label\",";   
  207.         }   
  208.         if(strlen($str) > 1) $str = substr($str, 0, -1);   
  209.         $this->noEmpty = $str."};\r\n";   
  210.     }   
  211.     //email字段检查 例 email('email', 'msn');   
  212.     function int()   
  213.     {   
  214.         $fields = func_get_args();   
  215.         $str = "\t\t\t\t".'intObj = $("';   
  216.         foreach ($fields as $name) {   
  217.             $str .= "#$name,";  
  218.         }  
  219.         if(strlen($str) > 3) $str = substr($str, 0, -1);  
  220.         $this->intObj = $str.'");';   
  221.         if(strlen($this->intObj) > 4) {   
  222.             $this->intObj .= "\r\n\t\t\t\tintObj.blur(intBlur);";   
  223.         }   
  224.     }   
  225.     //整数字段检查 例 email('email', 'msn');   
  226.     function email()   
  227.     {   
  228.         $fields = func_get_args();   
  229.         $str = "\t\t\t\t".'$("';   
  230.         foreach ($fields as $name) {   
  231.             $str .= "#$name,";  
  232.         }  
  233.         $str = substr($str, 0, -1);  
  234.         $this->email = $str.'");';   
  235.         $this->email .= "\r\n\t\t\t\temail.blur(emailBlur);";   
  236.     }   
  237.     //必须相等的字段名   
  238.     function eq($field1$field2)   
  239.     {   
  240.         $this->eq = "if(!isEq('$field1', '$field2')) {  
  241.                         $.tip('".$this->map[$field1]."和".$this->map[$field2]."必须相等');  
  242.                         return false;  
  243.                     }  
  244.             \t\t";   
  245.     }   
  246.     /**  
  247.      * 生成表单  
  248.      * @param str $action 操作成功后的动作url = 跳转; back = 后退; reload = 刷新  
  249.      */  
  250.     function create($action='none')   
  251.     {   
  252.         $this->header .= '<form class="clearfix '.$this->formClass.'" id="'.$this->formId.'" method="post" action="'.$this->action.'">'."\r\n";   
  253.         $this->header .= "<ul>\r\n";   
  254.         $this->footer .= "</ul>\r\n</form>\r\n";   
  255.         return "\r\n<!-- ************自动生成表单开始************ -->\r\n".$this->js($action).$this->css().$this->header.$this->main.$this->footer."\r\n<!-- ************自动生成表单结束************ -->\r\n";   
  256.     }   
  257.        
  258. }  

最后说明一下JS和CSS的生成方式

要实现一个随便镶入哪儿都过得去的表单,CSS代码不会太少。同样的实现JS检查和ajax提交的通用性代码量也不少,如果直接在类中输出,会显得编辑困难不符合分层设计的思想。

如果直接引用固定的css和js文件,代码全部定死,很难享受到封装的优势,例如表单宽,label宽,input宽,按钮宽都是应该根据构造函数的参数来定的。

直接生成与固定引用都不可取,最后只好扬长避短,让固定的代码写在固定的文件里,到时候引用,不固定的参数则用form类直接输出,虽然还是有点别扭,但是做前台代码的封装本身就是件别扭的事,只能尽量搞得漂亮一点了。

form.css代码就不帖了,下面是form.js文件,要配合jquery来使用。我的JS功底不行,恐怕贻笑大方了

JavaScript代码
  1. /**  
  2.  通用表单处理函数,包括字段检查与ajax提交  
  3. */  
  4. //提交   
  5. function submit(form, action){   
  6.     if(typeof empty != 'undefined' && !emptyCheck(empty)) return false;   
  7.     if(typeof email != 'undefined' && !emailCheck(email)) return false;   
  8.     if(typeof intObj != 'undefined' && !intCheck(intObj)) return false;   
  9.     var post = form.serializeArray();   
  10.     for (key in post) {   
  11.         if($('#'+post[key].name).attr('alt')=='fck') {   
  12.             post[key].value = $.getFckVal(post[key].name);   
  13.         }   
  14.     }   
  15.     $.post(url, post, function (data){   
  16.         if(data==1) {   
  17.             if(action == 'reload') {   
  18.                 $.tip('Save success', 1000, location.href);   
  19.             }else if(action == 'back') {   
  20.                 $.tip('Save success', 1000);   
  21.                 setTimeout("history.go(-1)", 1000);   
  22.             }else if(action == 'none') {   
  23.                 $.tip('Save success', 1000);   
  24.             }else{   
  25.                 $.tip('Save success', 1000, action);   
  26.             }   
  27.         }else {   
  28.             $.tip(data);   
  29.         }   
  30.     });   
  31. }   
  32. //是否为空检查,data为键值对 {title: '标题', keyword:'关键字'}   
  33. function emptyCheck(data)   
  34. {   
  35.     for (key in data) {   
  36.         if($('#'+key).attr('alt') == 'fck') {   
  37.             $('#'+key).val($.getFckVal(key));   
  38.         }   
  39.         if(!$('#'+key).val()) {   
  40.             $.tip('请输入'+data[key]);   
  41.             $('#'+key).css('border-color''#f00').focus();   
  42.             if($('#'+key).attr('alt') == 'fck') {   
  43.                 $('#'+key).val($.getFckVal(key));   
  44.             }   
  45.             $('#'+key).change(function (){   
  46.                 $(this).css('border-color''#386');   
  47.             });   
  48.             return false;   
  49.         }   
  50.     }   
  51.     return true;   
  52. }   
  53. //邮箱的blur检查   
  54. function emailBlur(){   
  55.     var pattern = /^([\w\-\.])+@([\w\-\.])+(\.[\w\-\.])+/;   
  56.     if(!$(this).val() || !pattern.exec($(this).val())) {   
  57.         $.tip('错误的邮箱格式');   
  58.         $(this).css('border-color''#f00').focus();   
  59.         return false;   
  60.     }else{   
  61.         $(this).css('border-color''#386');   
  62.         return true;   
  63.     }   
  64. }   
  65. //提交时的邮箱字段检查   
  66. function emailCheck(data)   
  67. {   
  68.     var pattern = /^([\w\-\.])+@([\w\-\.])+(\.[\w\-\.])+/;   
  69.     var rs = true;   
  70.     data.each(function (){   
  71.         if(!rs) return false;   
  72.         if(!$(this).val() || !pattern.exec($(this).val())) {   
  73.             $.tip('错误的邮箱格式');   
  74.             $(this).css('border-color''#f00').focus();   
  75.             rs = false;   
  76.         }else{   
  77.             $(this).css('border-color''#386');   
  78.         }   
  79.     })   
  80.     return rs;   
  81. }   
  82. //整数字段检查   
  83. function intBlur(){   
  84.     var pattern = /^[\d]+$/;   
  85.     if(!$(this).val() || !pattern.exec($(this).val())) {   
  86.         $.tip('必须是一个整数');   
  87.         $(this).css('border-color''#f00').focus();   
  88.         return false;   
  89.     }else{   
  90.         $(this).css('border-color''#386');   
  91.         return true;   
  92.     }   
  93. }   
  94. //提交时的整数字段检查   
  95. function intCheck(data)   
  96. {   
  97.     var pattern = /^[\d]+$/;   
  98.     var rs = true;   
  99.     data.each(function (){   
  100.         if(!rs) return false;   
  101.         if(!$(this).val() || !pattern.exec($(this).val())) {   
  102.             $.tip('必须是一个整数');   
  103.             $(this).css('border-color''#f00').focus();   
  104.             rs = false;   
  105.         }else{   
  106.             $(this).css('border-color''#386');   
  107.         }   
  108.     })   
  109.     return rs;   
  110. }   
  111.   
  112. //判断两个值是否相等, 参数可以用id名或者jquery对象   
  113. function isEq(obj1, obj2)   
  114. {   
  115.     if(typeof obj1 == 'string') {   
  116.         obj1 = $('#'+obj1);   
  117.         obj2 = $('#'+obj2);   
  118.     }   
  119.     if(obj1.val() != obj2.val()) {   
  120.         obj2.css('border-color''#f00').focus();   
  121.         if(obj2.attr('alt') == 'fck') {   
  122.             obj2.val($.getFckVal(key));   
  123.         }   
  124.         obj2.change(function (){   
  125.             obj2.css('border-color''#386');   
  126.         });   
  127.         return false;   
  128.     }   
  129.     return true;   
  130. }  

里面用到了两个jquery扩展功能,$.getFckVal是取得fck编辑器的值(要用FCK的API才能取到FCK的最新值),$.tip是我写的提示插件,有三个参数:提示信息、延时时间,跳转URL。后两个参数是可选的并且顺序任意。

以上只是显示层的表单封装,其它方面的简化除了组件化模式其它的仍有待探索,组件化模式也是经历多次改版仍在变化中。

]]>
<![CDATA[编码提速之旅——数据操作层]]> http://www.xingyl.com/?m=blog&id=180 Thu, 10 Jul 2008 13:56:49 GMT 数据操作层也就是俗称的MVC中的M层,负责底层的数据操作,为控制层(C)服务。在数据库驱动的动态网站中,数据操作层主要工作仍是与数据库打交道,因此全力简化数据操作仍是王道。

保存或更新一条记录,在去年,我的操作步骤一般如下:

1.在控制层里解开POST数据并过滤.实际上代码更复杂多变一些但我去掉了无关代码。

PHP代码
  1. $name = filter::simple($_POST['name']);//姓名  
  2. $gender = intval($_POST['gender']);//性别  
  3. $address = filter::simple($_POST['address']);//地址  
  4. $explain = filter::fck($_POST['explain']);//个人简介  
  5. $email = filter::simple($_POST['email']);//Email  
  6. $QQ = filter::simple($_POST['QQ']);//QQ  
  7. $MSN = filter::simple($_POST['MSN']);//MSN  
  8. $phone = filter::simple($_POST['phone']);//电话/手机  
  9. $trait = intval($_POST['trait']);//工作性质  
  10. $exp = intval($_POST['exp']);//工作经验  
  11. $payMod = filter::simple($_POST['payMod']);//付款方式  
  12. $bool = person::addPerson($name,$gender,$address,$explain,$email,$QQ,$MSN,$phone,$trait,$exp,$payMod,$class,$workClass,$skillClass,$path);  

2.然后在数据层的person类addPerson方法中实现保存

PHP代码
  1. function addPerson($name,$gender,$address,$brief,$email,$QQ,$MSN,$phone,$workProperty,$exp,$payMod,$class,$workClass,$skillClass,$opus)  
  2.     {  
  3.         if(db()->dbCount('t_person_temp'"p_name = '$name' AND p_email = '$email'"'p_id') > 0) return false;  
  4.         $sql = "INSERT INTO t_person_temp 
  5.             (p_name,p_gender,p_address,p_explain,p_email,p_qq,p_msn,p_phone,p_trait, 
  6.             p_exp,p_payMod,p_bigSkill,p_middleSkill,p_smallSkill,p_opus) 
  7.             VALUES 
  8.             ('$name','$gender','$address','$brief','$email','$QQ','$MSN','$phone','$workProperty', 
  9.             '$exp','$payMod','$class','$workClass','$skillClass','$opus')";  
  10.         return db()->exec($sql);  
  11.     }  

看着是工工整整,人模人样的,随着熟练度的提升写起来也算相当顺手的。但日复一日的写着类似的代码,写得再熟练还是烦,总要花时间在检查拼写错误这些鸡毛蒜皮的事上,在今年二月份写完TP系统,在想像晨奇(同事)告诉我他曾操作过一百多个字段的表的情形时,俺终于崩溃了。

第二阶段:自动构建SQL (model类)

虽然仍无机会操纵一百多个字段的数据表,但为免英年早逝,身为天才的俺必须防患于未然,于是想啊啊,搜索得知“SHOW fields FROM $table"可以取得表字段名,字段类型和主键等信息,(如果没有与表字段对照,那么难免会将$_POST中的无关字段带入的sql中产生错误。)于是关键问题过了,