Example of Configuration Driven Development with PHP
tokimeki 在回應《動態語言關於參數宣告的寫作風格》時提到: 所以我只要在函數內設定一個預設陣列,然後把參數陣列以及預設陣列丟進去處理就行了,傳回來就得到過濾好的參數陣列,而且保證每個參數都有值。接下來就可以對每個參數作驗證、運算等動作。
這個作法還可以按所謂「Configuration Driven Development」的概念進一步改良。 Configuration Driven Development 是以中介資料描述軟體運作時的組態,我們藉由組態內容便得以調整與協調程式運作的內容。可以參考 Steve McDuff 的這篇:《Configuration-driven development》。
トキメキ 的作法,我也曾做過類似的。我們的概念在於將「預設陣列」的內容視為一份以 PHP 語法描述的組態文件,而不是程式碼的其中一段。當我們需要改變某些程式動作時,我們只需要修改組態文件的內容即可。接下來以一個 web application 程式碼為示範。
組態文件: config.js
我比較過 PHP 和 JavaScript 的語法差異後,覺得 JavaScript 的語法在描述組態資料時較簡單,不會看到 array()
的字眼。所以我習慣上使用 JSON 文件格式儲存組態資料。
{ "id" : { "default" : false, "pattern" : "^\d+$", "error" : "Requried, all Digit." }, "name" : { "default" : false, "pattern" : ".+", "error" : "Required." }, "address" : { "default" : "", "pattern" : false, "error" : "" }, "email" : { "default" : "", "pattern" : "[\w\._-]+@[\w\._-]+", "error" : "Address is invalid." } }
主控程式: index.php
因為組態文件是 JSON 格式,所以 PHP 的版本須為 5.2 版以上 (內建 json extension) ,或是自行安裝 json extension 。(See also JSON in PHP)。
我們利用組態內容驗證及設定使用者輸入的資料內容,之後再傳遞給指定的函數處理。當然啦,如果這些組態內容只用在這裡的話,根本感覺不到有什麼好處。我們會把它丟給 View 繼續運用。
<?php // You may use PHP version 5.2 or above. $method1FieldsJSON = file_get_contents('config.js'); $method1Fields = json_decode($method1FieldsJSON, true); $method1Args = array(); foreach ($_GET as $k => $v) { $method1Args[$k] = $v; } foreach ($_POST as $k => $v) { $method1Args[$k] = $v; } $invalid = true; $invalidFields = array(); foreach($method1Fields as $k => $v) { if (!isset($method1Args[$k])) { if ($v['default'] === false) { break; } else { $method1Args[$k] = $v['default']; } } if ($method1Args[$k] == $v['default'] or ($v['pattern'] and preg_match('/'.$v['pattern'].'/', $method1Args[$k]) ) ) { $invalid = false; } else { $invalid = true; array_push($invalidFields, $k); } } if ($invalid) { require_once $method1View; } else { $method1($method1Args); } ?>
View
在 View 中,我們以 JavaScript 載入同一份組態文件,並使用組態內容檢查表單欄位的輸入值。運用 Configuration-driven development 的概念,我們日後如果要修改欄位的輸入格式檢查,只需要修改 config.js 的內容即可,而不需分別修改主控程式及 View 的內容。
<html> <body> <?php function hiddenInputElement($name, $value) { echo '<input type="hidden" name="', $name, '" value="', $value,'"/>'; } function textInputElement($name, $value) { echo '<input type="text" name="', $name, '" value="', $value,'"/>'; } ?> <script type="text/javascript"> function validForm(formId) { var fields = <?=$method1FieldsJSON;?>; var form = document.getElementById(formId); for (var i = 0; i < form.elements.length; ++i) { k = form.elements[i].name; pattern = new RegExp(fields[k].pattern); if ( form.elements[i].value != fields[k].default && !pattern.test(form.elements[i].value) ) { form.elements[i].focus(); window.alert(fields[k].error); return false; } } return true; } </script> <form id="form1" method="post" onclick="validForm('form1');"> <?=hiddenInputElement('id', $method1Args['id']);?><br/> <?=textInputElement('name', $method1Args['name']);?><br/> <?=textInputElement('address', $method1Args['address']);?><br/> <?=textInputElement('email', $method1Args['email']);?><br/> <button type="submit" value="submit">Submit</button> </form> </body> </html>
從上面的程式碼可以看到同一份組態文件就可以為不同程式語言服務,並使分隔開的程式區塊能夠產生相同的結果 (在此例中是相同的資料驗證結果)。這裡只是牛刀小試,往往我們也會把資料庫的資料欄位訊息也寫在組態文件中,於是組態文件可以兼作資料庫表格的 schema ,再兼程式語言的 class 資料成員宣告,再兼種種你想得到的用途。
總而言之, Configuration-driven development 的概念便是把原本散落在程式碼中的資料,以及以不同語言語法記述的重複資料內容,集合起來以一份中介的結構化文件儲存。一方面避免這些資料四處散佈難以管理;另一方面又可以為我們提供多種用途。
Configuration-driven development 的概念可以說天生就是針對動態語言而出現的,因為許多動態語言的資料描述語法便具有結構性,如果將那些敘述獨立於程式碼以外,則它們就是一份完整的結構化文件。 JSON 就是其中一例。這些敘述既是一份結構化文件,同時又是程式語言的子集,因此在動態語言中幾乎不需要任何額外處理動作就能載入到系統中,可以輕量化實踐 Configuration-driven development 。
樂多舊回應