首页 > 语言 > 关键词  > php教程最新资讯  > 正文

构建可配置PHP应用程序的正确方式

2008-09-09 10:19 · 稿源:互联网

本文举例说明了创建可配置PHP应用程序的几种方法。文中也探讨了应用程序中理想的配置点,并在应用程序过分可配置和过分封闭之间寻求一个平衡点。

如果计划让其他人或公司可以使用您的PHP应用程序,需要确保该程序是可配置的。至少,要允许用户以一种安全的方式设置数据库登录及密码,从而使其中的材料不会对外公开。

本文展示了几种用于存储配置设置及编辑这些设置的技术。另外,文中也为哪些元素需要设为可配置以及如何避免陷入配置过度或者配置不足的困境提供了指导。

使用INI文件进行配置

PHP内建了对配置文件的支持。这是通过php.ini文件这样的初始化文件(INI)机制实现的,在php.ini文件中定义了数据库连接超时或会话如何存储等常量。如果愿意的话,可以在这个php.ini文件中为应用程序定制配置。为了说明,我将下列代码行添加到php.ini文件中。

myapptempdir=foo

然后,我编写了一个小PHP脚本来读取这个配置项,如清单1所示。

清单1.ini1.php


<?php
functionget_template_directory()
{
 $v=get_cfg_var("myapptempdir");
 return($v==null)?"tempdir":$v;
}

echo(get_template_directory()."\n");
?>

当在命令行中运行这段代码时,得到如下结果:


%phpini1.php
foo
%

太棒了。但为什么不能用标准的INI函数来获取myapptempdir配置项的值呢?我研究了一下,发现在大多数情况下,定制配置项不能使用这些方法来获取。然而,使用get_cfg_var函数却是可以访问的。

为使这个方法更加简单,将对变量的访问封装在第二个函数中,该函数使用配置键名及一个缺省值作为参数,如下所示。

清单2.ini2.php


functionget_ini_value($n,$dv)
{
 $c=get_cfg_var($n);
 return($c==null)?$dv:$c;
}

functionget_template_directory()
{
 returnget_ini_value("myapptempdir","tempdir");
}

这是对如何访问INI文件的一个很好的概括,所以,如果要使用一个不同的机制或将这个INI文件存储到其他位置,就不需要为更改大量的函数而大费周折。

我不推荐使用INI文件作为应用程序的配置,这有两个理由。首先,虽然这样做较容易读取INI文件,但却几乎不可能安全地写INI文件。所以这样做只适合于只读配置项。第二,php.ini文件在服务器的所有应用程序上共享,所以我认为特定于应用程序的配置项不应该写在该文件中。

需要对INI文件了解什么呢?最重要的是如何重置include路径来添加配置项,如下所示。

清单3.ini3.php


<?php
echo(ini_get("include_path")."\n");
ini_set("include_path",
ini_get("include_path").":./mylib");
echo(ini_get("include_path")."\n");
?>

在本例中,我将我的本地mylib目录添加到了include路径中,所以能够从该目录中requirePHP文件,而不需要将该路径添加到require语句中。

PHP中的配置

通常对于在INI文件中存储配置条目的一个替代办法是使用一个简单的PHP脚本来保持数据。如下是一个样例。

清单4.config.php


<?php
#Specifythelocationofthetemporarydirectory
#
$TEMPLATE_DIRECTORY="tempdir";
?>

使用该常量的代码如下所示。

清单5.php.php


<?php
require_once'config.php';

functionget_template_directory()
{
 global$TEMPLATE_DIRECTORY;
 return$TEMPLATE_DIRECTORY;
}

echo(get_template_directory()."\n");
?>

该代码首先包含配置文件(config.php),接着就可以直接使用这些常量了。

使用这项技术有很多优势。首先,如果某些人仅仅浏览config.php文件,该页面是空白的。所以可以将config.php放到相同的文件中,并作为Web应用程序的根。第二,在任何编辑器中都可编辑,并且在一些编辑器中甚至具备语法着色及语法检查功能。

这项技术的缺点是,这是一个像INI文件一样的只读技术。将数据从此文件中提取出来是轻而易举的,但在该PHP文件中调整数据却很困难,在一些情况下甚至是不可能的。

