育兒中心2.0開發手冊
===
1. 2019/07/02 - ver1.0
:::success
:bookmark: 書籤
[TOC]
:::
# 程式碼撰寫風格
## 排版
### 結尾符號
括號緊連母句後換行。
``` php
if(){
}
for(){
}
```
### 換行
#### 1. php遇長索引陣列宣告換行。
```php
//ex1包覆在function內陣列
array_push($data['data'],array(
"key" => $row->key,
"name" => $row->name
));
//ex2普通宣告
$data = array(
"name" => $result->name,
"content" => $result->content,
"feedback" => $result->feedback,
"startDate" => $startTime[0]
);
//ex3 結構簡單,不包覆在Function中可不換行
$data = array("status"=>0,"data"=>array());
```
#### 2 鍊式結構
換行且向內縮排
```php
$this->db
->select('SHA1(`key`) as `key`,name')
->from('permission')
->where('SHA1(area_key)',$area_key);
```
#### 3 字串過長
依語意結構換行,並且向內縮排。
```php
//ex1
$this->light_datatables->ci->db
->from('user')
->select('SHA1(`user`.`key`)as user_key,
SHA1(`user`.permission_key)as permission_key,
`user`.`name`,
`user`.account,
permission.`name` as permission_name');
//ex2
$buttton = '
<button type="button" onclick="openModal(\'editUser\',\'[extra]\')" class="btn btn-outline-primary functions-btn">編輯資料</button>
<button type="button" onclick="openModal(\'watchUser\',\'[extra]\')" class="btn btn-outline-info functions-btn">查閱資料</button>
[extra]
';
```
## 命名
### 駝峰式命名
1. 變數、function、陣列索引名稱使用小駝峰命名
2. 與資料庫相關變數索引值,參照資料欄位名稱命名
### function 命名語意
1. 新增:add
2. 修改:edit
3. 移除:delete
4. 取得:get
5. 確認:check
6. 設定:set
``` php
//新增使用者
function addUser();
//修改使用者資料
function editUser();
//刪除使用者
function deleteUser();
//取得DB錯誤JSON
function getDBErrJson();
//確認使用者帳號是否重複
function checkUser();
//設定錯誤碼(成員變數的值指派)
function setError();
```
## PHP特性
### 字串串接變數
字串串接一律使用雙引號以及{}進行實作。
``` php
//在php7中,雙引號內的{}會被解析成變數帶入
$html = "<a href='{$url}'> <i class='fa {$menu[$i]['icon']}'></i> {$menu[$i]['name']} </a>";
```
### 判斷
善用??判斷在賦值的過程中減少isset的使用。
```php
//意義為:若$dataArray['cell']存在則使用原本的值,若不存在則回傳空字串。
set('cell', $dataArray['cell'] ?? "");
set('email', $dataArray['email'] ?? "");
```
### 判斷式與回傳布林
if判斷式中,變數非false則為true。
#### 1. Modal
```php
public function checkUser($area_key){
/*********/
if($result = $this->dbGetCatch()){
return $result->result();
}else{
return false;
}
}
```
#### 2.Controllers
```php
$checkUser = $this->model->checkUser($data);
if($checkUser){
/*********/
}else{
/*********/
}
```
# 開發規則
請務必遵照開發規則進行開發。
## 名詞對應


