CRUD生成器DBuilder設計與實現

源碼位於github:https://github.com/lvyahui8/dbuilder.git 。文中圖片若是過小看不清楚,請右鍵點擊「在新標籤頁中打開」便可看到原圖php

有興趣還能夠加QQ羣交流:146103720  DBuilder交流羣
css

第一章           引言

1.1 研究背景及意義

計算機軟件技術發展至今,數據庫已成爲最普遍使用的存儲格式化數據的媒介,數據庫程序開發技術也日趨完善,大型的ORM框架使得數據庫程序開發變得簡單,並已成爲操做關係型數據庫的主流方式。數據庫程序主要代碼爲CRUD(create, retrieve, update, delete)代碼,隨着ORM框架功能的完善,編寫CRUD代碼也衍生其固定的流程,對不一樣數據庫表進行操做的CRUD代碼也存在高度可重用性。當前編寫重複性的CRUD代碼成爲開發人員的常態,不只嚴重下降其積極性,並且損失其開發效率,因此迫切須要一種可以快速生成CRUD代碼的產品,以期減小這方面的工做,提升開發效率。html

1.2 研究現狀

目前國外已經誕生一些解決上述需求的、具備很高可用性的CRUD生成器產品:CrudKit,CRUD-Admin-Generator,Dadabik,GroceryCrud,SximoBuilder。這些產品各有其特色,但也有一共同點:都是基於PHP進行開發(這在必定程度上決定於PHP語法的靈活性及其解析性)。SximoBuilder是其中的典型表明,儘管SximoBuilder設計獨特、可用性高、流行度高,但也存在以下不足之處:前端

  • 不支持自定義表單控件;
  • 不支持多數據庫;
  • 驗證規則不完善,不支持異步驗證;
  • 代碼冗餘度極大。

然而對於當今日益複雜的web程序,上述幾點是開發過程必須考慮的問題,所以,開發一款既具備SximoBuilder現有功能、又完善其不足之處的CRUD生成器產品,勢在必行。mysql

1.3 研究內容

基於國內外CRUD生成器研究現狀,筆者開發一款名爲DBuilder(D爲DataAdministrator的簡寫)的CRUD 生成器。jquery

DBuilder借鑑SximoBuilder的模塊爲代碼單元、由模板生成代碼的思想,但擁有與SximoBuilder徹底不一樣的代碼生成器邏輯。它在實現SximoBuilder核心的代碼生成、通用CRUD兩種功能的基礎上,經過重寫代碼邏輯完善其不足之處:代碼冗餘度大、缺乏前端驗證。nginx

第二章           DBuilder系統分析

DBuilder面向的主要用戶人羣爲web後臺管理員以及開發人員,所以其系統分析過程,將更多的站在web後臺管理員及開發人員的角度考慮問題。git

2.1 需求分析

項目須要實現以下幾點核心功能。github

1)        數據源管理web

用戶能夠在界面爲項目配置多個數據源。配置的數據源信息包括數據庫類型、地址、數據庫名、端口、用戶名、密碼等信息。支持對數據源的增刪改查,保證對數據源的增刪改查不輕易形成系統問題。

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中,文件內容以下:

