源码位于github:https://github.com/lvyahui8/dbuilder.git 。文中图片如果太小看不清楚,请右键点击“在新标签页中打开”即可看到原图
计算机软件技术发展至今,数据库已成为最广泛使用的存储格式化数据的媒介,数据库程序开发技术也日趋完善,大型的ORM框架使得数据库程序开发变得简单,并已成为操作关系型数据库的主流方式。数据库程序主要代码为CRUD(create, retrieve, update, delete)代码,随着ORM框架功能的完善,编写CRUD代码也衍生其固定的流程,对不同数据库表进行操作的CRUD代码也存在高度可重用性。当前编写重复性的CRUD代码成为开发人员的常态,不仅严重降低其积极性,而且损失其开发效率,所以迫切需要一种能够快速生成CRUD代码的产品,以期减少这方面的工作,提高开发效率。
目前国外已经诞生一些解决上述需求的、具有很高可用性的CRUD生成器产品:CrudKit,CRUD-Admin-Generator,Dadabik,GroceryCrud,SximoBuilder。这些产品各有其特点,但也有一共同点:都是基于PHP进行开发(这在一定程度上决定于PHP语法的灵活性及其解析性)。SximoBuilder是其中的典型代表,尽管SximoBuilder设计独特、可用性高、流行度高,但也存在如下不足之处:
然而对于当今日益复杂的web程序,上述几点是开发过程必须考虑的问题,因此,开发一款既具有SximoBuilder现有功能、又完善其不足之处的CRUD生成器产品,势在必行。
基于国内外CRUD生成器研究现状,笔者开发一款名为DBuilder(D为DataAdministrator的简写)的CRUD 生成器。
DBuilder借鉴SximoBuilder的模块为代码单元、由模板生成代码的思想,但拥有与SximoBuilder完全不同的代码生成器逻辑。它在实现SximoBuilder核心的代码生成、通用CRUD两种功能的基础上,通过重写代码逻辑完善其不足之处:代码冗余度大、缺少前端验证。
DBuilder面向的主要用户人群为web后台管理员以及开发人员,因此其系统分析过程,将更多的站在web后台管理员及开发人员的角度考虑问题。
项目需要实现如下几点核心功能。
1) 数据源管理
用户可以在界面为项目配置多个数据源。配置的数据源信息包括数据库类型、地址、数据库名、端口、用户名、密码等信息。支持对数据源的增删改查,保证对数据源的增删改查不轻易造成系统问题。
2) 代码生成
此功能是DBuilder的核心要实现的功能,用户在选择数据源和数据表之后,能够对数据库表的字段做简单配置,配置包括Form表单配置、List(Table)列表配置、关系配置、全局属性配置。配置完成后DBuilder要能生成对数据库表的CRUD的MVC代码,即需要实现CRUD可用功能,但不用编写代码。
3) 数据库表CRUD
生成的代码必须支持数据表记录的新建、删除、更新、查询操作。
4) 菜单管理
DBuilder要能将生成的代码跟一个菜单项绑定,让用户点击菜单项之后,就可以使用DBuilder生成的CRUD功能。此菜单必须包括后台菜单,前台菜单不是必须的。
5) 用户管理
用户要实现多种角色。必须能够以邮箱为用户唯一标识,并作为登录参数。未来还要实现支持QQ、微信、新浪微博基于OAuth2.0的互联登录。
6) 权限管理
DBuilder要能实现不同用户角色对不同CRUD代码的执行、访问权限做到三维的可配置。譬如,现有一个文章管理的CRUD功能模块,用户角色分为系统管理员(SuperAdmin),管理员(Admin),访客(Guest),那么DBuilder要能实现如下的三维权限配置,且将之作为所有Module的默认权限。
表2-1 Module权限配置表
用户组与权限 |
查看 |
编辑 |
删除 |
导出 |
SuperAdmin |
√ |
√ |
√ |
√ |
Admin |
√ |
√ |
√ |
|
Guest |
√ |
|
|
|
7) 站点参数配置
DBuilder作为一个网站的web后台程序,对站点的全局参数配置也是必须的,这些参数包括网站名字、关键词、联系地址、友情链接等等。
8) 操作日志
DBuilder要记录用户的操作信息,包括访问的页面、执行的CRUD类型、时间等等信息。日志的记录形式支持数据库和文件两种方式。
9) 多语言支持
DBuilder要支持多国语言的切换。至少应该支持中文和英语两种语言,且以中文为默认。
10) 多数据库类型支持
DBuilder要支持多种类型数据库,暂时主要支持关系型数据库,包括mysql,MS SqlServer,oracle,PostGreSQL等等。
按照需求提取可得实体有:用户、用户组、数据源、代码模块、菜单,关系有:权限、日志。实体与关系的含义如下:
实体与关系的ER图如下:
图2-1 ER图
DBuilder应该要做成一套高性能、高可用的CRUD生成器,为此DBuilder设计中应该符合下面几项原则:
DBuilder有下面2个核心的构件Core CRUD 模块和GModule,GModule对Core CRUD 模块有继承依赖的关系,GModule由MVC Code和CRUD Config组成;Core CRUD模块是手工编写的代码,而GModule是DBuilder生成的代码;Core CRUD 模块实现CRUD操作,GModule实现扩展功能。下图表示了这两个构件的组成和关系
图3-1概念与构件
下面对图中设计的概念、构件、模块关系以及Build与CRUD流程做详细阐述。
Core CRUD 模块实现核心CRUD操作,一切对GModule MVC中Controller的CRUD请求,最终转交至Core CRUD 模块进行处理。Core CRUD 模块会开放一些预处理和后处理接口交由GModule实现,这些接口会在Model,Controller,View上都有体现。
Core CRUD 模块主要包括如下文件
Core CRUD 模块读取GModule Configuration实现真正的CRUD操作。
GModule(Generated Module)不但实现了Core CRUD Module接口(MVC代码),而且具有自己配置文件(CRUD Configuration)。每一GModule表示以一张数据库表为主表,具备CRUD功能的代码文件合集(包括对应的MVC + Configuration代码)。譬如,DBuilder生成的一个GModule, 主表为core数据源user表,名字为User,那么User GModule应包含下面代码文件:
代码文件命名取决于GModule的名字,故为保证生成的代码文件不冲突,取GModule的名字(GModule Key,GModule Name)作为GModule的唯一标识。每一个GModule的信息都被保存在数据库中。一次新建 GModule操作将会新建上述所有代码文件,更新相关文件,并插入一条GModule记录到数据库。一次更新 GModule操作将只会更新Configuration文件。
GModule 由MVC代码和CRUD Configuration代码组成,下面分别进行阐述:
GModule并不表示具体某一个模块,而是代指一类模块,这种模块可以由DBuilder生成,或者由开发人员手工建立。它主要用来实现Core CRUD Module的接口,主要包括下述几部分
1) Controller接口
假设GModule模块的 Controller为A,Core CRUD Module 的Controller为B,则A应继承自B。CRUD请求会先路由到A,而实际的处理者是B。A会实现B开放的下列接口。
2) Model 接口
GModule MVC代码中的Model也继承自BaseModel,实现 BaseModel类开放的一些接口可以完成扩展。
formatXXXAttribute():该接口用来格式化某个字段。本产品基于Laravel,其已经具备类似的接口,就是getXXXXAttribute()。但这样的接口的优先级比字段优先级高,这在特殊的情况下为开发带来了不便,所以再设计一个类似的接口,该接口的优先级低于字段本身。
3) View 接口
视图的扩展接口与前两者不同,主要体现在子视图与视图块上,也就是在Core CURD模块的视图基础上,扩展视图组件。默认Core CRUD MVC视图生成的是一个表格或者一个表单,占满页面。而View接口将提供在该表格上下左右扩展页面组件的能力。
4) Configuration
每一个GModule对应一个Configuration文件,其中包含GModule对主表各个字段的配置参数,以及布局参数。
CRUD请求路由到GModule的Controller,GModule代码实现Core CRUD MVC开放的接口,而由Core CRUD Module去真正实现对数据库的CRUD操作。每一个GModule的信息应该被记录在数据库表中,以便给GModule关联菜单,控制权限,记录操作日志等等。一些主要模块之间的关系如下图所示。
图3-2模块关系
从图2-2中可以看到,由GModule管理模块根据用户配置来生成一个GModule A,当用户的CRUD请求到达GModule A时,GModule 会讲请求转交Core CRUD进行处理,Core CRUD 模块再以SQL对数据库进行CRUD操作。
DBuilder项目的方案,将真正的CRUD操作交给了Core CRUD Module去执行,CRUD参数由GET或者POST请求参数与GModule Configuration构成,而GModule的MVC代码只是去实现Core CRUD MVC开放的一些预处理或者后处理接口。
图2-3是DBuilder最核心的流程图,包含Module的生成和处理CRUD请求的过程,图2-4是SximoBuilder 中Module的生成和处理CRUD请求的流程图。
图3-3 DBuilder 代码生成和处理CRUD的流程
图3-4 SximoBuilder 代码生成和处理CRUD的流程
对比两者,可以看到两者的最大区别,是DBuilder复用一份CRUD代码,而不是像Sximo那样为每一个Module生成一套可以当独执行的CRUD代码。这样做的好处是提高了复用性,并通过Module CRUD MVC实现预处理/后处理接口达到扩展性的目的。
Core数据源是DBuilder的默认数据源,其类型为mysql,数据库名为dbuilder,本节按照《数据原型分析》一节进行详细的数据库设计。为提高程序性能,数据源信息保存在代码文件app/config/datasource.php中,文件内容如下:
'core' => array ( 'driver' => 'mysql', 'host' => 'localhost', 'database' => 'dbuilder', 'username' => 'root', 'password' => 'root', 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', 'prefix' => '', 'edit' => false, 'port' => 3306, ), // more data source ); |
其中Core数据源有下述数据表:
1) d_menu 表:表示后台左侧树形菜单,每一个可点击跳转的菜单项必须与一个Module进行关联。
表3-1 web后台左侧菜单表
field |
type |
default |
info |
id |
int |
auto_increment |
PRI |
module_id |
int |
|
|
module_name |
varchar(12) |
|
|
parent_id |
|
null |
父菜单项 |
title |
varchar(12) |
module_title |
显示名称 |
_order |
int |
0 |
排序字段 |
2) d_module 表:记录了module信息,每一条d_module表的记录代表了DBuilder生成的一个Module。
表3-2 module信息描述
field |
type |
default |
info |
id |
int |
auto_increment |
PRI |
name |
varchar(32) |
|
UIN |
title |
varchar(32) |
|
module标题 |
note |
varchar(32) |
|
module 说明 |
db_source |
varchar(16) |
core |
数据源名称 |
db_table |
varchar(16) |
|
module主表 |
db_table_key |
varchar(16) |
|
主表PRI |
3) d_user 表:保存着使用后台程序的用户。
表3-3 web后台用户
field |
type |
default |
info |
id |
int |
auto_increment |
PRI |
username |
varhcar(64) |
|
用户名 |
|
varchar(64) |
|
邮箱 |
password |
varchar(64) |
|
HASH |
salt |
varchar(64) |
|
盐 |
last_login |
timestamp |
|
最后登录时间 |
remember_token |
|
|
记住密码口令 |
group_id |
int |
|
组ID |
4) d_group表:表示对后台用户的分组信息。
表3-4 用户分组表
field |
type |
default |
info |
id |
int |
auto_increment |
PRI |
name |
varhcar(64) |
|
组名 |
note |
varchar(128) |
|
组说明 |
level |
int |
|
组级别 |
5) d_group_access表:记录了每个GModule、不同后台用户组与各种操作权限的三维权限信息。
表3-5 用户组对Module权限表
field |
type |
default |
info |
id |
int |
auto_increment |
PRI |
group_id |
int |
|
组id |
module_id |
int |
|
Module模块ID |
edit |
int |
1 |
可编辑 |
view |
int |
1 |
可查看 |
delete |
int |
1 |
可删除 |
export |
int |
1 |
可导出 |
6) d_log表:记录了每个用户的操作日志。
表3-6 用户操作日志
field |
type |
default |
info |
id |
int |
auto_increment |
PRI |
user_id |
int |
|
用户id |
ip_addr |
varchar(15) |
|
客户端IP |
module_id |
int |
|
访问的moduleid |
module_title |
varchar(16) |
|
|
task |
varchar(16) |
|
操作 |
created_at |
timestamp |
|
可导出 |
DBuilder需要支持多数据源,多种类型数据库。数据源信息保存在d_database表中。考虑到数据库操作是频繁操作,如果将数据源信息保存在数据库中,则每次数据库操作将多一次数据源查询操作,这样做浪费性能。那么DBuilder不应该把数据源信息保存在数据库中,而应该保存在代码文件中。数据源管理的信息包括数据源名称(数据源的唯一标识,DBuilder默认的数据源名为core)、数据库类型、地址、端口、数据库名、用户名、密码等等信息。因为数据源管理模块并不对表进行增删改查操作,所以数据源管理模块并不是一个GModule模块。该模块的代码完全手工编写。
DBuilder将以基于名字为“Module”的GModule作为生成GModule的用户接口,该模块称作GModule管理模块,换言之GModule管理模块本身就是一个GModule,该GModule的主表即是core数据源中保存GModule信息的数据库表,改GModule的名字为“Module”。GModule 管理模块包含创建,更新和删除GModule 的所有代码文件以及数据库记录。GModule的新建和删除需要更新全局的GModule路由。
1) GModule 路由
GModule路由定义在一个独立的代码文件中,为一个以GModule名字进行减号分词并全部小写的字符串为键(譬如:GModule名字为OrderItem,则键值为order-item)、以Module中Controller类的类名为值的map字典,GModule路由是全局的。
2) GModule 新建&更新
新建GModule将在数据库中生成一条记录、生成所有的module文件、并更新路由。更新操作只修改配置文件。新建与更新都使用相同的编辑视图,此编辑视图是对GModule Configuration的图形化配置界面。
3) GModule 删除
GModule删除将删除所有的GModule MVC代码,删除GModule Configuration代码,删除数据库表记录,并更新GModule路由。
Core CRUD 模块是DBuilder处理CRUD请求的实际处理者,它由下述几部分组成:
1) 参数解析初始化
初始化Model,实例化一个Module的Model对象作为初始化查询器。加载Module Configuration,对未设置的值进行设置默认值,对参数进行汇聚。
2) 表单Form
主要包括新建和更新功能。根据GModule主表主键primaryKey是否设置判断是新建还是更新操作。下图是Form模块的流程
图3-5 Form执行流程
Form 分两部分,第一部分渲染Form页面给用户填写。第二部分为Form保存。
渲染Form页面需要考虑的有Form控件和有外键关系的字段要怎么处理。Form控件需要支持类型包括text、text_date、text_datetime、textarea、select、radio、checkbox、file、hidden、address以及custom,自定义控件应该继承FormControl类,自定义控件的渲染由控件的render方法完成。Form渲染需要判断有关系的字段做辅助加载。比如对post(文章)表进行编辑,post表有一个字段为category_id,表示文章的栏目ID,对应category(栏目)表的id字段。这时需要对category_id使用select,radio,checkbox控件进行加载,方便用户输入。比如使用select控件,那么应该将category.id作为option的value,将category.name作为option中的text。这样做也是为了方便用户输入。此步骤与List中搜索时有共性,因此代码可复用。
Form 保存需要考虑一些自定义控件的保存,自定义控件的数保存由自定义控件类的onSave方法完成。Form 保存还需要考虑关系的保存,默认应该级联更新附属表。Form 表单在用户输入完成点击保存之后,要分下面几步:
Form 还需要开放对应的预处理和后处理接口。
3) 列表List(Table)
List是一个分页Table,按照Module Configuration 中的字段配置显示分页数据。支持列表搜索,排序,勾选删除,导出等功能;
4) 查看View
View 暂时以Form为基础,提供预处理后处理接口,但不允许编辑。
代码按照前段资源、MVC、Configuration、Library等概念进行了分目录存放。下面表格中给出了主要目录的说明:
表4-2 代码主要目录
目录 |
作用 |
assets |
此目录存放着各种各样的前端资源。包括bootstrap,以及自定义的css和js文件。 |
plugins |
存放特殊前端插件的目录,比如富文本编辑器,视音频插件等等。 |
app/controllers/admin |
存放着MVC中控制器的目录。其中,DBuilder的核心在admin目录下。 |
app/models |
存放着MVC中模型(Model)的目录。用来做数据库查询用。 |
app/views |
存放着MVC中视图的目录。文件名以*.blade.php的格式命名。 |
app/library |
存放PHP辅助类,PHP库的目录。 |
app/config/crud |
存放Module Configuration的目录。 |
GModule配置文件定义了GModule的参数,该文件保存在app/config/crud/下,是以GModule Name进行蛇形分词得到的字符串命名的php文件(譬如:一GModule的名字为OrderItem,则GModule配置文件为order_item.php)。配置参数以数组格式返回。
考虑到PHP数组在表格中呈现的美观性,对参数以配置中的Key=>Value形式,以点分形式Key.Value表示。
表4-3 root配置
Configuration Key |
类型 |
默认值 |
含义 |
fields |
array |
array() |
字段列表 |
fields.field_name |
array |
array() |
对field_name字段的配置 |
fields.field_name.label |
string |
UP(field_name) |
显示在列表表格的表头的内容,和form控件旁边的内容 |
fields.field_name.form |
array |
array() |
field_name字段的表单配置,具体参考 fields.field_name.form配置 |
fields.field_name.list |
array |
array() |
field_name 字段的列表配置,具体参考 fields.field_name.list配置
|
fields.field_name. relation |
array |
array() |
field_name 字段的关系 |
表4-3中每个字段的表单配置说明如下表所示:
表4-4 fields.field_name.form配置
Configuration Key |
值类型 |
默认 |
含义 |
type |
string |
text |
Form控件类型 |
show |
bool |
true |
是否出现在表单 |
hidden |
bool |
false |
是否以隐藏的空间在表单中 |
rule |
string |
required |
验证规则 |
ajax_validate |
bool |
false |
是否异步验证 |
placeholder |
string |
|
控件中的提示 |
表4-3中每个字段的列表配置说明如下表所示:
表4-5 fields.field_name.list配置
Configuration Key |
值类型 |
默认 |
含义 |
show |
bool |
true |
是否出现在表单 |
sort |
bool |
true |
字段是否可以排序,默认可排序 |
search |
bool,array |
array() |
是否可搜索以及搜索规则 |
|
string |
|
控件中的提示 |
表4-3中每个字段的关系配置说明如下表所示:
表4-6 fields.field_name.relation配置
Configuration Key |
值类型 |
默认 |
含义 |
table |
string |
|
关联表 |
foreign_key |
string |
id |
对应关联表里的字段 |
show |
string |
|
关联表里的一个字段,当需要转义时,将用该字段代替field_nae字段显示 |
as |
string |
table_show |
转义查询出的值用哪个字段表示,主要为了防止主表和关联表有重复字段 |
下面是一个名为post的GModule的Configuration文件
<span style="color: #008080;"> 1</span> <?php <span style="color: #0000ff;">return <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;"> 2</span> 'data_source' => 'core', <span style="color: #008080;"> 3</span> 'table' => 'post', <span style="color: #008080;"> 4</span> 'fields' => <span style="color: #008080;"> 5</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;"> 6</span> 'id' => <span style="color: #008080;"> 7</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;"> 8</span> 'label' => 'ID', <span style="color: #008080;"> 9</span> 'form' => <span style="color: #008080;"> 10</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;"> 11</span> 'show' => <span style="color: #0000ff;">true</span>, <span style="color: #008080;"> 12</span> 'hidden' => <span style="color: #0000ff;">true</span>, <span style="color: #008080;"> 13</span> 'type' => 'text', <span style="color: #008080;"> 14</span> 'rule' => 'required', <span style="color: #008080;"> 15</span> 'ajax_validate' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;"> 16</span> 'placeholder' => '', <span style="color: #008080;"> 17</span> ), <span style="color: #008080;"> 18</span> 'list' => <span style="color: #008080;"> 19</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;"> 20</span> 'show' => <span style="color: #0000ff;">true</span>, <span style="color: #008080;"> 21</span> 'sort' => <span style="color: #0000ff;">true</span>, <span style="color: #008080;"> 22</span> 'search' => '=', <span style="color: #008080;"> 23</span> 'lookup' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;"> 24</span> ), <span style="color: #008080;"> 25</span> 'relation' => <span style="color: #008080;"> 26</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;"> 27</span> 'type' => '', <span style="color: #008080;"> 28</span> 'table' => '', <span style="color: #008080;"> 29</span> 'foreign_key' => '', <span style="color: #008080;"> 30</span> 'show' => '', <span style="color: #008080;"> 31</span> 'as' => '', <span style="color: #008080;"> 32</span> ), <span style="color: #008080;"> 33</span> ), <span style="color: #008080;"> 34</span> 'title' => <span style="color: #008080;"> 35</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;"> 36</span> 'label' => '标题', <span style="color: #008080;"> 37</span> 'form' => <span style="color: #008080;"> 38</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;"> 39</span> 'show' => <span style="color: #0000ff;">true</span>, <span style="color: #008080;"> 40</span> 'hidden' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;"> 41</span> 'type' => 'text', <span style="color: #008080;"> 42</span> 'rule' => 'required', <span style="color: #008080;"> 43</span> 'ajax_validate' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;"> 44</span> 'placeholder' => '', <span style="color: #008080;"> 45</span> ), <span style="color: #008080;"> 46</span> 'list' => <span style="color: #008080;"> 47</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;"> 48</span> 'show' => <span style="color: #0000ff;">true</span>, <span style="color: #008080;"> 49</span> 'sort' => <span style="color: #0000ff;">true</span>, <span style="color: #008080;"> 50</span> 'search' => '=', <span style="color: #008080;"> 51</span> 'lookup' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;"> 52</span> ), <span style="color: #008080;"> 53</span> 'relation' => <span style="color: #008080;"> 54</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;"> 55</span> 'type' => '', <span style="color: #008080;"> 56</span> 'table' => '', <span style="color: #008080;"> 57</span> 'foreign_key' => '', <span style="color: #008080;"> 58</span> 'show' => '', <span style="color: #008080;"> 59</span> 'as' => '', <span style="color: #008080;"> 60</span> ), <span style="color: #008080;"> 61</span> ), <span style="color: #008080;"> 62</span> 'short' => <span style="color: #008080;"> 63</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;"> 64</span> 'label' => '摘要', <span style="color: #008080;"> 65</span> 'form' => <span style="color: #008080;"> 66</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;"> 67</span> 'show' => <span style="color: #0000ff;">true</span>, <span style="color: #008080;"> 68</span> 'hidden' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;"> 69</span> 'type' => 'textarea', <span style="color: #008080;"> 70</span> 'rule' => 'required', <span style="color: #008080;"> 71</span> 'ajax_validate' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;"> 72</span> 'placeholder' => '', <span style="color: #008080;"> 73</span> ), <span style="color: #008080;"> 74</span> 'list' => <span style="color: #008080;"> 75</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;"> 76</span> 'show' => <span style="color: #0000ff;">true</span>, <span style="color: #008080;"> 77</span> 'sort' => <span style="color: #0000ff;">true</span>, <span style="color: #008080;"> 78</span> 'search' => '=', <span style="color: #008080;"> 79</span> 'lookup' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;"> 80</span> ), <span style="color: #008080;"> 81</span> 'relation' => <span style="color: #008080;"> 82</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;"> 83</span> 'type' => '', <span style="color: #008080;"> 84</span> 'table' => 'category', <span style="color: #008080;"> 85</span> 'foreign_key' => 'id', <span style="color: #008080;"> 86</span> 'show' => 'id', <span style="color: #008080;"> 87</span> 'as' => '', <span style="color: #008080;"> 88</span> ), <span style="color: #008080;"> 89</span> ), <span style="color: #008080;"> 90</span> 'content' => <span style="color: #008080;"> 91</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;"> 92</span> 'label' => '正文', <span style="color: #008080;"> 93</span> 'form' => <span style="color: #008080;"> 94</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;"> 95</span> 'show' => <span style="color: #0000ff;">true</span>, <span style="color: #008080;"> 96</span> 'hidden' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;"> 97</span> 'type' => 'wysiwyg', <span style="color: #008080;"> 98</span> 'rule' => 'required', <span style="color: #008080;"> 99</span> 'ajax_validate' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;">100</span> 'placeholder' => '', <span style="color: #008080;">101</span> ), <span style="color: #008080;">102</span> 'list' => <span style="color: #008080;">103</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">104</span> 'show' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;">105</span> 'sort' => <span style="color: #0000ff;">true</span>, <span style="color: #008080;">106</span> 'search' => '=', <span style="color: #008080;">107</span> 'lookup' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;">108</span> ), <span style="color: #008080;">109</span> 'relation' => <span style="color: #008080;">110</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">111</span> 'type' => '', <span style="color: #008080;">112</span> 'table' => 'category', <span style="color: #008080;">113</span> 'foreign_key' => 'id', <span style="color: #008080;">114</span> 'show' => 'id', <span style="color: #008080;">115</span> 'as' => '', <span style="color: #008080;">116</span> ), <span style="color: #008080;">117</span> ), <span style="color: #008080;">118</span> 'view_ct' => <span style="color: #008080;">119</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">120</span> 'label' => '查看次数', <span style="color: #008080;">121</span> 'form' => <span style="color: #008080;">122</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">123</span> 'show' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;">124</span> 'hidden' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;">125</span> 'type' => 'text', <span style="color: #008080;">126</span> 'rule' => 'required', <span style="color: #008080;">127</span> 'ajax_validate' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;">128</span> 'placeholder' => '', <span style="color: #008080;">129</span> ), <span style="color: #008080;">130</span> 'list' => <span style="color: #008080;">131</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">132</span> 'show' => <span style="color: #0000ff;">true</span>, <span style="color: #008080;">133</span> 'sort' => <span style="color: #0000ff;">true</span>, <span style="color: #008080;">134</span> 'search' => '=', <span style="color: #008080;">135</span> 'lookup' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;">136</span> ), <span style="color: #008080;">137</span> 'relation' => <span style="color: #008080;">138</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">139</span> 'type' => '', <span style="color: #008080;">140</span> 'table' => '', <span style="color: #008080;">141</span> 'foreign_key' => '', <span style="color: #008080;">142</span> 'show' => '', <span style="color: #008080;">143</span> 'as' => '', <span style="color: #008080;">144</span> ), <span style="color: #008080;">145</span> ), <span style="color: #008080;">146</span> 'created_at' => <span style="color: #008080;">147</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">148</span> 'label' => '创建时间', <span style="color: #008080;">149</span> 'form' => <span style="color: #008080;">150</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">151</span> 'show' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;">152</span> 'hidden' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;">153</span> 'type' => 'text', <span style="color: #008080;">154</span> 'rule' => 'required', <span style="color: #008080;">155</span> 'ajax_validate' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;">156</span> 'placeholder' => '', <span style="color: #008080;">157</span> ), <span style="color: #008080;">158</span> 'list' => <span style="color: #008080;">159</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">160</span> 'show' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;">161</span> 'sort' => <span style="color: #0000ff;">true</span>, <span style="color: #008080;">162</span> 'search' => '=', <span style="color: #008080;">163</span> 'lookup' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;">164</span> ), <span style="color: #008080;">165</span> 'relation' => <span style="color: #008080;">166</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">167</span> 'type' => '', <span style="color: #008080;">168</span> 'table' => '', <span style="color: #008080;">169</span> 'foreign_key' => '', <span style="color: #008080;">170</span> 'show' => '', <span style="color: #008080;">171</span> 'as' => '', <span style="color: #008080;">172</span> ), <span style="color: #008080;">173</span> ), <span style="color: #008080;">174</span> 'updated_at' => <span style="color: #008080;">175</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">176</span> 'label' => '更新时间', <span style="color: #008080;">177</span> 'form' => <span style="color: #008080;">178</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">179</span> 'show' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;">180</span> 'hidden' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;">181</span> 'type' => 'text', <span style="color: #008080;">182</span> 'rule' => 'required', <span style="color: #008080;">183</span> 'ajax_validate' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;">184</span> 'placeholder' => '', <span style="color: #008080;">185</span> ), <span style="color: #008080;">186</span> 'list' => <span style="color: #008080;">187</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">188</span> 'show' => <span style="color: #0000ff;">true</span>, <span style="color: #008080;">189</span> 'sort' => <span style="color: #0000ff;">true</span>, <span style="color: #008080;">190</span> 'search' => '=', <span style="color: #008080;">191</span> 'lookup' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;">192</span> ), <span style="color: #008080;">193</span> 'relation' => <span style="color: #008080;">194</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">195</span> 'type' => '', <span style="color: #008080;">196</span> 'table' => '', <span style="color: #008080;">197</span> 'foreign_key' => '', <span style="color: #008080;">198</span> 'show' => '', <span style="color: #008080;">199</span> 'as' => '', <span style="color: #008080;">200</span> ), <span style="color: #008080;">201</span> ), <span style="color: #008080;">202</span> 'category_id' => <span style="color: #008080;">203</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">204</span> 'label' => '栏目', <span style="color: #008080;">205</span> 'form' => <span style="color: #008080;">206</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">207</span> 'show' => <span style="color: #0000ff;">true</span>, <span style="color: #008080;">208</span> 'hidden' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;">209</span> 'type' => 'select', <span style="color: #008080;">210</span> 'rule' => 'required', <span style="color: #008080;">211</span> 'ajax_validate' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;">212</span> 'placeholder' => '', <span style="color: #008080;">213</span> ), <span style="color: #008080;">214</span> 'list' => <span style="color: #008080;">215</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">216</span> 'show' => <span style="color: #0000ff;">true</span>, <span style="color: #008080;">217</span> 'sort' => <span style="color: #0000ff;">true</span>, <span style="color: #008080;">218</span> 'search' => '=', <span style="color: #008080;">219</span> 'lookup' => <span style="color: #0000ff;">false</span>, <span style="color: #008080;">220</span> ), <span style="color: #008080;">221</span> 'relation' => <span style="color: #008080;">222</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">223</span> 'type' => 'belongsTo', <span style="color: #008080;">224</span> 'table' => 'category', <span style="color: #008080;">225</span> 'foreign_key' => 'id', <span style="color: #008080;">226</span> 'show' => 'title', <span style="color: #008080;">227</span> 'as' => 'category_title', <span style="color: #008080;">228</span> ), <span style="color: #008080;">229</span> ), <span style="color: #008080;">230</span> ), <span style="color: #008080;">231</span> 'list_options' => <span style="color: #008080;">232</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">233</span> 'page' => 10, <span style="color: #008080;">234</span> 'create' => <span style="color: #0000ff;">true</span>, <span style="color: #008080;">235</span> 'update' => <span style="color: #0000ff;">true</span>, <span style="color: #008080;">236</span> 'delete' => <span style="color: #0000ff;">true</span>, <span style="color: #008080;">237</span> ), <span style="color: #008080;">238</span> 'form_options' => <span style="color: #008080;">239</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">240</span> 'layout' => <span style="color: #008080;">241</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">242</span> 'cols' => 12, <span style="color: #008080;">243</span> 'label_cols' => 1, <span style="color: #008080;">244</span> 'input_cols' => 11, <span style="color: #008080;">245</span> ), <span style="color: #008080;">246</span> ), <span style="color: #008080;">247</span> 'relations' => <span style="color: #008080;">248</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">249</span> 'id' => <span style="color: #008080;">250</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">251</span> 'type' => '', <span style="color: #008080;">252</span> 'table' => '', <span style="color: #008080;">253</span> 'foreign_key' => '', <span style="color: #008080;">254</span> 'show' => '', <span style="color: #008080;">255</span> 'as' => '', <span style="color: #008080;">256</span> ), <span style="color: #008080;">257</span> 'title' => <span style="color: #008080;">258</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">259</span> 'type' => '', <span style="color: #008080;">260</span> 'table' => '', <span style="color: #008080;">261</span> 'foreign_key' => '', <span style="color: #008080;">262</span> 'show' => '', <span style="color: #008080;">263</span> 'as' => '', <span style="color: #008080;">264</span> ), <span style="color: #008080;">265</span> 'short' => <span style="color: #008080;">266</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">267</span> 'type' => '', <span style="color: #008080;">268</span> 'table' => '', <span style="color: #008080;">269</span> 'foreign_key' => '', <span style="color: #008080;">270</span> 'show' => '', <span style="color: #008080;">271</span> 'as' => '', <span style="color: #008080;">272</span> ), <span style="color: #008080;">273</span> 'content' => <span style="color: #008080;">274</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">275</span> 'type' => '', <span style="color: #008080;">276</span> 'table' => '', <span style="color: #008080;">277</span> 'foreign_key' => '', <span style="color: #008080;">278</span> 'show' => '', <span style="color: #008080;">279</span> 'as' => '', <span style="color: #008080;">280</span> ), <span style="color: #008080;">281</span> 'view_ct' => <span style="color: #008080;">282</span> <span style="color: #0000ff;">array</span><span style="color: #000000;"> ( </span><span style="color: #008080;">283</span> 'type' => '', <span style="color: #008080;">284</span> 'table' => '', <span style="color: #008080;">285</span> 'foreign_key' => '', <span style="color: #008080;">286</span> 'show' => '', <span ></span>