Home  >  Article  >  php教程  >  CRUD生成器DBuilder设计与实现

CRUD生成器DBuilder设计与实现

WBOY
WBOYOriginal
2016-07-06 13:28:20813browse

源码位于github:https://github.com/lvyahui8/dbuilder.git 。文中图片如果太小看不清楚,请右键点击“在新标签页中打开”即可看到原图

第一章           引言

1.1 研究背景及意义

计算机软件技术发展至今,数据库已成为最广泛使用的存储格式化数据的媒介,数据库程序开发技术也日趋完善,大型的ORM框架使得数据库程序开发变得简单,并已成为操作关系型数据库的主流方式。数据库程序主要代码为CRUD(create, retrieve, update, delete)代码,随着ORM框架功能的完善,编写CRUD代码也衍生其固定的流程,对不同数据库表进行操作的CRUD代码也存在高度可重用性。当前编写重复性的CRUD代码成为开发人员的常态,不仅严重降低其积极性,而且损失其开发效率,所以迫切需要一种能够快速生成CRUD代码的产品,以期减少这方面的工作,提高开发效率。

1.2 研究现状

目前国外已经诞生一些解决上述需求的、具有很高可用性的CRUD生成器产品:CrudKit,CRUD-Admin-Generator,Dadabik,GroceryCrud,SximoBuilder。这些产品各有其特点,但也有一共同点:都是基于PHP进行开发(这在一定程度上决定于PHP语法的灵活性及其解析性)。SximoBuilder是其中的典型代表,尽管SximoBuilder设计独特、可用性高、流行度高,但也存在如下不足之处:

  • 不支持自定义表单控件;
  • 不支持多数据库;
  • 验证规则不完善,不支持异步验证;
  • 代码冗余度极大。

然而对于当今日益复杂的web程序,上述几点是开发过程必须考虑的问题,因此,开发一款既具有SximoBuilder现有功能、又完善其不足之处的CRUD生成器产品,势在必行。

1.3 研究内容

基于国内外CRUD生成器研究现状,笔者开发一款名为DBuilder(D为DataAdministrator的简写)的CRUD 生成器。

DBuilder借鉴SximoBuilder的模块为代码单元、由模板生成代码的思想,但拥有与SximoBuilder完全不同的代码生成器逻辑。它在实现SximoBuilder核心的代码生成、通用CRUD两种功能的基础上,通过重写代码逻辑完善其不足之处:代码冗余度大、缺少前端验证。

第二章           DBuilder系统分析

DBuilder面向的主要用户人群为web后台管理员以及开发人员,因此其系统分析过程,将更多的站在web后台管理员及开发人员的角度考虑问题。

2.1 需求分析

项目需要实现如下几点核心功能。

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等等。

2.2 数据原型分析

按照需求提取可得实体有:用户、用户组、数据源、代码模块、菜单,关系有:权限、日志。实体与关系的含义如下:

  • 用户:表示使用DBuilder的用户;
  • 用户组:表示用户的类型分组,用户类型应该至少包括访客、管理员、超级管理员三种;
  • 数据源:表示DBuilder包含的数据库配置,一个数据源的配置包含连接一个数据库所需的基本参数;
  • 代码模块:表示DBuilder生成的代码模块,描述了代码文件和配置;
  • 菜单:表示DBuilder的左侧菜单项;
  • 权限:表示用户组对每个代码模块的各种操作权限;
  • 日志:表示用户对每个代码模块的CRUD访问日志。

实体与关系的ER图如下:

 

图2-1 ER图

2.3 原则性要求

DBuilder应该要做成一套高性能、高可用的CRUD生成器,为此DBuilder设计中应该符合下面几项原则:

  • DBuilder要精确到每个数据库字段可配置;
  • 应具备一个WEB后台应用的雏形,使用户可在此基础上快速建立完整的WEB后台应用;
  • DBuilder要尽可能减少SQL操作,必要时可借助缓存、异步等技术,减少请求的处理逻辑,提高页面效率,减少用户等待时间;
  • DBuilder要有美观、简洁、直观的用户界面;
  • DBuilder要留有大量的扩展接口,能够让用户通过二次开发快速实现较为复杂的功能。

第三章           DBuilder系统设计

3.1 系统架构

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流程做详细阐述。

3.1.1 Core CRUD 模块

Core CRUD 模块实现核心CRUD操作,一切对GModule MVC中Controller的CRUD请求,最终转交至Core CRUD 模块进行处理。Core CRUD 模块会开放一些预处理和后处理接口交由GModule实现,这些接口会在Model,Controller,View上都有体现。

Core CRUD 模块主要包括如下文件

  • app/controllers/admin/AdminController.php
  • app/models/BaseModel.php
  • app/config/crud/admin.php
  • app/views/admin/core/list.blade.php
  • app/views/admin/core/form.blade.php

Core CRUD 模块读取GModule Configuration实现真正的CRUD操作。

3.1.2 GModule

GModule(Generated Module)不但实现了Core CRUD Module接口(MVC代码),而且具有自己配置文件(CRUD Configuration)。每一GModule表示以一张数据库表为主表,具备CRUD功能的代码文件合集(包括对应的MVC + Configuration代码)。譬如,DBuilder生成的一个GModule, 主表为core数据源user表,名字为User,那么User GModule应包含下面代码文件:

  • controllers/UserController.php
  • models/User.php
  • views/user/_list.blade.php
  • views/user/_form.blade.php
  • views/user/view.blade.php
  • config/crud/user.php

代码文件命名取决于GModule的名字,故为保证生成的代码文件不冲突,取GModule的名字(GModule Key,GModule Name)作为GModule的唯一标识。每一个GModule的信息都被保存在数据库中。一次新建 GModule操作将会新建上述所有代码文件,更新相关文件,并插入一条GModule记录到数据库。一次更新 GModule操作将只会更新Configuration文件。