<?php return array (

    '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文件

  1 <?php return array (
  2   'data_source' => 'core',
  3   'table' => 'post',
  4   'fields' => 
  5   array (
  6     'id' => 
  7     array (
  8       'label' => 'ID',
  9       'form' => 
 10       array (
 11         'show' => true,
 12         'hidden' => true,
 13         'type' => 'text',
 14         'rule' => 'required',
 15         'ajax_validate' => false,
 16         'placeholder' => '',
 17       ),
 18       'list' => 
 19       array (
 20         'show' => true,
 21         'sort' => true,
 22         'search' => '=',
 23         'lookup' => false,
 24       ),
 25       'relation' => 
 26       array (
 27         'type' => '',
 28         'table' => '',
 29         'foreign_key' => '',
 30         'show' => '',
 31         'as' => '',
 32       ),
 33     ),
 34     'title' => 
 35     array (
 36       'label' => '標題',
 37       'form' => 
 38       array (
 39         'show' => true,
 40         'hidden' => false,
 41         'type' => 'text',
 42         'rule' => 'required',
 43         'ajax_validate' => false,
 44         'placeholder' => '',
 45       ),
 46       'list' => 
 47       array (
 48         'show' => true,
 49         'sort' => true,
 50         'search' => '=',
 51         'lookup' => false,
 52       ),
 53       'relation' => 
 54       array (
 55         'type' => '',
 56         'table' => '',
 57         'foreign_key' => '',
 58         'show' => '',
 59         'as' => '',
 60       ),
 61     ),
 62     'short' => 
 63     array (
 64       'label' => '摘要',
 65       'form' => 
 66       array (
 67         'show' => true,
 68         'hidden' => false,
 69         'type' => 'textarea',
 70         'rule' => 'required',
 71         'ajax_validate' => false,
 72         'placeholder' => '',
 73       ),
 74       'list' => 
 75       array (
 76         'show' => true,
 77         'sort' => true,
 78         'search' => '=',
 79         'lookup' => false,
 80       ),
 81       'relation' => 
 82       array (
 83         'type' => '',
 84         'table' => 'category',
 85         'foreign_key' => 'id',
 86         'show' => 'id',
 87         'as' => '',
 88       ),
 89     ),
 90     'content' => 
 91     array (
 92       'label' => '正文',
 93       'form' => 
 94       array (
 95         'show' => true,
 96         'hidden' => false,
 97         'type' => 'wysiwyg',
 98         'rule' => 'required',
 99         'ajax_validate' => false,
100         'placeholder' => '',
101       ),
102       'list' => 
103       array (
104         'show' => false,
105         'sort' => true,
106         'search' => '=',
107         'lookup' => false,
108       ),
109       'relation' => 
110       array (
111         'type' => '',
112         'table' => 'category',
113         'foreign_key' => 'id',
114         'show' => 'id',
115         'as' => '',
116       ),
117     ),
118     'view_ct' => 
119     array (
120       'label' => '查看次數',
121       'form' => 
122       array (
123         'show' => false,
124         'hidden' => false,
125         'type' => 'text',
126         'rule' => 'required',
127         'ajax_validate' => false,
128         'placeholder' => '',
129       ),
130       'list' => 
131       array (
132         'show' => true,
133         'sort' => true,
134         'search' => '=',
135         'lookup' => false,
136       ),
137       'relation' => 
138       array (
139         'type' => '',
140         'table' => '',
141         'foreign_key' => '',
142         'show' => '',
143         'as' => '',
144       ),
145     ),
146     'created_at' => 
147     array (
148       'label' => '建立時間',
149       'form' => 
150       array (
151         'show' => false,
152         'hidden' => false,
153         'type' => 'text',
154         'rule' => 'required',
155         'ajax_validate' => false,
156         'placeholder' => '',
157       ),
158       'list' => 
159       array (
160         'show' => false,
161         'sort' => true,
162         'search' => '=',
163         'lookup' => false,
164       ),
165       'relation' => 
166       array (
167         'type' => '',
168         'table' => '',
169         'foreign_key' => '',
170         'show' => '',
171         'as' => '',
172       ),
173     ),
174     'updated_at' => 
175     array (
176       'label' => '更新時間',
177       'form' => 
178       array (
179         'show' => false,
180         'hidden' => false,
181         'type' => 'text',
182         'rule' => 'required',
183         'ajax_validate' => false,
184         'placeholder' => '',
185       ),
186       'list' => 
187       array (
188         'show' => true,
189         'sort' => true,
190         'search' => '=',
191         'lookup' => false,
192       ),
193       'relation' => 
194       array (
195         'type' => '',
196         'table' => '',
197         'foreign_key' => '',
198         'show' => '',
199         'as' => '',
200       ),
201     ),
202     'category_id' => 
203     array (
204       'label' => '欄目',
205       'form' => 
206       array (
207         'show' => true,
208         'hidden' => false,
209         'type' => 'select',
210         'rule' => 'required',
211         'ajax_validate' => false,
212         'placeholder' => '',
213       ),
214       'list' => 
215       array (
216         'show' => true,
217         'sort' => true,
218         'search' => '=',
219         'lookup' => false,
220       ),
221       'relation' => 
222       array (
223         'type' => 'belongsTo',
224         'table' => 'category',
225         'foreign_key' => 'id',
226         'show' => 'title',
227         'as' => 'category_title',
228       ),
229     ),
230   ),
231   'list_options' => 
232   array (
233     'page' => 10,
234     'create' => true,
235     'update' => true,
236     'delete' => true,
237   ),
238   'form_options' => 
239   array (
240     'layout' => 
241     array (
242       'cols' => 12,
243       'label_cols' => 1,
244       'input_cols' => 11,
245     ),
246   ),
247   'relations' => 
248   array (
249     'id' => 
250     array (
251       'type' => '',
252       'table' => '',
253       'foreign_key' => '',
254       'show' => '',
255       'as' => '',
256     ),
257     'title' => 
258     array (
259       'type' => '',
260       'table' => '',
261       'foreign_key' => '',
262       'show' => '',
263       'as' => '',
264     ),
265     'short' => 
266     array (
267       'type' => '',
268       'table' => '',
269       'foreign_key' => '',
270       'show' => '',
271       'as' => '',
272     ),
273     'content' => 
274     array (
275       'type' => '',
276       'table' => '',
277       'foreign_key' => '',
278       'show' => '',
279       'as' => '',
280     ),
281     'view_ct' => 
282     array (
283       'type' => '',
284       'table' => '',
285       'foreign_key' => '',
286       'show' => '',
287       'as' => '',
288     ),
289     'created_at' => 
290     array (
291       'type' => '',
292       'table' => '',
293       'foreign_key' => '',
294       'show' => '',
295       'as' => '',
296     ),
297     'updated_at' => 
298     array (
299       'type' => '',
300       'table' => '',
301       'foreign_key' => '',
302       'show' => '',
303       'as' => '',
304     ),
305     'category_id' => 
306     array (
307       'type' => '',
308       'table' => '',
309       'foreign_key' => '',
310       'show' => '',
311       'as' => '',
312     ),
313   ),
314 );
GModule Configuration

4.3 數據源管理模塊實現

數據源管理模塊完成基於網頁界面對app/config/datasource.php文件的配置。包含數據源列表頁,數據源新建與編輯頁。

  • 數據源列表頁:對應請求路徑爲admin/data-source/list,此頁面讀取DataSource文件渲染List(table)頁面,密碼不顯示。
  • 數據源編輯頁面:對應請求路徑爲 admin/data-source/edit,GET請求讀取DataSource問價內容渲染表單,POST請求將數據源參數輸出值DataSource文件。此頁面提供測試鏈接功能,實現方法爲基於表單的數據源參數鏈接數據庫,執行一條SQL語句,鏈接是否成功取決於SQL是否執行成功。

實現數據源管理的核心控制器代碼放在DataSourceController.php文件中。

  1 <?php
  2 /**
  3  * Created by PhpStorm.
  4  * User: lvyahui
  5  * Date: 2016/5/12
  6  * Time: 15:35
  7  */
  8 
  9 namespace admin;
 10 
 11 use BaseModel;
 12 use Illuminate\Support\Facades\Config;
 13 use Illuminate\Support\Facades\Redirect;
 14 use SiteHelpers;
 15 use Illuminate\Support\Facades\Response;
 16 use Illuminate\Support\Facades\Input;
 17 
 18 use PDOException;
 19 use PDO;
 20 class DataSourceController extends AdminController
 21 {
 22     /**
 23      * 呈現數據源列表
 24      */
 25     public function getList()
 26     {
 27         $datasources = SiteHelpers::loadDataSources();
 28         $this->makeView(array(
 29             'datasources' => $datasources,
 30         ));
 31     }
 32 
 33     /**
 34      * 異步加載某數據源的全部數據表
 35      * @return \Illuminate\Http\JsonResponse
 36      */
 37     public function getTables()
 38     {
 39         $dataSourceName = Input::get("data_source");
 40         $dataSources = SiteHelpers::loadDataSources();
 41 
 42         $dataSource = $dataSources[$dataSourceName];
 43         $tables = BaseModel::getTableList($dataSource['database'], $dataSourceName);
 44         return Response::json(array(
 45             'success' => true,
 46             'data'    => array(
 47                 'tables'   => $tables,
 48                 'selected' => Input::get('table'),
 49             ),
 50         ));
 51     }
 52 
 53     /**
 54      * 呈現數據源編輯或者新建FORM
 55      * @param null $slug
 56      */
 57     public function getEdit($slug = null)
 58     {
 59         $dataSource = null;
 60         if ($slug) {
 61             // 更新
 62             $dataSource = $slug === 'core' ? Config::get('database.connections.core')
 63                 : Config::get('datasource.' . $slug);
 64             $dataSource['name'] = $slug;
 65         } else {
 66             // 新建
 67             $dataSource = array(
 68                 'name'     => '',
 69                 'driver'   => 'mysql',
 70                 'host'     => 'localhost',
 71                 'port'     => 3306,
 72                 'database' => '',
 73                 'username' => 'root',
 74                 'password' => '',
 75                 'charset'  =>   'utf8',
 76                 'collation' =>  'utf8_unicode_ci'
 77             );
 78         }
 79 
 80         $this->makeView(array(
 81             'dataSource'    =>  $dataSource
 82         ));
 83     }
 84 
 85     /**
 86      * 測試數據源鏈接是否可靠
 87      * @return \Illuminate\Http\JsonResponse
 88      */
 89     public function postTest(){
 90         $success = true;
 91         try{
 92             $dsn = Input::get('driver').':'.Input::get('host').':'.Input::get('port').';dbname='.Input::get('database');
 93             $dbh = new \PDO($dsn,Input::get('username'),Input::get('password'));
 94 //            $connection = new Connection($dbh,Input::get('database'));
 95 //            $key = md5(date("Y-m-d H:i:s"));
 96 //            DB::addConnection($key,$connection);
 97 //            if(!DB::connection($key)->getDatabaseName()){
 98 //                $success = false;
 99 //            }
100             $dbh = null;
101         }catch(PDOException  $e){
102             $success = false;
103         }
104         return Response::json(array(
105             'success'   =>  $success,
106         ));
107     }
108 
109     /**
110      * 保存編輯好的數據源信息
111      * @param null $primaryKeyValue
112      * @return mixed
113      */
114     public function postEdit($primaryKeyValue = null)
115     {
116         $dataSources = SiteHelpers::loadDataSources();
117         $name = Input::get('name');
118         $dataSources[$name] = Input::all();
119         SiteHelpers::saveDataSources($dataSources);
120 
121         return Redirect::action(get_class($this).'@getList');
122     }
123 
124 
125     public function getTableFields(){
126         $connection = Input::get('connection');
127         $table = Input::get('table');
128 
129         $rawFields = BaseModel::getTableColumns($table,$connection);
130 
131         $fields = array();
132         $pri = null;
133         foreach($rawFields as $field){
134             if($field->Key === 'PRI'){
135                 $pri = $field->Field;
136             }
137             $fields [] = $field->Field;
138         }
139 
140         return Response::json(array(
141             'success'   =>  true,
142             'data'  =>  array(
143                 'fields'    =>  $fields,
144                 'pri'       =>  $pri,
145             ),
146         ));
147     }
148 }
DataSourceController

4.4 CoreCRUD 模塊實現

CoreCRUD模塊涉及的代碼文件極其做用以下說明。

  • app/controllers/admin/AdminController.php:CoreCRUD模塊的控制器,是CRUD操做核心的邏輯代碼。主要分析請求參數和Module參數,調用Model層,渲染視圖層,實現List呈現、List搜索排序、Form呈現、Form保存等功能;
  • app/models/BaseModel.php:CoreCRUD模塊中的模型,是ModuleCRUD模塊中模型的基類。未與表格關聯。定義了一些公共的Model默認屬性,以及一些靜態的數據庫操做方法,好比拉取數據庫表字段列表;
  • app/config/crud/admin.php:CoreCRUD模塊中的默認crud參數配置文件,但ModuleCRUD模塊中的配置文件未定義某些參數時,將使用admin.php中的默認參數;
  • app/views/admin/core/list.blade.php:CoreCRUD模塊中的列表視圖文件,用來呈現數據列表;
  • app/views/admin/core/form.blade.php:CoreCRUD模塊中的數據記錄編輯視圖文件,用來呈現數據編輯的表單。

代碼文件以下

  1 <?php
  2 
  3 namespace admin;
  4 
  5 use Illuminate\Support\Facades\Request;
  6 use Illuminate\Support\Facades\Response;
  7 use Illuminate\Support\Facades\Redirect;
  8 use Illuminate\Support\Facades\Input;
  9 use Illuminate\Support\Facades\Config;
 10 use Illuminate\Support\Facades\View;
 11 use Illuminate\Support\Facades\Cache;
 12 use Illuminate\Support\Facades\URL;
 13 use SiteHelpers;
 14 use Module;
 15 
 16 class AdminController extends \BaseController
 17 {
 18     protected $layout = 'layouts.admin.main';
 19 
 20     /**
 21      * AdminController constructor.
 22      */
 23     public function __construct()
 24     {
 25         parent::__construct();
 26         View::share('stdName',$this->getStdName());
 27         View::share('reducName',SiteHelpers::reducCase($this->getStdName()));
 28         View::share('routeParams',$this->getRouteParams());
 29         if($this->model){
 30             $this->assignModel($this->model);
 31         }
 32         View::share('config',$this->savedConfig);
 33         if(!Cache::has('modules')){
 34             Cache::forever('modules',Module::all());
 35         }
 36     }
 37 
 38     public function getList(){
 39         $models = $this->paginateModels();
 40         $view = $this->getRouteParam('c').'._list';
 41         if(!View::exists($view)){
 42             $view = 'admin.core.list';
 43         }
 44         if(Request::ajax() || Input::has('isAjax')){
 45             return Response::json(array(
 46                'success'    =>  true,
 47                 'data'      =>  array(
 48                     'models'    =>  $models->toArray()
 49                 )
 50             ));
 51         }else{
 52             $this->makeView(array(
 53                 'models'  =>  $models,
 54                 $this->getStdName().'s' =>  $models,
 55             ),$view);
 56         }
 57     }
 58 
 59     public function getEdit($id = null)
 60     {
 61         if($id){
 62             $this->model = $this->model->find($id);
 63         }
 64         $data = array(
 65             $this->model->getKeyName() => $id,
 66             'model'   =>  $this->model,
 67             $this->modelName=>$this->model,
 68         );
 69         $this->beforeEdit($data);
 70 
 71         $view = $this->getRouteParam('c').'._form';
 72         if(!View::exists($view)){
 73             $view = 'admin.core.form';
 74         }
 75         $this->makeView($data,$view);
 76     }
 77 
 78     protected function config(){
 79         $config = 'crud/'.$this->getStdName();
 80         if(!file_exists(app_path('config/').$config.'.php')){
 81             $config = 'crud/admin';
 82         }
 83         return Config::get($config);
 84     }
 85 
 86     protected function assignModel($model)
 87     {
 88         $this->model = $model;
 89 
 90         $config = $this->config();
 91         $defaultConfig = Config::get('crud/admin');
 92         $relations = array();
 93         /* 將默認參數傳遞給module config */
 94         foreach($config['fields']   as $field => &$fieldConfig){
 95 //            if(isset($fieldConfig['value'])){
 96 //                $this->model->$field = $fieldConfig['value'];
 97 //            }
 98             $fieldConfig['form'] = array_merge(
 99                 $defaultConfig['fields']['field_name']['form'],
100                 isset($fieldConfig['form']) ? $fieldConfig['form'] : array()
101             );
102             $fieldConfig['list'] = array_merge(
103                 $defaultConfig['fields']['field_name']['list'],
104                 isset($fieldConfig['list']) ? $fieldConfig['list'] : array()
105             );
106             if(isset($fieldConfig['relation']) &&
107                 isset($fieldConfig['relation']['type']) && $fieldConfig['relation']['type'] !== '' ){
108                 $relations[$field] = $fieldConfig['relation'];
109             }
110         }
111 
112         $config['list_options'] = array_merge(
113             $defaultConfig['list_options'],
114             isset($config['list_options']) ? $config['list_options'] : array()
115         );
116 
117         $config['form_options'] = array_merge(
118             $defaultConfig['form_options'],
119             isset($config['form_options']) ? $config['form_options'] : array()
120         );
121 
122         /* 將字段的relation匯聚出來,是爲了後面的代碼方便,同時減小循環 */
123         $config['relations'] = $relations;
124 
125         $this->savedConfig = $config;
126     }
127 
128     protected function paginateModels()
129     {
130         $models = array();
131         if($this->model){
132             $query = $this->model->newQuery();
133             $this->handleListQuery($query);
134             $selects = array($this->model->getTable().'.*');
135 
136             foreach($this->savedConfig['relations'] as $field=>&$params){
137                 $query->join($params['table'],$params['table'].'.'.$params['foreign_key'],'=',$this->model->getTable().'.'.$field);
138                 if(!isset($params['as'])){
139                     $params['as'] = $params['table'].'_'.$params['show'];
140                 }
141                 $selects[] = $params['table'].'.'.$params['show'] . ' as '.$params['as'];
142             }
143             $query->select($selects);
144             $orderBy = Input::get('list_order_by');
145             if( $orderBy){
146                 $query->orderBy($this->model->getTable().'.'.$orderBy,Input::get('list_sort_asc') ? 'asc' : 'desc');
147             }else{
148                 $query->orderBy($this->model->getTable().'.'.$this->model->getKeyName(),'desc');
149             }
150             $page = Input::has('_page') ? Input::get('_page') : 10;
151             $models = $query->paginate($page);
152         }
153         return $models;
154     }
155 
156 
157     public function postEdit($primaryKeyValue = null){
158 
159         $primaryKeyName = $this->model->getKeyName();
160         if($primaryKeyValue == null){
161             $primaryKeyValue = Input::get($primaryKeyName);
162         }
163 
164         $fields = $this->savedConfig['fields'];
165         $datas = array();
166         foreach($fields as $field => $fieldConfig){
167             if(Input::has($field)){
168                 $datas[$field] = Input::get($field);
169             }
170         }
171 
172         if($primaryKeyValue){
173             $this->model->where($primaryKeyName,$primaryKeyValue)->update($datas);
174         }else{
175             $this->model->fill($datas);
176             $this->model->save();
177         }
178         $this->afterSave($this->model);
179         $resp = Redirect::action(get_class($this).'@getList')->withMessage('save success!');
180 
181         return $resp;
182     }
183 
184     public function getIndex()
185     {
186         $this->makeView(null,'admin.index');
187     }
188 
189     public function postDelete(){
190         $ids = explode(',',Input::get('ids'));
191         $data = array();
192         $success   =  true;
193         $data['ids'] = $ids;
194         $ids = array_filter($ids,function($id){
195             return $id;
196         });
197         $this->model->whereIn($this->model->getKeyName(),$ids)->delete();
198         $data['redirect_url'] = URL::to(action(get_class($this).'@getList'));
199         return Response::json(array(
200             'success'   =>  $success,
201             'data'      =>  $data,
202         ));
203     }
204 
205     public function getDelete($id){
206         $this->beforeDelete($id);
207         $this->model->where($this->model->getKeyName(),$id)->delete();
208         return Redirect::action(get_class($this).'@getList');
209     }
210 
211     public function missingMethod($parameters = array())
212     {
213         //
214         $this->makeView(null,'site.404');
215     }
216 
217     protected function handleListQuery(&$query)
218     {
219         $searchFields = array_intersect_key($this->savedConfig['fields'],Input::all());
220         foreach($searchFields as $field=> $fieldConfig){
221             if(isset($fieldConfig['list']['search'])){
222                 $value = Input::get($field);
223                 $operator = $fieldConfig['list']['search'];
224                 if($value !== ''){
225                     if($operator){
226                         if($operator === 'like'){
227                             $value = '%'.$value.'%';
228                         }
229                         $query = $query->where($this->model->getTable().'.'.$field,$operator,$value);
230                     }else{
231                         $query = $query->where($this->model->getTable().'.'.$field,$value);
232                     }
233                 }
234             }
235         }
236 
237     }
238 
239     protected function beforeDelete($id)
240     {
241 
242     }
243 
244     protected function beforeEdit(&$data)
245     {
246 
247     }
248 
249     protected function afterSave($model)
250     {
251 
252     }
253 
254     public function getHelp(){
255         $this->makeView(null,'admin.help');
256     }
257 
258 }
AdminController
  1 <?php
  2 
  3 /**
  4  * Created by PhpStorm.
  5  * User: Administrator
  6  * Date: 2015/10/10 0010
  7  * Time: 17:58
  8  */
  9 class BaseModel extends Eloquent
 10 {
 11     protected $table = '';
 12     protected $guarded = array('id');
 13     public $timestamps = false;
 14     public static function getTranslates($translate){
 15         $rows = DB::table($translate['table'])->select(array($translate['foreign_key'],$translate['show']))->get();
 16         return $rows;
 17     }
 18 
 19 
 20     static function getTableList( $db ,$connection = null)
 21     {
 22         $t = array();
 23         $dbname = 'Tables_in_'.$db ;
 24         $tables = $connection ? DB::connection($connection)->select("SHOW TABLES FROM {$db}") : DB::select("SHOW TABLES FROM {$db}");
 25         foreach($tables as $table)
 26         {
 27             $t[$table->$dbname] = $table->$dbname;
 28         }
 29         return $t;
 30     }
 31 
 32     static function getTableColumns( $table,$connection  = false)
 33     {
 34 //        $columns = array();
 35         $sql  = "SHOW COLUMNS FROM $table";
 36         $rawColumns = $connection ? DB::connection($connection)->select($sql)
 37             : DB::select($sql);
 38 //        foreach($rawColumns as $column)
 39 //            $columns[$column->Field] = $column->Field;
 40         return $rawColumns;
 41     }
 42 
 43     function getColoumnInfo( $result )
 44     {
 45         $pdo = DB::getPdo();
 46         $res = $pdo->query($result);
 47         $i =0;    $coll=array();
 48         while ($i < $res->columnCount())
 49         {
 50             $info = $res->getColumnMeta($i);
 51             $coll[] = $info;
 52             $i++;
 53         }
 54         return $coll;
 55 
 56     }
 57 
 58     function builColumnInfo( $statement )
 59     {
 60         $driver         = Config::get('database.default');
 61         $database         = Config::get('database.connections');
 62         $db         = $database[$driver]['database'];
 63         $dbuser     = $database[$driver]['username'];
 64         $dbpass     = $database[$driver]['password'];
 65         $dbhost     = $database[$driver]['host'];
 66 
 67         $data = array();
 68         $mysqli = new mysqli($dbhost,$dbuser,$dbpass,$db);
 69         if ($result = $mysqli->query($statement)) {
 70 
 71             /* Get field information for all columns */
 72             while ($finfo = $result->fetch_field()) {
 73                 $data[] = (object) array(
 74                     'Field'    => $finfo->name,
 75                     'Table'    => $finfo->table,
 76                     'Type'    => $finfo->type
 77                 );
 78             }
 79             $result->close();
 80         }
 81 
 82         $mysqli->close();
 83         return $data;
 84 
 85     }
 86 
 87     static function findPrimarykey( $table, $db = null)
 88     {
 89         $query = "SHOW KEYS FROM `{$table}` WHERE Key_name = 'PRIMARY'";
 90         $primaryKey = '';
 91         $keys = $db ? DB::connection($db)->select($query) : DB::select($query);
 92 
 93         foreach($keys as $key)
 94         {
 95             $primaryKey = $key->Column_name;
 96         }
 97 
 98         return $primaryKey;
 99     }
100 }
BaseModel
  1 <?php
  2 $formOption = $config['form_options'];
  3 $layout = $formOption['layout'];
  4 $labelCols = $layout['label_cols'];
  5 $inputCols = $layout['input_cols'];
  6 $labelCss = "col-sm-$labelCols";
  7 $inputCss = "col-sm-$inputCols";
  8 //  插件是否加載
  9 $loadUE = false;
 10 $loadSBox = false;
 11 $loadDatePicker = false;
 12 ?>
 13 <div class="panel panel-primary">
 14     <div class="panel-heading">
 15         <h3 class="panel-title">@if($model->id)  編輯<code>#{{$model->id}}</code>@else 新建  @endif</h3>
 16     </div>
 17     <div class="panel-body">
 18         <div class="row">
 19             <div class="col-sm-{{$layout['cols']}} col-sm-offset-{{(12-$layout['cols'])/2}}">
 20                 <form class="form-horizontal validate" action="{{URL::to('admin/'.$stdName.'/edit')}}" method="post">
 21                     <input type="hidden" name="{{$model->getKeyName()}}" value="{{$model->getKey()}}">
 22                     <?php foreach($config['fields'] as $field => $settings):?>
 23                     <?php
 24                     if ($field === $model->getKeyName() || !$settings['form']['show']) continue;
 25                     $type = $settings['form']['type'];
 26                     $rule = $settings['form']['rule'];
 27                     ?>
 28                     <?php if($settings['form']['type'] === 'hidden'):?>
 29                     <input type="hidden" name="{{$field}}" value="{{$model->$field}}">
 30                     <?php continue;?>
 31                     <?php endif; ?>
 32                     <div class="form-group">
 33                         <label for="{{$field}}"
 34                                class="{{$labelCss}} control-label">{{isset($settings['label']) ? $settings['label'] : strtoupper($field)}}</label>
 35                         <div class="{{$inputCss}}">
 36                             <?php if($type === 'textarea'):?>
 37                             <textarea name="{{$field}}" id="{{$field}}" rows="10"
 38                                       {{SiteHelpers::inputValidate($rule)}}
 39                                       class="form-control">{{$model->$field}}</textarea>
 40                             <?php elseif($type === 'select'):?>
 41                             <?php $loadSBox = true;?>
 42                             <select name="{{$field}}" id="{{$field}}" class="selectboxit" {{SiteHelpers::inputValidate($rule)}}>
 43                                 @if (isset($settings['form']['options']))
 44                                     @if(is_array($settings['form']['options']))
 45                                         @foreach($settings['form']['options'] as $value => $text)
 46                                             <option value="{{$value}}"
 47                                                     @if($value == $model->$field) selected @endif>{{$text}}</option>
 48                                         @endforeach
 49                                     @elseif(is_string($settings['form']['options']))
 50                                         @foreach($$settings['form']['options'] as $value => $text)
 51                                             <option value="{{$value}}">{{$value}}</option>
 52                                         @endforeach
 53                                     @endif
 54                                 @elseif(isset($settings['relation']['type']) && $settings['relation']['type'] )
 55                                     <?php
 56                                     $fieldTranslate = $config['relations'][$field];
 57                                     $options = BaseModel::getTranslates($fieldTranslate);
 58                                     foreach($options as $option):
 59                                     ?>
 60                                     <option value="<?=$option->$fieldTranslate['foreign_key']?>"
 61                                             @if($option->$fieldTranslate['foreign_key'] == $model->$field) selected @endif
 62                                     ><?=$option->$fieldTranslate['show']?>
 63                                     </option>
 64                                     <?php endforeach;?>
 65                                 @endif
 66                             </select>
 67                             <?php elseif($type === 'wysiwyg'):?>
 68                             <?php
 69                             $loadUE = true;
 70                             ?>
 71                             <script type="text/plain" name="{{$field}}" id="wysiwyg-edit"
 72                                     style="width:100%;height:240px;">{{$model->$field}}</script>
 73                             <?php elseif ($type === 'radio' || $type === 'checkbox'): ?>
 74 
 75                             @if(isset($settings['form']['options']))
 76                                 @foreach($settings['form']['options'] as $option => $text)
 77                                     <div class="{{$type}} {{$type}}-replace">
 78                                         <input type="{{$type}}" value="{{$option}}" name="{{$field}}"
 79                                                id="{{$field}}" @if($model->field === $option) checked @endif>
 80                                         <label>{{$text}}</label>
 81                                     </div>
 82                                 @endforeach
 83                             @endif
 84                             <?php elseif($type === 'date'):?>
 85                             <?php $loadDatePicker = true;?>
 86                             <div class="input-group">
 87                                 <input type="text" name="{{$field}}" id="{{$field}}" class="form-control datepicker" data-format="yyyy-MM-dd" {{SiteHelpers::inputValidate($rule)}}>
 88                                 <div class="input-group-addon">
 89                                     <a href="#"><i class="entypo-calendar"></i></a>
 90                                 </div>
 91                             </div>
 92                             <?php elseif($type === 'password'):?>
 93                             <input type="password" class="form-control" name="{{$field}}" id="{{$field}}"
 94                                    value="{{$model->$field}}"
 95                                     {{SiteHelpers::inputValidate($rule)}}
 96                             >
 97                             <?php elseif($type === 'file'):?>
 98                             <input type="file"
 99                                    class="form-control file2 inline btn btn-primary"
100                                    data-label="<i class='glyphicon glyphicon-file'></i> 選擇文件" >
101                             <?php else:?>
102                             <input type="text" class="form-control" name="{{$field}}" id="{{$field}}"
103                                    {{SiteHelpers::inputMask($rule)}}
104                                    {{SiteHelpers::inputValidate($rule)}}
105                                    value="{{$model->$field}}">
106                             <?php endif;?>
107                         </div>
108                     </div>
109                     <?php endforeach;?>
110                     <div class="form-group">
111                         <div class="{{$inputCss}} col-sm-offset-{{$labelCols}}">
112                             <button type="submit" class="btn btn-primary">保存</button>
113                         </div>
114                     </div>
115                 </form>
116             </div>
117         </div>
118     </div>
119 </div>
120 @yield('form.bottom','')
121 @section('styles')
122     @if($loadSBox)
123         {{HTML::style('assets/js/selectboxit/jquery.selectBoxIt.css')}}
124     @endif
125 @append
126 @section('scripts')
127     <?php if($loadUE):?>
128     {{HTML::script('plugins/ue-utf8-php/ueditor.config.js')}}
129     {{HTML::script('plugins/ue-utf8-php/ueditor.all.min.js')}}
130     {{HTML::script('plugins/ue-utf8-php/lang/zh-cn/zh-cn.js')}}
131     <?php endif;?>
132     @if($loadSBox)
133         {{HTML::script('assets/js/selectboxit/jquery.selectBoxIt.min.js')}}
134     @endif
135     @if($loadDatePicker)
136         {{HTML::script('assets/js/bootstrap-datepicker.js')}}
137     @endif
138     {{HTML::script('assets/js/jquery.inputmask.bundle.min.js')}}
139     {{HTML::script('assets/js/jquery.validate.min.js')}}
140 @append
141 
142 @section('footScript')
143     <script>
144         var ue = null,
145                 ueId = 'wysiwyg-edit';
146         if (document.getElementById(ueId)) {
147             ue = UE.getEditor(ueId);
148         }
149     </script>
150 @append
app/views/admin/core/form.blade.php
  1 @section('headStyle')
  2     <style>
  3         .sort.sort-active {
  4             color: #000;
  5             font-weight: bold;
  6         }
  7     </style>
  8 @append
  9 <?php
 10 $list_options = $config['list_options'];
 11 $loadSBox = false;
 12 $loadDatePicker = false;
 13 ?>
 14 <div class="panel panel-default">
 15     <div class="panel-heading">
 16         <h3 class="panel-title"><?=isset($navMap[$stdName]['text']) ? $navMap[$stdName]['text'] : strtoupper($stdName)?>
 17             列表</h3>
 18     </div>
 19     <div class="panel-body">
 20         <div class="row">
 21             <div class="col-sm-12">
 22                 <div class="btn-group btn-group-sm" role="group">
 23                     @if($list_options['create'])
 24                         <a href="{{URL::to('admin/'.$stdName.'/edit')}}" class="btn btn-primary">新建</a>
 25                     @endif
 26                     <a class="btn btn-danger delete-selected">刪除</a>
 27                     <a class="btn btn-default">導出</a>
 28                 </div>
 29             </div>
 30         </div>
 31         <br>
 32         <form class="list-form" action="" method="get">
 33             <input type="hidden" name="list_sort_asc" value="{{Input::get('list_sort_asc') !== null ? Input::get('list_sort_asc') : 1}}">
 34             <input type="hidden" name="list_order_by" value="">
 35             <table class="table table-bordered responsive table-hover table-striped">
 36                 <thead>
 37                 <tr>
 38                     <th>
 39                         <div class="checkbox checkbox-replace">
 40                             <input type="checkbox" class="item-all">
 41                         </div>
 42                     </th>
 43                     <?php foreach($config['fields'] as $field=>$settings):?>
 44                     <?php if($settings['list']['show']):?>
 45                     <th @if($settings['list']['sort'])
 46                         class="sort @if($field === Input::get('list_order_by')) sort-active @endif"
 47                         data-field="{{$field}}" @endif
 48                     ><?=is_array($settings) && isset($settings['label']) ? $settings['label'] : strtoupper($field)?>
 49                         <span class="pull-right">
 50                             @if($field === Input::get('list_order_by'))  @if(Input::get('list_sort_asc') == 1) <i class="fa fa-sort-asc"></i> @else <i class="fa fa-sort-desc"></i>  @endif @endif
 51                         </span>
 52                     </th>
 53                     <?php endif;?>
 54                     <?php endforeach;?>
 55                     <th>操做</th>
 56                 </tr>
 57                 </thead>
 58                 <tbody>
 59                 <tr>
 60                     <td></td>
 61                     @foreach($config['fields'] as $field=>$fieldConfig)
 62                         @if($fieldConfig['list']['show'])
 63                             @if(isset($fieldConfig['list']['search']) && $fieldConfig['list']['search'] !== false)
 64                                 <td>
 65                                     @if($fieldConfig['form']['type'] == 'select' || ($fieldConfig['form']['type'] === 'radio' || $fieldConfig['form']['type'] == 'checkbox'))
 66                                         <?php $loadSBox = true;?>
 67                                         @if(isset($fieldConfig['form']['options']) && $fieldConfig['form']['options'])
 68                                             <select name="{{$field}}" id="{{$field}}" class="selectboxit">
 69                                                 <option value="" class="default-value">請選擇</option>
 70                                                 @foreach($fieldConfig['form']['options'] as $option => $text)
 71                                                     <option value="{{$option}}" @if(Input::get($field) && Input::get($field) === $option) selected @endif>{{$text}}</option>
 72                                                 @endforeach
 73                                             </select>
 74                                         @elseif(isset($fieldConfig['relation']['type']) && $fieldConfig['relation']['type'] )
 75                                             {{View::make('components.relation_select',array(
 76                                             'fieldConfig'=>$fieldConfig,'field' =>  $field, ))}}
 77                                         @endif
 78                                     @elseif($fieldConfig['form']['type'] === 'date')
 79                                         <?php $loadDatePicker = true;?>
 80                                         <input type="text" name="{{$field}}" id="{{$field}}" class="form-control datepicker" data-format="yyyy-MM-dd" value="{{Input::get($field)}}">
 81                                     @else
 82                                         <input type="text" name="{{$field}}" id="{{$field}}"
 83                                                value="{{Input::get($field)}}" class="form-control input-sm">
 84                                     @endif
 85                                 </td>
 86                             @else
 87                                 <td></td>
 88                             @endif
 89                         @endif
 90                     @endforeach
 91                     <td>
 92                         <div class="btn-group btn-group-sm" role="group">
 93                             <button type="submit" class="btn btn-primary">搜索</button>
 94                             <button type="reset" onclick="resetForm(this)" class="btn btn-warning hidden">重置</button>
 95                         </div>
 96                     </td>
 97                 </tr>
 98                 <?php foreach($models as  $model):?>
 99                 <tr>
100                     <td width="18px">
101                         <div class="checkbox checkbox-replace">
102                             <input type="checkbox" name="d_delete_select" class="item" value="{{$model->id}}">
103                         </div>
104                     </td>
105                     <?php foreach($config['fields'] as $filed=>$settings):?>
106                     <?php if($settings['list']['show']):?>
107                     <?php
108                     $value = $model->$filed;
109                     /* 字段在列表中須要翻譯 */
110                     if (array_key_exists($filed, $config['relations'])) {
111                         $value = $model->$config['relations'][$filed]['as'];
112                     }
113                     ?>
114                     <td>{{$value}}</td>
115                     <?php endif;?>
116                     <?php endforeach;?>
117                     <td>
118                         <div class="btn-group btn-group-sm" role="group">
119                             @if($list_options['update'])
120                                 <a href="{{URL::to('admin/'.$stdName.'/edit/'.$model->id)}}"
121                                    class="btn btn-primary">編輯</a>
122                             @endif
123                             @if($list_options['delete'])
124                                 <a href="{{URL::to('admin/'.$stdName.'/delete/'.$model->id)}}"
125                                    class="btn btn-danger">刪除</a>
126                             @endif
127                             @if(View::exists('admin.'.snake_case($stdName).'.list_item_links'))
128                                 @include('admin.'.snake_case($stdName).'.list_item_links',array('model'=>$model))
129                             @endif
130                         </div>
131                     </td>
132                 </tr>
133                 <?php endforeach;?>
134                 </tbody>
135             </table>
136         </form>
137         <div class="pull-right">
138             {{$models->appends(Input::all())->links()}}
139         </div>
140     </div>
141 </div>
142 
143 @section('styles')
144     {{HTML::style('assets/js/datatables/responsive/css/datatables.responsive.css')}}
145 
146     @if($loadSBox)
147         {{HTML::style('assets/js/selectboxit/jquery.selectBoxIt.css')}}
148     @endif
149 @append
150 
151 @section('scripts')
152     {{HTML::script('assets/js/jquery.dataTables.min.js')}}
153     {{HTML::script('assets/js/datatables/jquery.dataTables.columnFilter.js')}}
154     @if($loadSBox)
155         {{HTML::script('assets/js/selectboxit/jquery.selectBoxIt.min.js')}}
156     @endif
157     @if($loadDatePicker)
158         {{HTML::script('assets/js/bootstrap-datepicker.js')}}
159     @endif
160 @append
161 
162 @section('footScript')
163     <script>
164         $(document).ready(function(){
165             $('th.sort').click(function(){
166                 var $th = $(this);
167                 $('input[name="list_order_by"]').val($th.data('field'));
168                 $('input[name="list_sort_asc"]').val($th.find('i').hasClass('fa-sort-asc') ? 0 : 1);
169                 $('form.list-form').submit();
170             });
171 
172             $('input.item-all').change(function(){
173                 var $this = $(this),
174                         $items = $('input.item');
175                 if($this.is(':checked')){
176                     $items.prop('checked','checked');
177                 }else{
178                     $items.removeProp('checked');
179                 }
180                 $items.trigger('change');
181             });
182 
183 
184             $('a.delete-selected').click(function(){
185                 var ids = [],
186                         $items = $('input.item:checked');
187                 $items.each(function(i){
188                     ids.push($(this).val());
189                 });
190                 var idsStr = ids.join(',');
191                 confirmModal({
192                     message  :   '確認刪除:'+idsStr,
193                     onOk:   function(){
194                         $.post('{{URL::to('admin/'.snake_case($stdName).'/delete')}}',{"ids":idsStr},function(resp){
195                             if(resp.success){
196                                 window.location.href = resp.data.redirect_url;
197                             }
198                         },'json');
199                     }
200                 });
201                 return false;
202             });
203         });
204     </script>
205 @append
206 
207 @section('modals')
208     <div class="modal fade" id="confirm-modal" data-backdrop="static">
209         <div class="modal-dialog">
210             <div class="modal-content">
211                 <div class="modal-header">
212                     <h4 class="modal-title">操做確認</h4>
213                 </div>
214                 <div class="modal-body">
215 
216                 </div>
217                 <div class="modal-footer">
218                     <button type="button" class="btn btn-default cancel" data-dismiss="modal">取消</button>
219                     <button type="button" class="btn btn-info ok">確認</button>
220                 </div>
221             </div>
222         </div>
223     </div>
224 @stop
app/views/admin/core/list.blade.php
 1 <?php
 2 /**
 3  * 說明:
 4  * 1. 如下配置項,不設置即是默認
 5  * Created by PhpStorm.
 6  * User: lvyahui
 7  * Date: 2016/5/2
 8  * Time: 12:33
 9  */
10 
11 return array(
12 
13     /**
14      * 全部字段配置
15      */
16     'fields'    =>  array(
17         'field_name' =>  array(
18             /* 顯示在列表表格的表頭的內容,和form控件旁邊的內容*/
19             'label' =>  '字段中文名',
20             /* 字段缺省值 */
21             'value' =>  false,
22             /* 針對表單的設置 */
23             'form'  =>  array(
24                 'show'  =>  true,
25                 'hidden'    =>  false,
26                 /*
27                  * 字段對應表單的控件類型,默認text,
28                  * 還支持經常使用的控件類型
29                  * textarea
30                  * radio
31                  * checkbox
32                  * number
33                  * ipaddr
34                  * wyswyg
35                  * select
36                  * date
37                  * file
38                  * 以及自定義類型
39                  * */
40                 'type'  =>  'text',
41                 /*
42                 'type'  =>  array(
43                     'select'    =>  array(
44                         'options'   =>  function(){
45                             return array();
46                         }
47                     ),
48                 ),
49                 'type'  =>  array(
50                     'radio'     =>  array(),
51                 ),
52                 */
53                 /* 提交表單後的驗證規則 */
54                 'rule'  =>  'required',
55                 'ajax_validate' =>  false,
56                 'placeholder'   =>  'xx',
57 
58             ),
59             // 針對列表的設置
60             'list'  =>  array(
61                 /* 字段在列表是否顯示,默認爲顯示 */
62                 'show'  =>  true,
63                 /* 字段是否能夠排序,默認不能排序 */
64                 'sort'  =>  true,
65                 /* 是否可以按這個字段搜索 */
66                 'search'    =>  true,
67                 /* 字段進行翻譯,好比欄目Id字段,通常要轉成欄目名稱顯示 */
68                 'lookup'    =>  false,
69             ),
70 
71         ),
72         // more fields
73     ),
74 
75     /**
76      * 全局form配置,優先級小於字段配置
77      */
78     'form_options'  =>  array(
79         'layout'    =>  array(
80             'cols'  =>  12,
81             'label_cols' =>  1,
82             'input_cols' =>  11,
83         ),
84     ),
85 
86     /**
87      * 全局list配置,優先級小於字段配置
88      */
89     'list_options'  =>  array(
90         'page'      =>  10,
91         'create'    =>  true,
92         'update'    =>  true,
93         'delete'    =>  true,
94     ),
95 
96 );
app/config/crud/admin.php

4.5 GModule 管理模塊實現

GModule是一類由DBuilder生成的模塊,它有一組模板定義在app/template目錄下:

  • app/template/_form.tpl
  • app/template/_list.tpl
  • app/template/controller.tpl
  • app/template/model.tpl

前面設計中指出,GModule管理模塊自己是一個名爲「Module」,主表爲d_module,且手工創建的GModule,故其代碼組成也是符合GModule規範的,筆者編寫的代碼主要爲擴展代碼。GModule管理模塊對應了下述代碼文件:

  • app/controllers/admin/ModuleController.php:控制器(Controller)代碼,其實現CoreCRUD模塊的接口,以及擴展的url接口;
  • app/models/Module.php:GModule管理模塊的模型;
  • app/views/admin/module/_form.blade.php: FORM視圖代碼,其在原有的CoreCRUD 模塊的FORM表單下部,擴展了一組Tab,其中第一個Tab中顯示了全部字段的詳細配置,經過以上擴展就能實如今CoreCRUD生成的Form表單頁面中對GModule進行配置;
  • app/views/admin/module/_list.blade.php: LIST視圖代碼;
  • app/views/admin/module/fields_config.blade.php:字段配置表格視圖代碼;
  • app/views/admin/module/list_item_links.blade.php:擴展連接視圖代碼;
  • app/config/crud/module.php:GModule Configuration文件。

下面貼上主要的代碼文件ModuleController.php

  1 <?php
  2 /**
  3  * Created by PhpStorm.
  4  * User: lvyahui
  5  * Date: 2016/5/12
  6  * Time: 15:28
  7  */
  8 
  9 namespace admin;
 10 
 11 define('MODULE_ROUTES', json_encode(include(app_path() . '/module_routes.php')));
 12 
 13 use Illuminate\Support\Facades\Redirect;
 14 use SiteHelpers;
 15 use BaseModel;
 16 use Module;
 17 use ConfigUtils;
 18 use Illuminate\Support\Facades\Input;
 19 use Illuminate\Support\Facades\Config;
 20 use Illuminate\Support\Facades\Response;
 21 
 22 class ModuleController extends AdminController
 23 {
 24 
 25     protected function beforeEdit(&$data)
 26     {
 27         $data ['dataSources'] = SiteHelpers::loadDataSources();
 28         if ($data['model']->id) {
 29             $data ['moduleConf'] = ConfigUtils::get($data['model']->name);
 30         }
 31     }
 32 
 33     protected function afterSave($module)
 34     {
 35         /* 生成代碼文件 */
 36         $codes = array(
 37             'moduleName'      => $module->name,
 38             'moduleTitle'     => $module->title,
 39             'tablePrimaryKey' => BaseModel::findPrimarykey($module->db_table, $module->db_source),
 40             'moduleNote'      => $module->note,
 41             'date'            => date('Y-m-d'),
 42             'dbSource'        => $module->db_source,
 43             'dbTable'         => $module->db_table,
 44         );
 45         $this->removeFiles($codes['moduleName']);
 46         /* 生成默認module Configuration*/
 47         $moduleConfs = $this->buildConfiguration($module->db_table, $module->db_source);
 48         SiteHelpers::saveArrayToFile(app_path('config/crud/') . snake_case($codes['moduleName']) . '.php', $moduleConfs);
 49 
 50         $controller = file_get_contents(app_path('template') . '/controller.tpl');
 51         $model = file_get_contents(app_path('template') . '/model.tpl');
 52         $formView = file_get_contents(app_path('template') . '/_form.tpl');
 53         $listView = file_get_contents(app_path('template') . '/_list.tpl');
 54         $codes['timestamps'] = isset($moduleConfs['fields']['created_at']) && isset($moduleConfs['fields']['updated_at'])
 55                                 ? 'true' : 'false';
 56         $buildController = SiteHelpers::blend($controller, $codes);
 57         $buildModel = SiteHelpers::blend($model, $codes);
 58         /* 生成 MVC 文件*/
 59         file_put_contents(app_path() . "/controllers/admin/{$codes['moduleName']}Controller.php", $buildController);
 60         file_put_contents(app_path() . "/models/{$codes['moduleName']}.php", $buildModel);
 61         $viewPath = app_path('/views/admin/') . snake_case($codes['moduleName']);
 62         if (!file_exists($viewPath)) mkdir($viewPath);
 63         file_put_contents($viewPath . "/_form.blade.php", $formView);
 64         file_put_contents($viewPath . "/_list.blade.php", $listView);
 65 
 66 
 67         /* 更新路由 */
 68         $moduleRoutes = json_decode(MODULE_ROUTES, true); //require(app_path().'/module_routes.php');
 69         if (is_array($moduleRoutes)) {
 70             $moduleRoutes[SiteHelpers::reducCase($codes['moduleName'])] = 'admin\\' . "{$codes['moduleName']}Controller";
 71             SiteHelpers::saveArrayToFile(app_path() . '/module_routes.php', $moduleRoutes);
 72         }
 73     }
 74 
 75     protected function beforeDelete($id)
 76     {
 77         $module = Module::find($id);
 78         $moduleName = $module->name;
 79         $this->removeFiles($moduleName);
 80     }
 81 
 82     /**
 83      * 刪除GModule相關文件文件
 84      * @param $moduleName
 85      */
 86     public function removeFiles($moduleName)
 87     {
 88         $controller = app_path('admin/controllers') . "/{$moduleName}Controller.php";
 89         if (file_exists($controller)) {
 90             unlink($controller);
 91         }
 92         $model = app_path('models') . "/{$moduleName}.php";
 93         if (file_exists($model)) {
 94             unlink($model);
 95         }
 96         $moduleConf = app_path('config/crud/') . snake_case($moduleName) . '.php';
 97         if (file_exists($moduleConf)) {
 98             unlink($moduleConf);
 99         }
100 
101         $viewPath = app_path('/views/admin/') . snake_case($moduleName);
102         $formFile = $viewPath . '/_form.blade.php';
103         $listFile = $viewPath . '/_list.blade.php';
104         if (file_exists($formFile)) unlink($formFile);
105         if (file_exists($listFile)) unlink($listFile);
106     }
107 
108     private function buildConfiguration($table, $connection)
109     {
110         $rawColumns = BaseModel::getTableColumns($table, $connection);
111         $fields = ConfigUtils::build($rawColumns);
112         return array(
113             'data_source' => $connection,
114             'table'       => $table,
115             'fields'      => $fields,
116         );
117     }
118 
119     /**
120      * 獲取字段配置列表
121      * @return bool
122      */
123     public function getFieldsConfig()
124     {
125         $filedsConfig = null;
126         if (Input::has('module_name')) {
127             $filedsConfig = ConfigUtils::get(Input::get('module_name'))['fields'];
128         } else {
129             $table = Input::get('table');
130             $connection = Input::get('connection');
131 
132             $filedsConfig = $this->buildConfiguration($table, $connection)['fields'];
133         }
134 
135         $resp = $this->makeView(array(
136             'fieldsConfig' => $filedsConfig,
137         ));
138         if ($resp) {
139             return $resp;
140         }
141     }
142 
143 
144     /**
145      * 保存字段列表配置
146      * @return mixed
147      */
148     public function postSaveFieldsConf()
149     {
150         $resp = Redirect::action(get_class($this) . '@getEdit', Input::get('id'));
151         $postFields = Input::get('fields');
152         $moduleName = Input::get('module_key');
153         $confKey = SiteHelpers::reducCase($moduleName);
154         $savedConfig = ConfigUtils::get($confKey);
155         foreach ($savedConfig['fields'] as $fieldName => &$savefield) {
156             $postField = $postFields[$fieldName];
157             $savefield['label'] = $postField['label'];
158             $savefield['form']['show'] = isset($postField['form']['show']);
159             $savefield['list']['show'] = isset($postField['list']['show']);
160         }
161         ConfigUtils::saveGModuleConf($confKey, $savedConfig);
162         return $resp;
163     }
164 
165     /**
166      * 呈現某一字段的配置參數FORM
167      * @return bool
168      */
169     public function getFieldConfig()
170     {
171         $moduleKey = Input::get('module_key');
172         $field = Input::get('field');
173 
174         $moduleConfig = Config::get('crud/' . snake_case($moduleKey));
175         $fieldConf = &$moduleConfig['fields'][$field];
176         $dbSource = SiteHelpers::loadDataSources()[$moduleConfig['data_source']];
177         $tables = BaseModel::getTableList($dbSource['database'],$moduleConfig['data_source']);
178 
179         $resp = $this->makeView(array(
180             'field'       => $field,
181             'fieldConfig' => $fieldConf,
182             'moduleKey'   => $moduleKey,
183             'tables'    =>  $tables,
184             'connection'    =>  $moduleConfig['data_source'],
185         ));
186 
187         if ($resp) return $resp;
188     }
189 
190     /**
191      * 保存某一字段的配置參數
192      * @return \Illuminate\Http\JsonResponse
193      */
194     public function postFieldConfig()
195     {
196         $data = array(
197             'success' => true,
198         );
199         $moduleKey = Input::get('module_key');
200         $field = Input::get('field');
201         $moduleConfig = Config::get('crud/' . snake_case($moduleKey));
202         $fieldConf = &$moduleConfig['fields'][$field];
203 
204         $postFormConf = Input::get('form');
205         $postListConf = Input::get('list');
206         $postRelationConf = Input::get('relation');
207         $fieldConf['form']['type'] = $postFormConf['type'];
208         if((
209                 $postFormConf['type'] === 'select'
210                 || $postFormConf['type'] === 'radio'
211                 || $postFormConf['type'] === 'checkbox'
212             )
213             && isset($postFormConf['options'])
214             && $postFormConf['options']
215         ){
216             $rawOptions = explode(',',$postFormConf['options']);
217             $options = array();
218             foreach($rawOptions as $option){
219                 $options[$option]   = $option;
220             }
221             $fieldConf['form']['options'] = $options;
222         }
223         $fieldConf['form']['placeholder'] = $postFormConf['placeholder'];
224         $fieldConf['form']['rule']  =  $postFormConf['rule'];
225         $fieldConf['list']['sort'] = isset($postListConf['sort']);
226         $fieldConf['list']['search'] = $postListConf['search'];
227 
228         $fieldConf['relation']  =   $postRelationConf;
229 
230         SiteHelpers::saveArrayToFile(app_path('config/crud/' . snake_case($moduleKey) . '.php'), $moduleConfig);
231 
232         return Response::json($data);
233     }
234 }
ModuleController

4.6 部署

DBuilder部署運行的操做系統能夠是Windows或Linux,本文將基於LNMP(Linux+Nginx+MySQL+PHP)環境進行部署,詳細部署環境要求:

  • PHP Version > 5.4
  • MCrypt PHP 必須安裝
  • OpenSSL 必須安裝
  • MySQL Version > 5.4
  • Nginx、Apache等服務器

首先,須要將DBuilder放置到Nginx的Default Server或者Vhost中,這裏以Default Server爲例。本文中DBuilder的根目錄爲

/home/wwwroot/dbuilder/

編輯nginx.conf文件,修改server節點:

server

{

   listen 80 default_server;

   #listen [::]:80 default_server ipv6only=on;

   #server_name www.lnmp.org;

   index index.html index.htm index.php;

   root  /home/wwwroot/dbuilder;

 

   #error_page   404   /404.html;

   include enable-php.conf;

 

   location / {

       try_files $uri $uri/ /index.php?$query_string;

   }

 

   location ~ [^/]\.php(/|$)

   {

          # comment try_files $uri =404; to enable pathinfo

          try_files $uri =404;

          fastcgi_pass  unix:/tmp/php-cgi.sock;

          #fastcgi_pass 127.0.0.1:9000;

          fastcgi_index index.php;

          include fastcgi.conf;

          #include pathinfo.conf;

   }

 

   location /nginx_status

   {

       stub_status on;

       access_log   off;

   }

 

   location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$

   {

       expires      30d;

   }

 

   location ~ .*\.(js|css)?$

   {

       expires      12h;

   }

 

   location ~ /\.

   {

       deny all;

   }

 

   access_log  /home/wwwlogs/access.log  access;

}

修改DBuilder項目文件所屬用戶,保證nginx http進程對文件有讀權限,本文部署環境中,nginx http進程爲www用戶進程;同時須要給部分DBuilder目錄徹底的寫入權限,執行下列命令:

cd /home/wwwroot

chown –R www dbuilder

chgrp –R www dbuilder

cd dbuilder

chmod –R 777 app/storage

chmod -R 665 app/controllers/admin app/config/crud app/models/ app/views

創建數據庫,在mysql中建立名爲dbuilder的數據庫,並source Dbuilder根目錄下的dbuilder.sql,具體執行以下命令

# 首先進入msyql

mysql –uroot –pyour_root_password

# 進入mysql以後

create database dbuilder default char set utf8;

use dbuilder;

source /home/wwwroot/dbuilder/dbuilder.sql;

至此DBuilder部署完成,經過瀏覽器訪問http://hostname/admin (hostname爲主機域名或ip地址)便可以訪問到DBuilder。

4.7 案例

設定:在不編寫代碼的基礎上,以DBuilder生成一個簡單可用的博客後臺,博客後臺有post表和category表,位於core數據源。

CREATE TABLE post

(

    id INT(11) PRIMARY KEY NOT NULL,

    category_id INT(11) NOT NULL,

    title VARCHAR(64) NOT NULL,

    short VARCHAR(256) NOT NULL,

    content TEXT NOT NULL,

    view_ct INT(11) DEFAULT '0' NOT NULL,

    created_at TIMESTAMP DEFAULT 'CURRENT_TIMESTAMP' NOT NULL,

    updated_at TIMESTAMP DEFAULT '0000-00-00 00:00:00' NOT NULL

) DEFAULT CHAR SET utf8;

 

CREATE TABLE category

(

    id INT(11) PRIMARY KEY NOT NULL,

    title VARCHAR(32) NOT NULL,

    level INT(11),

    weight INT(11) DEFAULT '0' NOT NULL COMMENT '排序字段',

    parent_id INT(11),

    post_ct INT(11) DEFAULT '0' NOT NULL,

) DEFAULT CHAR SET utf8;

4.7.1 新建GModule

準備好數據庫表便可新建GModule,下面新建名爲「Post」的GModule。進入GModule管理->新建界面,按圖填寫保存。

 

圖4-1 新建GModule頁面

編輯新建的Post GModule,能夠看到在下部多出一個含有表格的tab。

 

圖4-2 GModule Configuration字段配置頁面

如今對於post表的全部字段都是默認配置,分別查看List和Form,能夠看到List和Form都能正常讀取數據庫數據。

 

圖4-3 GMoudle 列表頁面

 

圖4-4 GModule表單頁面

上面兩圖呈現的List和Form並不具備可用性,所以須要對字段作配置。

4.7.2 GModule配置

首先修改字段的中文名、是否包含在form、是否包含在List等屬性。

 

圖4-5 GModule Configuration字段配置頁面

保存以後,再次刷新Post列表和Form。對比圖4-三、圖4-4發現內容發生了變化

 

圖4-6 GModule列表頁面

 

圖4-7 GModule表單頁面

下面對每一個字段作更詳細的配置以獲得更符合咱們需求的頁面,修改控件類型:short(摘要)字段爲textarea(多行文本)類型,content(正文)字段爲wysiwyg(富文本)類型,category_id字段爲select(下拉列表)類型,updated_at(修改時間)爲date(日期)類型。修改category_id(欄目外鍵)的關係爲所屬關係,並填寫以下:

 

圖4-8 GModule 字段詳細配置表單

修改short(摘要)字段、title(標題)字段爲不可排序與like模糊搜索,修改updated_at搜索方式爲「>=」搜索

4.7.3 List&Form效果

刷新Post列表,可看到以下兩個控件:date和select控件。

 

圖4-9 GModule 列表搜索日期與下拉列表控件

輸入搜索條件爲修改日期:2016-03-0三、欄目:C++、摘要:收到。結果按閱讀次數排序。獲得下面的列表結果。

 

圖4-10 GModule 列表搜索與排序

點擊其中一條記錄進行編輯,測試Form功能。

 

圖4-11 GModule編輯表單

修改以後點擊保存也是正常可用的。

整個配置過程,只需幾分鐘,但卻實現了上述較爲複雜的功能。而若是換成開發人員手工編寫相似功能模塊,至少須要兩三個小時的時間,相比之下,DBuilder極大的提升了開發效率。

第五章           總結與展望

本文基於WEB技術基本實現了一款可用的CRUD生成器,其內核實現比SximoBuilder更精簡,在代碼高度複用的前提下,提供更強的擴展性,並支持多數據庫、前端驗證、自定義表單控件等等。

因爲時間緣由,DBuilder還沒有實現諸如用戶管理、權限控制、操做日誌記錄、站點配置、多語言化等功能。另外,隨着技術進步和網絡普及,如何作到高併發、高性能以及支持數據集羣的web系統是當前web項目開發須要着重考慮的問題。筆者將在DBuilder的後續改進中實現上述功能,並對高併發提供支持。同時,爲了更好的推廣和發展DBuilder,筆者已將DBuilder開源至Github:https://github.com/lvyahui8/dbuilder.git 。

相關文章
相關標籤/搜索