下面的替代方法显示了如何编写在本质上既可读又可写的配置系统。

#p#分页标题#e#

文本文件

前面的两个例子对于只读配置条目都是合适的,但对于既读又写的配置参数来说又如何呢?首先,看看清单6中的文本配置文件。

清单6.config.txt


#Myapplication'sconfigurationfile
Title=MyApp
TemplateDirectory=tempdir

这是同INI文件相同的文件格式,但我自己编写了辅助工具。为此,我创建了自己的Configuration类,如下所示。

清单7.text1.php


<?php
classConfiguration
{
 private$configFile='config.txt';
 private$items=array();
 function__construct(){$this->parse();}
 function__get($id){return$this->items[$id];}
 functionparse()
 {
$fh=fopen($this->configFile,'r');
while($l=fgets($fh))
{
 if(preg_match('/^#/',$l)==false)
 {
preg_match('/^(.*?)=(.*?)$/',$l,$found);
$this->items[$found[1]]=$found[2];
 }
}
fclose($fh);
 }
}

$c=newConfiguration();

echo($c->TemplateDirectory."\n");
?>

该代码首先创建了一个Configuration对象。该构造函数接下来读取config.txt并用解析过的文件内容来设置局部变量$items。

该脚本随后寻找TemplateDirectory,这并没有在对象中直接定义。因此,使用设置成'TemplateDirectory'的$id来调用神奇的__get方法,__get方法针对该键返回$items数组中的值。

这个__get方法特定于PHPV5环境,所以此脚本必须在PHPV5下运行。实际上,本文中所有的脚本都需要在PHPV5下运行。

当在命令行运行此脚本时,能看到下列结果:


%phptext1.php
tempdir
%

一切都在预料之中,该对象读取config.txt文件,然后为TemplateDirectory配置项获得正确的值。

但对于设置一个配置值,应该怎么做呢?在此类中建立一个新方法及一些新的测试代码,就能够得到这个功能,如下所示。

清单8.text2.php


<?php
classConfiguration
{
 ...

 function__get($id){return$this->items[$id];}

 function__set($id,$v){$this->items[$id]=$v;}
 functionparse(){...}
}
$c=newConfiguration();
echo($c->TemplateDirectory."\n");
$c->TemplateDirectory='foobar';
echo($c->TemplateDirectory."\n");
?>

现在,有了一个__set函数,它是__get函数的“堂兄弟”。该函数并不为一个成员变量获取值,当要设置一个成员变量时,才调用这个函数。底部的测试代码设置值并打印出新值。

下面是在命令行中运行此代码时出现的结果:


%phptext2.php
tempdir
foobar
%

太好了!但如何能将它存储到文件中,从而将使这个改动固定下来呢?为此,需要写文件并读取它。用于写文件的新函数,如下所示。

清单9.text3.php


<?php
classConfiguration
{
 ...

 functionsave()
 {
$nf='';
$fh=fopen($this->configFile,'r');
while($l=fgets($fh))
{
 if(preg_match('/^#/',$l)==false)
 {
preg_match('/^(.*?)=(.*?)$/',$l,$found);
$nf.=$found[1]."=".$this->items[$found[1]]."\n";
 }
 else
 {
$nf.=$l;
 }
}
fclose($fh);
copy($this->configFile,$this->configFile.'.bak');
$fh=fopen($this->configFile,'w');
fwrite($fh,$nf);
fclose($fh);
 }
}

$c=newConfiguration();
echo($c->TemplateDirectory."\n");
$c->TemplateDirectory='foobar';
echo($c->TemplateDirectory."\n");
$c->save();
?>

新的save函数巧妙地操作config.txt。我并没有仅用更新过的配置项重写文件(这样会移除掉注释),而是读取了这个文件并灵活地重写了$items数组中的内容。这样的话,就保留了文件中的注释。

在命令行运行该脚本并输出文本配置文件中的内容,能够看到下列输出。

清单10.保存函数输出


%phptext3.php
tempdir
foobar
%catconfig.txt
#Myapplication'sconfigurationfile
Title=MyApp
TemplateDirectory=foobar
%