| 編號 | 名稱 |英|
| -------- | -------- | -------- |
| 1 |頁面小功能 | multi function |
| 2 | 導航列 | navbar |
| 3 | 主區塊 | body content |
| 4 | 彈跳視窗 | modal |
## View
每個View的構成已被拆分成最小元件,每個View都一定會載入下面三個元件。
| 名稱 | 功能 | 位置 |
| -------- | -------- | -------- |
| headLoad.php | 載入基本的javascript以及style檔案 | view/basic/ |
| header.php | 定義每個頁面的導航列以及功能鍵 | view/basic/ |
| footer.php | 定義頁腳 | view/basic/ |
### 元件導向
將頁面上所有的功能都看做是獨立的原件,最後再將它們載入。
1. 所有view的進入點都是「Controllers」的名稱加上「_view」。
2. 每個元件所要執行的function都必須使用物件包裝起來。
3. 除了物件外不可定義全域變數。
4. 所有的元件統一在進入點進行加載。
5. 額外所使用到的javascript以及css引入檔,也是在進入點進行加載。
我們以user功能進行舉例,在user(使用者與權限)下擁有這些元件。
| 名稱 | 功能 | 目錄 |
| -------- | -------- | -------- |
| user_view.php | 進入點,加載所有元件 | view/user |
| bodyContent.php | 主區塊所顯示的內容,在user下是一張DataTable。 | view/user |
| addUser_modal | 新增使用者的彈跳視窗。 | view/user |
| addPermission_modal.php | 新增職位權限的彈跳視窗。 | view/user |
| editPermission_modal.php | 編輯職位權限彈跳視窗。 | view/user |
| setPermission_modal.php | 設定使用者職位權限彈跳視窗。 | view/user |
| editUser_modal.php | 編輯使用者資料的彈跳視窗。 | view/user |
| watchUser_modal.php | 閱覽使用者資料的彈跳視窗。 | view/user |
### 彈跳視窗
彈跳視窗的觸發只能由Controllers所定義的按鈕進行啟動。並且,為了資料傳遞的正確性,請務必遵守規則。
1. 彈跳視窗必須自己獨立成一個元件。
2. 所有彈跳視窗的載入必須在進入點的</body>前
3. 彈跳視窗若開啟時有需要執行動作,直接進行監聽即可。
```javascript
//捕捉開啟動作
$('#addUser').on('show.bs.modal', function (e) {
addUserObject.getAllPermission();
})
//定義這個元件的專屬物件
var addUserObject = {
//取得並更新權限列表
getAllPermission : function(){
$('#addUserPermission').html('');
/*********/
},
/*****/
}
```
4. 如果在Controller生成按鈕時有傳遞資訊,則可以呼叫全域物件viewData
```javascript
//viewData中的物件名稱與彈跳視窗的ID相同
//如果按鈕傳值只有一個,那麼就在陣列0的位置,傳值二個以上則是從1開始呼叫,以此類推。
//viewData中的資訊會隨著彈跳視窗的開啟而刷新
var addUserObject = {
/*******/
submit : function(){
var areaKey = viewData.addUser[0];
$('#addUserPermission').html('');
/*********/
},
/*****/
}
#### 表單處理
1. 表單的input盡量以name進行命名,若非必要不要使用id進行操作。
2. form需要設定一個ID
3. 表單的驗證必須獨立寫成一個function。
4. 表單的取值一律使用全域方法serializeObject()一次取完。
```javascript
var addUserObject = {
/.../
//定義新增動作
submit : function(){
var formData = $("#addUserForm").serializeObject();
if(this.checkForm(formData)) return;
/.../
},
//定義表單送出檢測
checkForm : function(formData){
if(formData.name == "" || formData.account == ""){
swal('錯誤','使用者名稱與帳號為必填項目,請確認.','error');
return true;
}
if(formData.password == "" || formData.repassword == ""){
swal('錯誤','使用者密碼與再次輸入密碼為必填項目,請確認.','error');
return true;
}
if(formData.password != formData.repassword){
swal('錯誤','密碼與確認密碼不同,請確認後送出!','error');
return true;
}
return false;
}
}
```
### ajax
1. 除了dataTable以外的所有ajax請求都必須使用全域function「ajaxPost()」
2. ajaxPost(url,jsonString),只接受傳入網址以及json字串
3. ajaxPost()會自動拋出連線錯誤以及資料庫錯誤,只需要專注於頁面邏輯的撰寫即可。
4. ajaxPost()必須接續 「.done()」 撰寫頁面邏輯。
5. ajaxPost()只接受json進json出。
```javascript
//定義新增動作
submit : function(){
var formData = $("#addUserForm").serializeObject();
if(this.checkForm(formData)) return;
//執行ajaxPost方法,並且傳入網址以及Form表單的Json字串
ajaxPost(base_url('user/addUser'),JSON.stringify(formData))
//定義完成動作,e是json的物件
.done(function(e) {
//狀態判斷
if(e.status == 0){
swal('錯誤','這個帳號已有人使用','error');
}else{
$('#addUser').modal('hide');
loadTable();
swal('新增成功!','','success');
}
});
}
```
## Controller
Controller負責頁面的導向、資料的消毒,以及回傳資料的串接。
### 底層 Infrastructure
所有的controllers都必須繼承Infrastructure並實作construct。而Infrastructure則會繼承CI_Controller並實作construct;所以我們可以在controllers下直接$this->functions(),取用Infrastructure及CI_Controller所定義的成員方法。
1. 可以取得登入狀態以及各式key。
```php
/**
* 回傳是否擁有登入狀態
* @return Boolean
*/
public function getLogin();
/**
* 回傳用戶主鍵
* @return Boolean
*/
public function getKey();
/**
* 回傳用戶名稱
* @return Atring
*/
public function getUserName();
/**
* 回傳地區主鍵
* @return Atring
*/
public function getAreaKey();
/**
* 回傳權限主鍵
* @return Atring
*/
public function getPermissionKey();
/**
* 主鍵解密
* @param String,String
* @return Atring
*/
public function getKeyDecode($encode,$keyName);
```
所有的key都是使用sha加密,在存進資料庫前必須進行解密,請呼叫getKeyDecode()方法,傳入key以及key所屬資料表名稱。
2. XSS消毒
$_POST的值必須使用xss()方法進行消毒,xss()允許傳入陣列並且回傳消毒完成的陣列。
```php
//將json解析為陣列後進行消毒
$data = $this->xss(json_decode($_POST['data'],true));
```
### 頁面小功能定義
每個頁面都可以多個小功能按鈕,這些按鈕可能指向彈跳視窗的啟動,或者是不同的主區塊。
1. 需要使用到頁面小功能的Controller必須定義私有成員變數$multiFunctions。
2. __construct()必須定義每個小功能的按鈕規則以及icon
3. 若是要啟動彈跳視窗,那麼只能透過openModal()進行呼叫。
```php
class Activity extends Infrastructure {
private $multiFunctions;
/**
* 載入父類別建構方法
* 預先處理需執行的項目
*/
public function __construct(){
parent::__construct();
$this->multiFunctions = array(
array(
"title" => "新增活動",
"href" => "javascript:openModal('addActivity');",
"target" => "_self",
"icon" => "fa-plus-square"
),
array(
"title" => "活動管理",
"href" => base_url("activity"),
"target" => "_self",
"icon" => "fa-telegram"
),
array(
"title" => "活動報名",
"href" => base_url("activity/sign"),
"target" => "_self",
"icon" => "fa-sign-in"
)
);
/****/
}
```
### 載入view
1. 有頁面小功能
```php
$data = $this->getPageInfo("活動管理",$this->multiFunctions);
$this->load->view('activity/activity_index_view',$data);
```
2. 沒有頁面小功能
```php
$data = $this->getPageInfo("入館簽到");
$this->load->view('site_view',$data);
```
### dataTable
1. DataTable套件使用 Light-datatables
2. 使用詳情請看說明書 https://hackmd.io/OHEHrdBqTwagNLNy_Jcliw
3. 或是參考User Controller 的用法
### 載入model
1. 統一在__construct進行載入
2. 必須縮短名稱
3. 只要有需求一個Controllers可以同時載入多個model。
```php
$this->load->model("User_model","model",TRUE);
```
### 處理ajax請求
1. 前端傳遞過來的請求一定是POST,若有傳遞值一定是JSON字串。
2. 回傳一定要使用JSON並且必須有status表示狀態以及data表示資料
3. model呼叫失敗一定得使用getDBErrJson回傳失敗的JSON。
```php
/**
* 取得所有權限
*/
public function getAllPermission(){
if($this->getLogin()){
//取得資料並且判斷是否成功
$result = $this->model->getAllPermission($this->getAreaKey());
if($result){
//初始化回傳陣列
$data = array('status'=>1,'data'=>array());
foreach($result as $key => $row){
array_push($data['data'],array(
"key" => $row->key,
"name" => $row->name
));
}
echo json_encode($data);
}else{
//取得失敗JSON回傳
echo $this->model->getDBErrJson();
}
}
}
```
### 頁面重新定向
```php
redirect(base_url("login"));
```
## Model
Model只負責與資料庫溝通以及回傳結果的工作。
### 例外捕捉層 DataBaseError
所有的Model都必須繼承DataBaseError並且實作__construct;而DataBaseError將會繼承CI_Model以及實作__construct,所以在Model中可以使用到DataBaseError與CI_Model所提供的functions。載入model的controllers也可以透過$this->model->functions(),取用DataBaseError所定義的fuinction。
例外捕捉層DataBaseError處理所有資料庫出現錯誤的情形,阻止codelinghter拋出例外,改用json輸出錯誤碼通知前端,讓前端做出相對的反應。
```php
//執行get query並且捕捉錯誤
public function dbGetCatch();
//執行Insert query並且捕捉錯誤
public function dbInsertCatch($tableName);
//執行Delete query並且捕捉錯誤
public function dbDeleteCatch($tableName);
//執行Update query並且捕捉錯誤
public dbUpdateCatch($tableName,$data);
//回傳包裝好的錯誤json
public function getDBErrJson();
```
### 撰寫風格
1. 善用鏈式結構讓程式更清楚明白
2. 回傳必須利用if進行判斷是否成功
```php
public function getAllPermission($area_key){
$this->db
->select('SHA1(`key`) as `key`,name')
->from('permission')
->where('SHA1(area_key)',$area_key);
if($result = $this->dbGetCatch()){
return $result->result();
}else{
return false;
}
}
```
```php
function addUser($dataArray){
$this->db
->set('area_key', $dataArray['area_key'])
->set('permission_key', $dataArray['permission_key'])
->set('account', $dataArray['account'])
->set('password', $dataArray['password'])
->set('name', $dataArray['name'])
->set('birthday', $dataArray['birthday'] ?? "")
->set('phone', $dataArray['phone'] ?? "")
->set('cell', $dataArray['cell'] ?? "")
->set('email', $dataArray['email'] ?? "");
if($result = $this->dbInsertCatch('user')){
return true;
}else{
return false;
}
}
```