育兒中心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{ /*********/ } ``` # 開發規則 請務必遵照開發規則進行開發。 ## 名詞對應 ![](https://i.imgur.com/I9Au61u.png) ![](https://i.imgur.com/COi2liW.png) | 編號 | 名稱 |英| | -------- | -------- | -------- | | 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; } } ```