原始的config.txt文件现在被新值更新了。

#p#分页标题#e#

XML配置文件

尽管文本文件易于阅读及编辑,但却不如XML文件流行。另外,XML有众多适用的编辑器,这些编辑器能够理解标记、特殊符号转义等等。所以配置文件的XML版本会是什么样的呢?清单11显示了XML格式的配置文件。

清单11.config.xml


<?xmlversion="1.0"?>
<config>
<Title>MyApp</Title>
<TemplateDirectory>tempdir</TemplateDirectory>
</config>

清单12显示了使用XML来装载配置设置的Configuration类的更新版。

清单12.xml1.php


<?php
classConfiguration
{
 private$configFile='config.xml';
 private$items=array();
 function__construct(){$this->parse();}
 function__get($id){return$this->items[$id];}
 functionparse()
 {
$doc=newDOMDocument();
$doc->load($this->configFile);
$cn=$doc->getElementsByTagName("config");
$nodes=$cn->item(0)->getElementsByTagName("*");
foreach($nodesas$node)
$this->items[$node->nodeName]=$node->nodeValue;
 } 
}

$c=newConfiguration();
echo($c->TemplateDirectory."\n");
?>

看起来XML还有另一个好处:代码比文本版的代码更为简洁、容易。为保存这个XML,需要另一个版本的save函数,将结果保存为XML格式,而不是文本格式。

清单13.xml2.php


...
functionsave()
{
 $doc=newDOMDocument();
 $doc->formatOutput=true;

 $r=$doc->createElement("config");
 $doc->appendChild($r);

 foreach($this->itemsas$k=>$v)
 {
$kn=$doc->createElement($k);
$kn->appendChild($doc->createTextNode($v));
$r->appendChild($kn);
 }

 copy($this->configFile,$this->configFile.'.bak');

 $doc->save($this->configFile);
}
...

这段代码创建了一个新的XML文档对象模型(DocumentObjectModel,DOM),然后将$items数组中的所有数据都保存到这个模型中。完成这些以后,使用save方法将XML保存为一个文件。

#p#分页标题#e#

使用数据库

最后的替代方式是使用一个数据库保存配置元素的值。那首先要用一个简单的模式来存储配置数据。下面是一个简单的模式。

清单14.schema.sql


DROPTABLEIFEXISTSsettings;
CREATETABLEsettings(
 idMEDIUMINTNOTNULLAUTO_INCREMENT,
 nameTEXT,
 valueTEXT,
 PRIMARYKEY(id)
);

这要求进行一些基于应用程序需求的调整。例如,如果想让配置元素按照每个用户进行存储,就需要添加用户ID作为额外的一列。

为了读取及写入数据,我编写了如图15所示的更新过的Configuration类。

清单15.db1.php


<?php
require_once('DB.php');
$dsn='mysql://root:password@localhost/config';
$db=&DB::Connect($dsn,array());
if(PEAR::isError($db)){die($db->getMessage());}

classConfiguration
{
 private$configFile='config.xml';
 private$items=array();
 function__construct(){$this->parse();}
 function__get($id){return$this->items[$id];}
 function__set($id,$v)
 {
global$db;
$this->items[$id]=$v;
$sth1=$db->prepare('DELETEFROMsettingsWHEREname=?');
$db->execute($sth1,$id);
if(PEAR::isError($db)){die($db->getMessage());}
$sth2=$db->prepare('INSERTINTOsettings(id,name,value)VALUES(0,?,?)');
$db->execute($sth2,array($id,$v));
if(PEAR::isError($db)){die($db->getMessage());}
 }

 functionparse()
 {
global$db;
$doc=newDOMDocument();
$doc->load($this->configFile);
$cn=$doc->getElementsByTagName("config");
$nodes=$cn->item(0)->getElementsByTagName("*");
foreach($nodesas$node)
$this->items[$node->nodeName]=$node->nodeValue;
$res=$db->query('SELECTname,valueFROMsettings');
if(PEAR::isError($db)){die($db->getMessage());}
while($res->fetchInto($row)){
 $this->items[$row[0]]=$row[1];
}
 }
}

$c=newConfiguration();
echo($c->TemplateDirectory."\n");
$c->TemplateDirectory='newfoo';
echo($c->TemplateDirectory."\n");
?>

这实际上是一个混合的文本/数据库解决方案。请仔细观察parse方法。该类首先读取文本文件来获取初始值,然后读取数据库,进而将键更新为最新的值。在设置一个值后,键就从数据库中移除掉,并添加一条具有更新过的值的新记录。

观察Configuration类如何通过本文的多个版本来发挥作用是一件有趣的事,该类能从文本文件、XML及数据库中读取数据,并一直保持相同的接口。我鼓励您在开发中也使用具有相同稳定性的接口。对于对象的客户机来说,这项工作具体是如何运行的是不明确的。关键的是对象与客户机之间的契约。

什么是配置及怎样配置

在配置过多的配置选项与配置不足间找一个适当的中间点是一件困难的事。可以肯定的是,任何数据库配置(例如,数据库名称、数据库用户用及密码)都应该是可配置的。除此之外,我还有一些基本的推荐配置项。

在高级设置中,每一个特性都应该有一个独立的启用/禁用选项。根据其对应用程序的重要性来允许或禁用这些选项。例如,在一个Web论坛应用程序中,延时特性在缺省状态下是启用的。但电子邮件通知在缺省状态下却是禁用的,因为这似乎需要定制。

用户界面(UI)选项全应该设置到一个位置上。界面的结构(例如,菜单位置、额外的菜单项、链接到界面特定元素的URL、使用的logo,诸如此类)全应该设置到一个单一位置上。我强烈地建议不要将字体、颜色或样式条目指定为配置项。这些都应该通过层叠样式表(CascadingStyleSheets,CSS)来设置,且配置系统应该指定使用哪个CSS文件。CSS是设置字体、样式、颜色等等的一种有效且灵活的方式。有许多出色的CSS工具,您的应用程序应该很好地利用CSS,而不是试图自行设置标准。

在每一个特性中,我推荐设置3到10个配置选项。这些配置选项应该以一种意义明显的方式命名。如果配置选项能够通过UI设置,在文本文件、XML文件及数据库中的选项名称应该直接同界面元素的标题相关。另外,这些选项全应该有明确的缺省值。

总的来说,下面这些选项应该是可配置的:电子邮件地址、CSS所使用的东西、从文件中引用的系统资源的位置以及图形元素的文件名。

对于图形元素,您也许想要创建一个名为皮肤的独立的配置文件类型,该类型中包含了对配置文件的设置,包括CSS文件的位置、图形的位置及这些类型的东西。然后,让用户在多种皮肤文件中进行挑选。这使得对应用程序外观和感觉的大规模更改变得简单。这也同样为用户提供了一个机会,使应用程序能够在不同的产品安装间更换皮肤。本文并不涵盖这些皮肤文件,但您在这里学到的基础知识将会使对皮肤文件的支持变得更加简单。

结束语

可配置性对于任何PHP应用程序来说都是至关重要的一个部分,一开始就应该成为设计的中心部分。我希望本文能够对您实现配置架构提供一些帮助,并对应该允许什么样的配置选项有所指导。