GModule 由MVC代码和CRUD Configuration代码组成,下面分别进行阐述:

  • MVC代码:用来实现扩展接口。CRUD请求应最先路由到GModule MVC的中的Controller(控制器)。并且GModule MVC 应与Core CRUD Module的MVC代码有继承关系。
  • CRUD Configuration代码:实现对GModule主表增删改查参数的配置。该文件放置在app/config/crud/目录下,以php array的格式定义。它包含对所有字段的表单,列表,视图,关系等参数的配置,以及全局的参数配置。

GModule并不表示具体某一个模块,而是代指一类模块,这种模块可以由DBuilder生成,或者由开发人员手工建立。它主要用来实现Core CRUD Module的接口,主要包括下述几部分

1)        Controller接口

假设GModule模块的 Controller为A,Core CRUD Module 的Controller为B,则A应继承自B。CRUD请求会先路由到A,而实际的处理者是B。A会实现B开放的下列接口。

  • beforeListExcuteQuery(&querier):该接口在List查询器执行查询之前调用,传递的参数为查询器引用。用来在查询之前,绑定特殊的查询参数。
  • beforeList(&data):该接口在List查询器执行之后,渲染List视图之前调用。传递的参数为视图参数引用,其中包括查询出的model集合。用来对查询的model 集合做后处理,或者对list视图绑定一些Module专有的参数。
  • beforeEditExcuteQuery(&querier):该接口在Edit请求中Model查询器执行查询之前调用,传递的是查询器引用。用来绑定查询model需要的特殊参数。
  • beforeEdit(&data):该接口在Edit中Model查询器执行之后,渲染视图之前调用,传递的是视图参数引用,其中包括查询器查询出的model。用来做渲染前的预处理。
  • afterSave(&model):该接口在Edit中,保存编辑的之后调用,传递的是保存在数据库中,最新的数据库记录持久化的model。用来对model做一些复杂的后级联处理。
  • beforeView(data): 该接口在View请求中,View 查询器查询之后调用,传递的是视图参数的引用。用来对视图显示做预处理。

2)        Model 接口

GModule MVC代码中的Model也继承自BaseModel,实现 BaseModel类开放的一些接口可以完成扩展。

formatXXXAttribute():该接口用来格式化某个字段。本产品基于Laravel,其已经具备类似的接口,就是getXXXXAttribute()。但这样的接口的优先级比字段优先级高,这在特殊的情况下为开发带来了不便,所以再设计一个类似的接口,该接口的优先级低于字段本身。

3)        View 接口

视图的扩展接口与前两者不同,主要体现在子视图与视图块上,也就是在Core CURD模块的视图基础上,扩展视图组件。默认Core CRUD MVC视图生成的是一个表格或者一个表单,占满页面。而View接口将提供在该表格上下左右扩展页面组件的能力。

4)        Configuration

每一个GModule对应一个Configuration文件,其中包含GModule对主表各个字段的配置参数,以及布局参数。

3.1.3 模块关系

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操作。

3.1.4 Build 与 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实现预处理/后处理接口达到扩展性的目的。

3.2 Core数据源

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)

 

用户名

email

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

 

可导出

3.3 数据源管理模块

DBuilder需要支持多数据源,多种类型数据库。数据源信息保存在d_database表中。考虑到数据库操作是频繁操作,如果将数据源信息保存在数据库中,则每次数据库操作将多一次数据源查询操作,这样做浪费性能。那么DBuilder不应该把数据源信息保存在数据库中,而应该保存在代码文件中。数据源管理的信息包括数据源名称(数据源的唯一标识,DBuilder默认的数据源名为core)、数据库类型、地址、端口、数据库名、用户名、密码等等信息。因为数据源管理模块并不对表进行增删改查操作,所以数据源管理模块并不是一个GModule模块。该模块的代码完全手工编写。

3.4 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路由。

3.5 Core CRUD 模块

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 表单在用户输入完成点击保存之后,要分下面几步:

  • 根据字段配置的验证规则进行验证;
  • 应判断Module Configuration 中的relation进行分析,进行必要的级联操作;
  • 并要调用自定义控件的onSave方法;
  • 最后才应更新或新建主表数据;
  • 跳转:更新或新建成功跳转至List,失败跳转至Form。

Form 还需要开放对应的预处理和后处理接口。

3)        列表List(Table)

List是一个分页Table,按照Module Configuration 中的字段配置显示分页数据。支持列表搜索,排序,勾选删除,导出等功能;

  • 分页展现数据以InitQuerier模块得到的Model作为查询器,结合分页,查询出基本的数据列表。分页类型为全页刷新类型(非异步分页);
  • List搜索:支持在Module Configuration中定义了search不等于false的字段作为搜索条件。搜索关系为逻辑与的关系。并反映在GET参数上。搜索输入控件根据字段的form type来定。在Form 中定义为select,radio,checkbox控件的字段,在List中都将使用select控件作为输入控件;
  • List 排序:以在Module Configuration中定义了form.sort 不等于 false的字段作为可排序字段。排序只支持按单一字段排序,降序方式含升序和降序;
  • List 多选操作主要支持多选删除,多选复制操作,任何删除操作都需确认;
  • List 数据每行记录的支持的操作按Module Configuration中的配置给出,默认支持编辑,删除,查看三项操作;
  • List 也要开放预处理/后处理接口给Module CRUD MVC。

4)        查看View

View 暂时以Form为基础,提供预处理后处理接口,但不允许编辑。

第四章           DBuilder系统实现

4.1 目录结构

代码按照前段资源、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的目录。

4.2 GModule 配置文件

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>
Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn