Auto Code

How to develope your app with this project.

前言

这个代码生成器其实并不难,我只是把一个简单的应有的东西理清罢了。如果你看了这个功能觉得很不错,想加入到你的系统里面,或者你的开发框架是其它的比如Yii、Ci、Yaf、Zend,或者跟PHP都不搭边,也没事,让我来告诉你怎么使用或者如何做你自己的代码生成器。

生成目标与内容

代码生成器生成的内容:

  • 基本MVC,完成CURD(增删改查)
  • 表间关系
  • 动态查询与显示

基本MVC

PHP的一些主流框架都可以做到通用的单表增删改查,这里不再赘述。 前台视图我提一下,最多就四个界面:列表、添加、修改和查看。 列表是表格,功能就一个显示和查询,难的地方在于自定义显示和查询的字段,这个后面具体会提到。 添加和修改界面生成时只要将数据库表字段类型与表单类型对应即可,比如varchar生成文本框,enum生成列表框。

表间关系处理

这里以用户角色表为例,用户ID和角色ID分别是关联用户表和角色表

              CREATE TABLE `user_role` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `uid` int(11) unsigned NOT NULL COMMENT '用户ID',
  `rid` int(11) unsigned NOT NULL COMMENT '角色ID',
  PRIMARY KEY (`id`),
  UNIQUE KEY `index_uid_rid` (`uid`,`rid`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 COMMENT='用户角色'$$
            
生成时,我做了如下的处理:
1、查找当前表外键
从数据库表间关系中(这个可以查到)查找当前表的外键,然后前台生成添加、修改或者查询界面时这个字段就不再生成一个简单的文本框,而是生成一个引用框或者列表框。
2、设置外键字段在列表中的显示字段。
前台列表界面显示时,如果这个外键是要显示的字段,不能直接显示,否则显示的就是用户ID或者角色ID,应该显示的字段是用户名和角色名称,这个叫显示字段。我做的生成器是这个字段可以在表间关系界面中设置,默认显示第二个字段(一般第一个字段是主键)。然后在列表中显示时显示用户名。
3、在Model中,写入用户表和角色表的关联和显示字段。
将以上两条所提到的信息保存下来,用于编辑和列表界面的数据显示。

动态查询与显示

想要动态,也就是随时可以修改和重新设置,那么就得把查询和显示的字段信息作为参数存下来,不能硬编码写到代码里面。条件 动态显示是这样,动态查询的话稍微复杂一点,因为查询条件的处理要复杂一点:查询的匹配方式主要有三种,等于、模糊和区间查询,区间查询的话需要生成2个控件,这个让用户在选择查询字段的时候选择。如果是外键字段,那么生成一个查找带回框或者列表框都可以。

最终的效果

设计表及字段

通过代码生成,我们希望的是不用写一行代码就能让设计器本身提供基本的操作数据功能和界面,为了达到这个目的,你需要在设计数据库时提供尽可能详尽的信息:字段说明以及表关系

在列表界面中,字段说明用于填充表格的列标头th中的内容(用户名、姓名、邮箱);添加界面中的字段说明也是一样。

建立数据表

CREATE TABLE `user` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `email` varchar(100) DEFAULT NULL COMMENT 'email',
  `display_name` varchar(50) DEFAULT NULL COMMENT '姓名',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8$$
          

最终生成的界面和效果

列表界面

用户名 姓名 email 状态 操作
admin 管理员 admin@admin.com
user 用户 user@user.com
... ... ...
... ... ...

添加界面

Heads up! 在建立表结构时,请为字段提供 COMMENT 字段说明,用于生成代码时填充列表界面的标头和添加界面中的字段说明。

<div class="control-group">
<label class="control-label" for="字段ID">字段说明</label>
<div class="controls">
<input type="text" value="字段默认值" name="字段ID" id="字段ID" class="{验证类型}" placeholder="">
<label class="help-inline">提示文字</label>
</div>
</div>

数据库表字段类型与表单类型

生成时能够根据字段类型自动生成对应的表单元素,对应关系如下

数据表字段 表单元素 前台验证
主键PRIhidden 
char,varchartext 
text,blobarea 
enumselect 
bitcheckbox 
int,decimal,float,doubletext{number:true,range:[下限,上限]'}
data,datetimedatedateISO:true
NOT NULL {required:true}
其它等待您的扩展 

模块列表

模块列表显示了当前数据库中的所有表,选择修改进入表单设计器,可以修改数据表对应的模块名、所属分组以及显示字段等。

名称
标题
模块
分组
记录
数据长度
索引大小
引擎
字符集
操作
userUserAdmin0163840InnoDButf8_general_ci
........................

表单设计器

用于后台生成tpl模板和Model,主要设计以下功能:

  • 列表界面显示字段,查询字段、显示顺序,是否允许插入及更新
  • 添加界面中的显示字段,顺序,字段对应的表单元素,前台验证规则,提示信息,后台自动验证和自动生成规则
  • 表间关系引用

插入列和更新列对应Model中的 $insertFields$updateFields

验证规则 和 自动生成 对应Model中的 $_validate$_auto

具体文档详见ThinkPHP官方文档 插入及更新过滤 自动验证 自动完成

表名
分组 模块
字段 列表 添加
引用 名称 标题 类型 插入 更新 只读 顺序 显示 查询 表单类型 显示 顺序 前台验证 提示
id int(11) unsigned
username varchar(50)
email varchar(100)
password varchar(45)
display_name varchar(50)
sex enum('男','女','未知')
phone varchar(50)
card_id varchar(18)
photo varchar(255)
qq varchar(20)
weibo varchar(100)
register_time datetime
last_login_time datetime
status varchar(255)

动态查询

在列表列中有一列“查询”,用来设置某个字段是否是查询字段。设置可查询后,在前台的列表中,会生成查询条件。样式如下

用户名 类型 是否 时间范围 -

查询条件生成规则

控件类型:同添加界面的控件类型

数据来源(主要是select和引用的数据来源)

  • 数据表字段的enmu类型值
  • 函数,比如获取某个表字段的所有值
  • 引用,引用的字段做为查询条件时,弹出引用对话框,选择即可

匹配方式 参照表达式查询 ,实际使用时,只用前3种:eq(默认),between和like

表达式 含义
EQ 等于(=)
BETWEEN 区间查询
LIKE 模糊查询
NEQ 不等于(<>)
GT 大于(>)
EGT 大于等于(>=)
LT 小于(<)
ELT 小于等于(<=)
[NOT] IN (不在)IN 查询
EXP 表达式查询,支持SQL语法

匹配方式between时系统会生成2个控件

表间关系

在建立表结构时,如果同时建立了表间的关联,则系统在生成模板时能够自动生成表关系。否则,需要在这里进行表间关系设计。

比如,在文章表article与用户表user关联,每个文章都属于一个用户,一个用户有多篇文章, 用sql关系表达就是 article.user_id=user.id

ThinkPHP官方文档 关联模型

以下这个表间关系设计界面是把关联模型和表间关系设计界面合并了,如果无需用到关联模型,仅仅只用引用某个表用于添加和列表中显示,那么按照下面这样操作即可

关联条件留空即表示 关联主键=关联外键,比如这里 article.user_id=user.id

显示字段表示,通过查找带回时显示的字段

关联表名
关联类型
被关联表
映射名称

关联主键
关联条件
关联外键
显示字段

关联顺序
关联条目
关联字段
映射字段

关联键1关联类型关联键2关联条件显示字段 关联字段映射名称映射字段关联条目关联顺序
article.user_idBELONGS_TOuser.idusernameuser

表间关系生成效果

在article的添加界面中,user_id就应该作为引用字段,单击后面的查找按钮时,弹出User模块的引用,文本框内的内容为“显示字段”,在这里是username

<div class="control-group">
  <label class="control-label" for="user_id">作者</label>
  <div class="controls">
    <div class="input-append" id="broadband_refer_user_1">
        <input type="hidden" value="" name="id" id="id" data-target="user_id" class="{digits:true,range:[-2147483648,2147483647]}" placeholder="">
        <input type="text" value="" name="username" id="user_name" data-target="user_name" class="{digits:true,required}" placeholder="" readonly="">
        <a data-target="#modal-refer" href="/User/refer/article_refer_user_1.htm" data-toggle="modal"><span class="add-on"><i class="icon-search"></i></span></a>
    </div>
  <label class="help-inline"></label>
  </div>
</div>

默认代码模板

系统会生成4个文件到对应的文件夹下,暂不支持独立分组

  • ./Lib/Model/分组/XxxModel.class.php
  • ./Lib/Action/分组/XxxAction.class.php
  • ./Lib/Tpl/分组/Xxx/add.html
  • ./Lib/Tpl/分组/Xxx/edit.html

生成的Action类继承CommAction,提供了以下方法,配合生成的模板,能够完整的完成所提供的功能:
默认 index
引用 refer
日志 log
添加 add
查看 view
编辑 edit
保存 save
删除 delete
导入 import
导出 export
上传 upload
扩展 operate

系统生成类位于Lib/Comm/CodeAction.class.php,你可以通过扩展加入你想要的通用功能。

如果你不想要某些只具有CommAction功能的Action类实体文件,可以通过在在配置文件中设置 空模块 空操作 来将这些模块定向到EmptyAction(位于CodeAction同目录下)

表名
class UserModel extends AdvModel {
    protected $fields =  array (
        //0 => 'id', 
        //1 => 'name', 
        //'_autoinc' => true, 
        //'_pk' => 'id', 
        //'_type' => array ( 
        //    'id' => 'int(11) unsigned', 
        //    'name' => 'varchar(50)', 
        //),
        $data.fields}
    );

    protected $insertFields = array();
    protected $updateFields = array();
    protected $readonlyField = array();

    public $viewFields = array(
        //'Category'=>array('title'=>'category_name', '_on'=>'Blog.category_id=Category.id','_type'=>'INNER'),
            );

    protected $_filter = array(
        //'过滤的字段'=>array('写入过滤规则','读取过滤规则',是否传入整个数据对象),
            )

    protected $_validate = array(
        //array(验证字段,验证规则,错误提示,[验证条件,附加规则,验证时间])
            )

    protected $_auto = array ( 
        //array(填充字段,填充内容,[填充条件,附加规则])
            }

    protected $_map = array(
        //'txt_user' =>'username', //把表单中的text_user字段映射到数据表的username字段
            )

    protected $_scope = array(
        'default'=>array(
            //'table'->array(''=>''),
            //'field'->array(''=>''),
            //'where'=>array(''=>''),
            //'order'=>array(''=>''),
            //'limit'->array(''=>''),
        ),
    )
}
Class UserModel extends CommAction{

}
<div class="row-fluid">
<form class="form-inline form-horizontal" action="{:U('save')}" method="post" id='form'>
<div class="control-group">
    <label class="control-label" for="id"></label>
    <div class="controls">
<input type="hidden" value="" name="id" id="id" class="{required:true,digits:true,range:[0,4294967295]}" placeholder="">        <label class="help-inline"></label>
    </div>
</div><div class="control-group">
    <label class="control-label" for="username">用户名</label>
    <div class="controls">
        <input type="text" class="{required:true,maxlength:50,}"  value="" name="username" id="username" placeholder="">        <label class="help-inline"></label>
    </div>
</div><div class="control-group">
    <label class="control-label" for="email">email</label>
    <div class="controls">
        <input type="text" class="{maxlength:100,}"  value="" name="email" id="email" placeholder="">        <label class="help-inline"></label>
    </div>
</div><div class="control-group">
    <label class="control-label" for="password">密码</label>
    <div class="controls">
        <input type="text" class="{maxlength:45,}"  value="" name="password" id="password" placeholder="">        <label class="help-inline"></label>
    </div>
</div><div class="control-group">
    <label class="control-label" for="display_name">姓名</label>
    <div class="controls">
        <input type="text" class="{maxlength:50,}"  value="" name="display_name" id="display_name" placeholder="">        <label class="help-inline"></label>
    </div>
</div><div class="control-group">
    <label class="control-label" for="sex">性别</label>
    <div class="controls">
<select id="sex" name="sex" class="selected" data-value="未知">
                            <option value ="男">男</option>            <option value ="女">女</option>            <option value ="未知">未知</option>        </select>        <label class="help-inline"></label>
    </div>
</div><div class="control-group">
    <label class="control-label" for="phone">手机</label>
    <div class="controls">
        <input type="text" class="{maxlength:50,}"  value="" name="phone" id="phone" placeholder="">        <label class="help-inline"></label>
    </div>
</div><div class="control-group">
    <label class="control-label" for="card_id">身份证号</label>
    <div class="controls">
        <input type="text" class="{maxlength:18,}"  value="" name="card_id" id="card_id" placeholder="">        <label class="help-inline"></label>
    </div>
</div><div class="control-group">
    <label class="control-label" for="photo">头像</label>
    <div class="controls">
        <input type="text" class="{maxlength:255,}"  value="" name="photo" id="photo" placeholder="">        <label class="help-inline"></label>
    </div>
</div><div class="control-group">
    <label class="control-label" for="qq">qq</label>
    <div class="controls">
        <input type="text" class="{maxlength:20,}"  value="" name="qq" id="qq" placeholder="">        <label class="help-inline"></label>
    </div>
</div><div class="control-group">
    <label class="control-label" for="weibo">微博</label>
    <div class="controls">
        <input type="text" class="{maxlength:100,}"  value="" name="weibo" id="weibo" placeholder="">        <label class="help-inline"></label>
    </div>
</div><div class="control-group">
    <label class="control-label" for="register_time">注册时间</label>
    <div class="controls">
<input type="text" class="{dateISO:true,date:true,}" name="register_time" id="register_time"  rel="datepicker" data-date="" data-date-format="yyyy-mm-dd">        <label class="help-inline"></label>
    </div>
</div><div class="control-group">
    <label class="control-label" for="last_login_time">上次登录</label>
    <div class="controls">
<input type="text" class="{dateISO:true,date:true,}" name="last_login_time" id="last_login_time"  rel="datepicker" data-date="" data-date-format="yyyy-mm-dd">        <label class="help-inline"></label>
    </div>
</div><div class="control-group">
    <label class="control-label" for="status">状态</label>
    <div class="controls">
        <input type="text" class="{maxlength:255,}"  value="" name="status" id="status" placeholder="">        <label class="help-inline"></label>
    </div>
</div>
</form>
</div>
<div class="row-fluid">
<form class="form-inline form-horizontal" action="{:U('save')}" method="post" id='form'>
<div class="control-group">
    <label class="control-label" for="id"></label>
    <div class="controls">
<input type="hidden" value="{$data.id}" name="id" id="id" class="{required:true,digits:true,range:[0,4294967295]}" placeholder="">        <label class="help-inline"></label>
    </div>
</div><div class="control-group">
    <label class="control-label" for="username">用户名</label>
    <div class="controls">
        <input type="text" class="{required:true,maxlength:50,}"  value="{$data.username}" name="username" id="username" placeholder="">        <label class="help-inline"></label>
    </div>
</div><div class="control-group">
    <label class="control-label" for="email">email</label>
    <div class="controls">
        <input type="text" class="{maxlength:100,}"  value="{$data.email}" name="email" id="email" placeholder="">        <label class="help-inline"></label>
    </div>
</div><div class="control-group">
    <label class="control-label" for="password">密码</label>
    <div class="controls">
        <input type="text" class="{maxlength:45,}"  value="{$data.password}" name="password" id="password" placeholder="">        <label class="help-inline"></label>
    </div>
</div><div class="control-group">
    <label class="control-label" for="display_name">姓名</label>
    <div class="controls">
        <input type="text" class="{maxlength:50,}"  value="{$data.display_name}" name="display_name" id="display_name" placeholder="">        <label class="help-inline"></label>
    </div>
</div><div class="control-group">
    <label class="control-label" for="sex">性别</label>
    <div class="controls">
<select id="sex" name="sex" class="selected" data-value="{$data.sex}">
                            <option value ="男">男</option>            <option value ="女">女</option>            <option value ="未知">未知</option>        </select>        <label class="help-inline"></label>
    </div>
</div><div class="control-group">
    <label class="control-label" for="phone">手机</label>
    <div class="controls">
        <input type="text" class="{maxlength:50,}"  value="{$data.phone}" name="phone" id="phone" placeholder="">        <label class="help-inline"></label>
    </div>
</div><div class="control-group">
    <label class="control-label" for="card_id">身份证号</label>
    <div class="controls">
        <input type="text" class="{maxlength:18,}"  value="{$data.card_id}" name="card_id" id="card_id" placeholder="">        <label class="help-inline"></label>
    </div>
</div><div class="control-group">
    <label class="control-label" for="photo">头像</label>
    <div class="controls">
        <input type="text" class="{maxlength:255,}"  value="{$data.photo}" name="photo" id="photo" placeholder="">        <label class="help-inline"></label>
    </div>
</div><div class="control-group">
    <label class="control-label" for="qq">qq</label>
    <div class="controls">
        <input type="text" class="{maxlength:20,}"  value="{$data.qq}" name="qq" id="qq" placeholder="">        <label class="help-inline"></label>
    </div>
</div><div class="control-group">
    <label class="control-label" for="weibo">微博</label>
    <div class="controls">
        <input type="text" class="{maxlength:100,}"  value="{$data.weibo}" name="weibo" id="weibo" placeholder="">        <label class="help-inline"></label>
    </div>
</div><div class="control-group">
    <label class="control-label" for="register_time">注册时间</label>
    <div class="controls">
<input type="text" value="{$data.register_time}" class="{dateISO:true,date:true,}" name="register_time" id="register_time"  rel="datepicker" data-date="" data-date-format="yyyy-mm-dd">        <label class="help-inline"></label>
    </div>
</div><div class="control-group">
    <label class="control-label" for="last_login_time">上次登录</label>
    <div class="controls">
<input type="text" value="{$data.last_login_time}" class="{dateISO:true,date:true,}" name="last_login_time" id="last_login_time"  rel="datepicker" data-date="" data-date-format="yyyy-mm-dd">        <label class="help-inline"></label>
    </div>
</div><div class="control-group">
    <label class="control-label" for="status">状态</label>
    <div class="controls">
        <input type="text" class="{maxlength:255,}"  value="{$data.status}" name="status" id="status" placeholder="">        <label class="help-inline"></label>
    </div>
</div>
</form>
</div>

自定义开发

系统生成代码后,你需要根据自己的业务对代码进行二次开发和扩展,完成自定义开发后,你可能需要将整个系统的模块和操作生成RBAC的节点数据,以便进行权限或菜单管理。

在模块管理界面中,选择模块后单击生成节点即可。

原理

通过读取APP_GROUP_LIST,获取所有分组,然后遍历各个分组下以*Action.class.php的文件,建立该Action对象,获取该对象的所有方法,过滤掉ThinkPHP本身提供的底层方法,获取到的便是用户的操作方法列表,插入到数据库中即可。

public function getFunction($module){
    if(empty($module))return null;
    $action=A($module);
    $functions=get_class_methods($action);
    //ThinkPHP的Action底层方法
    $inherents_functions = array(
      '_initialize','__construct','getActionName','isAjax','display','show','fetch',
      'buildHtml','assign','__set','get','__get','__isset',
      '__call','error','success','ajaxReturn','redirect','__destruct'
    );

    foreach ($functions as $func){
      if(!in_array($func, $inherents_functions)){
        $customer_functions[]=$func;
      }
    }
    return $customer_functions;
  }