举报

  • 相关推荐
  • AI 应用程序 EPIK 凭借年鉴照片功能在 App Store 上排名第一

    又一款人工智能应用程序火了。飙升至AppStore榜首的人工智能应用程序是EPIK,这是一款照片编辑应用程序,可以让用户生成怀旧的、90年代风格的自己的“年鉴”照片,作为其众多模板之一。这意味着热度最后可能会减退,用户转向下一个人工智能应用趋势。

  • 一条价值百万的Prompt:角色扮演游戏(RPG)助手应用程序开发

    ChatGPT商业理念:PregnancyBlog-一款面向桌面RPG玩家的应用程序,ChatGPT帮助生成角色背景故事、情节曲折或解决游戏中的查询商业计划大纲执行摘要市场机会目标听众产品开发内容创作市场策略货币化和收入未来展望/增长结论ChatGPT业务概要启动时间:2至3周难度级别:中等收入潜力:15,000美元至200,000美元/年您可以使用ChatGPT启动“RPGAssistant”应用程序来创收,该应用程序可增�

  • Dropbox 推出视频应用程序,更新AI工具和网络界面

    Dropbox最近宣布一系列重大更新,旨在适应分布式虚拟工作的新时代。这些更新包括新的视频工具、AI工具的增强功能以及新的订阅计划,旨在提高工作效率和支持用户的不断增长的需求。这些更新将使Dropbox用户能够更高效地处理工作,满足不断增长的工作需求,支持分布式虚拟工作的新时代。

  • OpenAI计划重磅更新 应用程序开发成本可降低20倍

    OpenAI计划在下个月进行重磅更新,以更低价格、更高效的方式帮助开发人员构建专属ChatGPT。这一更新的关键是增加内存存储功能,理论上将应用程序的成本降低了20倍左右。OpenAI的发展将对人工智能领域产生深远的影响,进一步促进大语言模型在各个行业的应用和发展。

  • TikTok引入“Direct Post”功能 支持从AI应用程序直接发布内容

    TikTok最新推出的“DirectPost”功能,允许用户直接从多种热门编辑应用程序发布视频内容。这一功能不仅让第三方应用程序更紧密地与TikTok集成允许它们在自己的平台内设置字幕、受众设置等选项,然后通过单击按钮将信息发送到TikTok。通过这一功能,TikTok将能够充分利用创意应用领域的进步,包括那些正在利用AI技术进行视频编辑的应用。

  • TikTok 现在支持从 AI 支持的 Adobe 应用程序、CapCut、Twitch 等直接发布内容

    TikTok今天推出了一个新功能,允许用户直接从一系列热门编辑应用程序发布视频到其平台上,包括Adobe的人工智能视频编辑软件PremierePro,以及其AI创意应用AdobeExpress等包括Twitch、SocialPilot和字节跳动的CapCut等其他应用。图片来自TikTok这项新功能名为「DirectPost」,实际上是针对希望与TikTok更紧密集成的第三方应用程序开发者的。TikTok指出,该API很快也将支持照片。

  • 高效开源LLM框架Haystack 助力打造高效应用程序

    Haystack是一个端到端的自然语言处理框架,它能够帮助您构建由大型语言模型、Transformer模型、向量搜索等驱动的应用程序。无论您想要进行问题回答、答案生成、语义文档搜索是构建能够进行复杂决策和查询解决的工具,Haystack都可以帮助您使用最先进的NLP模型构建解决您的用例的端到端NLP应用程序。Haystack支持多种文档存储,包括ElasticSearch、Opensearch、Weaviate、Pinecone、FAISS等。

  • 国家网信办发布第一批26家应用程序分发平台备案编号

    国家互联网信息办公室发布了第一批应用程序分发平台备案编号。网信办表示,2022年8月1日《移动互联网应用程序信息服务管理规定》正式实施以来,国家互联网信息办公室依法依规组织开展应用程序分发平台备案管理工作。根据《移动互联网应用程序信息服务管理规定》有关要求,备案仅是对应用程序分发平台提供分发服务行为的确认,不代表对该平台服务能力和其在架应�

  • iPhone 16和iPhone 16 Plus用什么屏幕?苹果亲自回

    有传闻称明年iPhone16和iPhone16Plus将采用“更高刷新频率的显示屏”,但目前尚不清楚是否采用ProMotion技术。苹果公司现已公布最新iPhone15和Pro系列的详细配置iPhone15系列依然使用的是60Hz屏幕。该爆料者在跟帖中澄清说,他并没有说2024年推出的新iPhone将采用120Hz刷新率,但苹果有可能使用90Hz面板Pro版本则独享120Hz显示屏。

  • 蓝色没人要?苹果iPhone 15 Pro同配置机型差价近两千

    我们注意到iPhone15Pro1TB版本不同配色机型之间的价格差距已经接近2000元。iPhone15Pro1TB的官方指导价和官方网站价格仍然为12999元人民币,但在第三方渠道上,蓝色钛金属版本售价已经比官网价格低了1600元左右,黑色钛金属版本为11999元人民币,原色钛金属版本为12299元人民币,白色钛金属版本为13199元人民币。iPhone15Pro1TB蓝色钛金属版本的价格可能还会继续下跌。