本文舉例說明了創建可配置 PHP 應用程序的幾種方法文中也探討了應用程序中理想的配置點並在應用程序過分可配置和過分封閉之間尋求一個平衡點
如果計劃讓其他人或公司可以使用您的 PHP 應用程序需要確保該程序是可配置的至少要允許用戶以一種安全的方式設置數據庫登錄及密碼從而使其中的材料不會對外公開
本文展示了幾種用於存儲配置設置及編輯這些設置的技術另外文中也為哪些元素需要設為可配置以及如何避免陷入配置過度或者配置不足的困境提供了指導
使用 INI 文件進行配置
PHP 內建了對配置文件的支持這是通過 phpini 文件這樣的初始化文件(INI)機制實現的在 phpini 文件中定義了數據庫連接超時或會話如何存儲等常量如果願意的話可以在這個 phpini 文件中為應用程序定制配置為了說明我將下列代碼行添加到 phpini 文件中
myapptempdir=foo
然後我編寫了一個小 PHP 腳本來讀取這個配置項如清單 所示
清單 iniphp
<?php
function get_template_directory()
{
$v = get_cfg_var( myapptempdir );
return ( $v == null ) ? tempdir : $v;
}
echo( get_template_directory()\n );
?>
當在命令行中運行這段代碼時得到如下結果
% php iniphp
foo
%
太棒了但為什麼不能用標准的 INI 函數來獲取 myapptempdir
配置項的值呢?我研究了一下發現在大多數情況下定制配置項不能使用這些方法來獲取然而使用 get_cfg_var
函數卻是可以訪問的
為使這個方法更加簡單將對變量的訪問封裝在第二個函數中該函數使用配置鍵名及一個缺省值作為參數如下所示
清單 iniphp
function get_ini_value( $n $dv )
{
$c = get_cfg_var( $n );
return ( $c == null ) ? $dv : $c;
}
function get_template_directory()
{
return get_ini_value( myapptempdir tempdir );
}
這是對如何訪問 INI 文件的一個很好的概括所以如果要使用一個不同的機制或將這個 INI 文件存儲到其他位置就不需要為更改大量的函數而大費周折
我不推薦使用 INI 文件作為應用程序的配置這有兩個理由首先雖然這樣做較容易讀取 INI 文件但卻幾乎不可能安全地寫 INI 文件所以這樣做只適合於只讀配置項第二phpini 文件在服務器的所有應用程序上共享所以我認為特定於應用程序的配置項不應該寫在該文件中
需要對 INI 文件了解什麼呢?最重要的是如何重置 include
路徑來添加配置項如下所示
清單 iniphp
<?php
echo( ini_get(include_path)\n );
ini_set(include_path
ini_get(include_path):/mylib );
echo( ini_get(include_path)\n );
?>
在本例中我將我的本地 mylib 目錄添加到了 include 路徑中所以能夠從該目錄中 require
PHP 文件而不需要將該路徑添加到 require
語句中
PHP 中的配置
通常對於在 INI 文件中存儲配置條目的一個替代辦法是使用一個簡單的 PHP 腳本來保持數據如下是一個樣例
清單 configphp
<?php
# Specify the location of the temporary directory
#
$TEMPLATE_DIRECTORY = tempdir;
?>
使用該常量的代碼如下所示
清單 phpphp
<?php
require_once configphp;
function get_template_directory()
{
global $TEMPLATE_DIRECTORY;
return $TEMPLATE_DIRECTORY;
}
echo( get_template_directory()\n );
?>
該代碼首先包含配置文件(configphp)接著就可以直接使用這些常量了
使用這項技術有很多優勢首先如果某些人僅僅浏覽 configphp 文件該頁面是空白的所以可以將 configphp 放到相同的文件中並作為 Web 應用程序的根第二在任何編輯器中都可編輯並且在一些編輯器中甚至具備語法著色及語法檢查功能
這項技術的缺點是這是一個像 INI 文件一樣的只讀技術將數據從此文件中提取出來是輕而易舉的但在該 PHP 文件中調整數據卻很困難在一些情況下甚至是不可能的
下面的替代方法顯示了如何編寫在本質上既可讀又可寫的配置系統
文本文件
前面的兩個例子對於只讀配置條目都是合適的但對於既讀又寫的配置參數來說又如何呢?首先看看清單 中的文本配置文件
清單 configtxt
# My applications configuration file
Title=My App
TemplateDirectory=tempdir
這是同 INI 文件相同的文件格式但我自己編寫了輔助工具為此我創建了自己的 Configuration
類如下所示
清單 textphp
<?php
class Configuration
{
private $configFile = configtxt;
private $items = array();
function __construct() { $this>parse(); }
function __get($id) { return $this>items[ $id ]; }
function parse()
{
$fh = fopen( $this>configFile r );
while( $l = fgets( $fh ) )
{
if ( preg_match( /^#/ $l ) == false )
{
preg_match( /^(*?)=(*?)$/ $l $found );
$this>items[ $found[] ] = $found[];
}
}
fclose( $fh );
}
}
$c = new Configuration();
echo( $c>TemplateDirectory\n );
?>
該代碼首先創建了一個 Configuration
對象該構造函數接下來讀取 configtxt 並用解析過的文件內容來設置局部變量 $items
該腳本隨後尋找 TemplateDirectory
這並沒有在對象中直接定義因此使用設置成 TemplateDirectory
的 $id
來調用神奇的 __get
方法__get
方法針對該鍵返回 $items
數組中的值
這個 __get
方法特定於 PHP V 環境所以此腳本必須在 PHP V 下運行實際上本文中所有的腳本都需要在 PHP V 下運行
當在命令行運行此腳本時能看到下列結果
% php textphp
tempdir
%
一切都在預料之中該對象讀取 configtxt 文件然後為 TemplateDirectory
配置項獲得正確的值
但對於設置一個配置值應該怎麼做呢?在此類中建立一個新方法及一些新的測試代碼就能夠得到這個功能如下所示
清單 textphp
<?php
class Configuration
{
function __get($id) { return $this>items[ $id ]; }
function __set($id$v) { $this>items[ $id ] = $v; }
function parse() { }
}
$c = new Configuration();
echo( $c>TemplateDirectory\n );
$c>TemplateDirectory = foobar;
echo( $c>TemplateDirectory\n );
?>
現在有了一個 __set
函數它是 __get
函數的 堂兄弟該函數並不為一個成員變量獲取值當要設置一個成員變量時才調用這個函數底部的測試代碼設置值並打印出新值
下面是在命令行中運行此代碼時出現的結果
% php textphp
tempdir
foobar
%
太好了!但如何能將它存儲到文件中從而將使這個改動固定下來呢?為此需要寫文件並讀取它用於寫文件的新函數如下所示
清單 textphp
<?php
class Configuration
{
function save()
{
$nf = ;
$fh = fopen( $this>configFile r );
while( $l = fgets( $fh ) )
{
if ( preg_match( /^#/ $l ) == false )
{
preg_match( /^(*?)=(*?)$/ $l $found );
$nf = $found[]=$this>items[$found[]]\n;
}
else
{
$nf = $l;
}
}
fclose( $fh );
copy( $this>configFile $this>configFilebak );
$fh = fopen( $this>configFile w );
fwrite( $fh $nf );
fclose( $fh );
}
}
$c = new Configuration();
echo( $c>TemplateDirectory\n );
$c>TemplateDirectory = foobar;
echo( $c>TemplateDirectory\n );
$c>save();
?>
新的 save
函數巧妙地操作 configtxt我並沒有僅用更新過的配置項重寫文件(這樣會移除掉注釋)而是讀取了這個文件並靈活地重寫了 $items
數組中的內容這樣的話就保留了文件中的注釋
在命令行運行該腳本並輸出文本配置文件中的內容能夠看到下列輸出
清單 保存函數輸出
% php textphp
tempdir
foobar
% cat configtxt
# My applications configuration file
Title=My App
TemplateDirectory=foobar
%
原始的 configtxt 文件現在被新值更新了
XML 配置文件
盡管文本文件易於閱讀及編輯但卻不如 XML 文件流行另外XML 有眾多適用的編輯器這些編輯器能夠理解標記特殊符號轉義等等所以配置文件的 XML 版本會是什麼樣的呢?清單 顯示了 XML 格式的配置文件
清單 configxml
<?xml version=?>
<config>
<Title>My App</Title>
<TemplateDirectory>tempdir</TemplateDirectory>
</config>
清單 顯示了使用 XML 來裝載配置設置的 Configuration
類的更新版
清單 xmlphp
<?php
class Configuration
{
private $configFile = configxml;
private $items = array();
function __construct() { $this>parse(); }
function __get($id) { return $this>items[ $id ]; }
function parse()
{
$doc = new DOMDocument();
$doc>load( $this>configFile );
$cn = $doc>getElementsByTagName( config );
$nodes = $cn>item()>getElementsByTagName( * );
foreach( $nodes as $node )
$this>items[ $node>nodeName ] = $node>nodeValue;
}
}
$c = new Configuration();
echo( $c>TemplateDirectory\n );
?>
看起來 XML 還有另一個好處代碼比文本版的代碼更為簡潔容易為保存這個 XML需要另一個版本的 save
函數將結果保存為 XML 格式而不是文本格式
清單 xmlphp
function save()
{
$doc = new DOMDocument();
$doc>formatOutput = true;
$r = $doc>createElement( config );
$doc>appendChild( $r );
foreach( $this>items as $k => $v )
{
$kn = $doc>createElement( $k );
$kn>appendChild( $doc>createTextNode( $v ) );
$r>appendChild( $kn );
}
copy( $this>configFile $this>configFilebak );
$doc>save( $this>configFile );
}
這段代碼創建了一個新的 XML 文檔對象模型(Document Object Model DOM)然後將 $items
數組中的所有數據都保存到這個模型中完成這些以後使用 save
方法將 XML 保存為一個文件
使用數據庫
最後的替代方式是使用一個數據庫保存配置元素的值那首先要用一個簡單的模式來存儲配置數據下面是一個簡單的模式
清單 schemasql
DROP TABLE IF EXISTS settings;
CREATE TABLE settings (
id MEDIUMINT NOT NULL AUTO_INCREMENT
name TEXT
value TEXT
PRIMARY KEY ( id )
);
這要求進行一些基於應用程序需求的調整例如如果想讓配置元素按照每個用戶進行存儲就需要添加用戶 ID 作為額外的一列
為了讀取及寫入數據我編寫了如圖 所示的更新過的 Configuration
類
清單 dbphp
<?php
require_once( DBphp );
$dsn = mysql://root:password@localhost/config;
$db =& DB::Connect( $dsn array() );
if (PEAR::isError($db)) { die($db>getMessage()); }
class Configuration
{
private $configFile = configxml;
private $items = array();
function __construct() { $this>parse(); }
function __get($id) { return $this>items[ $id ]; }
function __set($id$v)
{
global $db;
$this>items[ $id ] = $v;
$sth = $db>prepare( DELETE FROM settings WHERE name=? );
$db>execute( $sth $id );
if (PEAR::isError($db)) { die($db>getMessage()); }
$sth = $db>prepare(
INSERT INTO settings ( id name value ) VALUES ( ? ? ) );
$db>execute( $sth array( $id $v ) );
if (PEAR::isError($db)) { die($db>getMessage()); }
}
function parse()
{
global $db;
$doc = new DOMDocument();
$doc>load( $this>configFile );
$cn = $doc>getElementsByTagName( config );
$nodes = $cn>item()>getElementsByTagName( * );
foreach( $nodes as $node )
$this>items[ $node>nodeName ] = $node>nodeValue;
$res = $db>query( SELECT namevalue FROM settings );
if (PEAR::isError($db)) { die($db>getMessage()); }
while( $res>fetchInto( $row ) ) {
$this>items[ $row[] ] = $row[];
}
}
}
$c = new Configuration();
echo( $c>TemplateDirectory\n );
$c>TemplateDirectory = new foo;
echo( $c>TemplateDirectory\n );
?>
這實際上是一個混合的文本/數據庫解決方案請仔細觀察 parse
方法該類首先讀取文本文件來獲取初始值然後讀取數據庫進而將鍵更新為最新的值在設置一個值後鍵就從數據庫中移除掉並添加一條具有更新過的值的新記錄
觀察 Configuration
類如何通過本文的多個版本來發揮作用是一件有趣的事該類能從文本文件XML 及數據庫中讀取數據並一直保持相同的接口我鼓勵您在開發中也使用具有相同穩定性的接口對於對象的客戶機來說這項工作具體是如何運行的是不明確的關鍵的是對象與客戶機之間的契約
什麼是配置及怎樣配置
在配置過多的配置選項與配置不足間找一個適當的中間點是一件困難的事可以肯定的是任何數據庫配置(例如數據庫名稱數據庫用戶用及密碼)都應該是可配置的除此之外我還有一些基本的推薦配置項
在高級設置中每一個特性都應該有一個獨立的啟用/禁用選項根據其對應用程序的重要性來允許或禁用這些選項例如在一個 Web 論壇應用程序中延時特性在缺省狀態下是啟用的但電子郵件通知在缺省狀態下卻是禁用的因為這似乎需要定制
用戶界面(UI)選項全應該設置到一個位置上界面的結構(例如菜單位置額外的菜單項鏈接到界面特定元素的 URL使用的 logo諸如此類)全應該設置到一個單一位置上我強烈地建議不要將字體顏色或樣式條目指定為配置項這些都應該通過層疊樣式表(Cascading Style SheetsCSS)來設置且配置系統應該指定使用哪個 CSS 文件CSS 是設置字體樣式顏色等等的一種有效且靈活的方式有許多出色的 CSS 工具您的應用程序應該很好地利用 CSS而不是試圖自行設置標准
在每一個特性中我推薦設置 到 個配置選項這些配置選項應該以一種意義明顯的方式命名如果配置選項能夠通過 UI 設置在文本文件XML 文件及數據庫中的選項名稱應該直接同界面元素的標題相關另外這些選項全應該有明確的缺省值
總的來說下面這些選項應該是可配置的電子郵件地址CSS 所使用的東西從文件中引用的系統資源的位置以及圖形元素的文件名
對於圖形元素您也許想要創建一個名為皮膚 的獨立的配置文件類型該類型中包含了對配置文件的設置包括 CSS 文件的位置圖形的位置及這些類型的東西然後讓用戶在多種皮膚文件中進行挑選這使得對應用程序外觀和感覺的大規模更改變得簡單這也同樣為用戶提供了一個機會使應用程序能夠在不同的產品安裝間更換皮膚本文並不涵蓋這些皮膚文件但您在這裡學到的基礎知識將會使對皮膚文件的支持變得更加簡單
結束語
可配置性對於任何 PHP 應用程序來說都是至關重要的一個部分一開始就應該成為設計的中心部分我希望本文能夠對您實現配置架構提供一些幫助並對應該允許什麼樣的配置選項有所指導
From:http://tw.wingwit.com/Article/program/PHP/201311/20775.html