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

  本文举例说明了创建可配置PHP应用程序的几种方法。文中也探讨了应用程序中理想的配置点,并在应用程序过分可配置和过分封闭之间寻求一个平衡点。
  如果计划让其他人或公司可以使用您的PHP应用程序,需要确保该程序是可配置的。至少,要允许用户以一种安全的方式设置数据库登录及密码,从而设置中的材料不会对外公开。论文展示了几篇
  文章另外,文中还为哪些元素需要设置可配置以及如何避免渐变配置过度或者配置不足的困境提供了指导。使用INI文件进行配置PHP内建了
  对
  配置文件的支持。是通过php.ini文件这样的初始化文件(INI)机制实现的,在php.ini文件中定义了数据库连接超时或会话存储方式等常量。如果愿意的话,可以在这个php.ini中文件中为应用程序配置定制。为了说明,我将以下行添加到php.ini代码文件中
  。myapptempdir=foo
  然后,我编写了一个小PHP脚本来读取这个配置项,如清单1所示。
  清单1.ini1.php
  <?php
  function get_template_directory()
  {
  $v=get_cfg_var("myapptempdir");
  返回($v==null)?“临时目录”:$v;
  }
  echo(get_template_directory()."\n");
  ?>
  当在命令行中运行布尔代码时,得到如下结果:
  %php ini1.php
  foo
  %
  太棒了。但为什么不能用标准的INI函数来获取myapptempdir配置项的值呢?我研究了一下,发现在大多数情况下,定制配置项不能使用这些方法来获取。但是,使用get_cfg_var函数却是可以访问的。为了使这个方法更加简单,将变量
  的访问封装在第二个函数中,该函数使用配置键名及一个动作值作为参数,如下所示。
  清单2.ini2.php
  function get_ini_value($n,$dv)
  {
  $c=get_cfg_var($n);
  返回($c==null)?$dv:$c;
  }
  函数get_template_directory()
  {
  return get_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路径中,所以能够从该目录中require PHP文件,而不需要将路径添加到require语句中。
  PHP中的配置
  通常用于在INI文件中存储配置条目的一个替代方法是使用一个简单的PHP脚本来保持数据。如下是一个样例。清单
  4.config.php
  <?php
  #指定临时目录的位置
  #
  $TEMPLATE_DIRECTORY="tempdir";
  ?>
  使用该常量的代码如下所示。
  清单5.php.php
  <?php
  require_once'config.php';
  函数get_template_directory()
  {
  全局$TEMPLATE_DIRECTORY;
  返回$TEMPLATE_DIRECTORY;
  }
  echo(get_template_directory()."\n");
  ?>
  该代码首先包含配置文件(config.php),接下来就可以直接使用这些常量了。
  使用这个技术有很多优势。首先,如果有人正常浏览config.php文件,该页面是空白的。所以可以将config.php放到相同的文件中,并作为Web应用程序的根。第二,在任何编辑器中都可编辑,并且在编辑器中甚至具备语法着色及语法检查功能。
  邻近技术的缺点是,这是一个像INI文件一样的相似技术。将数据文件中提取出来是轻而易举的,但是在PHP文件中调整数据却很困难,在某些情况下甚至是不可能的的。
  下面的替代方法显示了如何编写在本质上既不又可写的配置系统。前面的
  文本文件
  的两个例子对于串口配置入口都是合适的,但对于既读又写的配置参数来首先,看看清单6中的文本配置文件。
  清单6.config.txt
  #我的应用程序的配置文件
  Title=My App
  TemplateDirectory=tempdir
  这是与INI相同的文件格式,但我自己编写的文件了辅助工具。为此,我创建了自己的配置类,如下所示。
  清单7.text1.php
  <?php
  class Configuration
  {
  private$configFile='config.txt';
  私有$items=array();
  函数__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,$找到);
  $this->items[$found[1]]=$found[2];
  fclose($
  fh
  );
  $
  c
  =新配置();
  echo($c->TemplateDirectory."\n");
  ?>
  该代码首先创建了一个配置对象。该构造函数接下来读取config.txt并用解析过的文件内容来设置局部变量$items。
  该脚本并查找TemplateDirectory,这并在没有对象中直接定义。,使用设置成'TemplateDirectory'的$id来调用神奇的__get方法,__get方法针对该键返回$items备份中的值。
  这个__get方法特定于PHP V5环境,所以此脚本必须在PHP V5下运行。上,本文中所有的脚本都需要在PHP V5下运行。
  当在命令行运行该脚本时,可以看到以下结果:
  %php text1.php
  tempdir
  %
  一切都在猜测,对象读取配置。txt文件,然后为TemplateDirectory配置项获得正确的值。
  但对于设置一些配置值,应该怎么做呢?在此类中建立一个新方法及新的测试代码,就能够得到这个功能,如下所示。
  清单8.text2.php
  <?php
  class Configuration
  {
  ...
  function __get($id){return$this->items[$id];}
  function __set($id,$v){$this->items[$id]=$v;}
  函数解析(){...}
  }
  $c=new Configuration();
  echo($c->TemplateDirectory."\n");
  $c->TemplateDirectory='foobar';
  echo($c->TemplateDirectory."\n");
  ?>
  现在,有了一个__set函数,它是__get函数的“堂兄弟”。该函数并不是为一个成员变量获取值,当要设置一个成员变量时,才调用这个函数。底部的测试代码设置值并打印出新值。
  下面是在命令行中运行此代码时出现的结果:
  %php text2.php
  tempdir
  foobar
  %
  太棒了!但是如何能将它存储到文件中,从而导致这个问题固定下来呢?为此,需要写文件并读取它。用于写文件的新函数,如下所示。
  清单9.text3.php
  <?php
  class Configuration
  {
  ...
  function save()
  {
  $nf='';
  $fh=fopen($this->configFile,'r');
  while($l=fgets($fh))
  {
  if(preg_match('/^#/',
  preg_match('/^(.*?)=(.*?)$/',$l,$found);
  $nf.=$found[1]."=".$this->items[$found[1]]."\n";
  }
  else
  {
  $nf.=$l;
  fclose($
  fh
  );
  复制($this->configFile,$this->configFile.'.bak');
  $fh=fopen($this->configFile,'w');
  fwrite($fh,$nf);
  fclose($fh);
  $
  c
  =新配置();
  echo($c->TemplateDirectory."\n");
  $c->TemplateDirectory='foobar';
  echo($c->TemplateDirectory."\n");
  $c->save();
  ?>
  新的保存函数巧妙操作config.txt。我并没有仅用更新过的配置项重写文件(这样会删除注释),而是读取了这个文件并灵活地重写了$items仓库中的内容。这样的话,就保留了文件中的注释。
  在命令行运行该脚本输出并文本配置文件中的内容,能够看到以下输出。
  清单10.保存输出函数
  %php text3.php
  tempdir
  foobar
  %cat config.txt
  #我的应用程序的配置文件
  Title=My App
  TemplateDirectory=foobar
  %
  原始的config.txt文件现已被新值更新了。
  XML配置文件
  虽然文本文件易于阅读及编辑,但不如XML文件流行。,XML有多种适用的编辑器,这些编辑器能够理解标记、特殊符号转义等。所以配置文件的XML版本会不会呢?清单11显示了XML格式的配置文件。清单
  11.config.xml
  <?xml version="1.0"?>
  <config>
  <Title>我的应用程序</Title>
  <TemplateDirectory>tempdir</TemplateDirectory>
  </config>
  清单12显示了使用XML来装载配置设置的配置类的清单
  12.xml1.php
  <?php
  class Configuration
  {
  private$configFile='config.xml';
  私有$items=array();
  函数__construct(){$this->parse();}
  function __get($id){return$this->items[$id];}
  函数解析()
  {
  $doc=new DOMDocument();
  $doc->load($this->configFile);
  $cn=$doc->getElementsByTagName("config");
  $nodes=$cn->item(0)->getElementsByTagName("*");
  foreach($nodes as$node)
  $this->items[$node->nodeName]=$node->nodeValue;
  $
  c
  =新配置();
  echo($c->TemplateDirectory."\n");
  ?>
  看起来XML还有另一个好处:代码比文本版本的代码更加简洁、容易。为了保存这个XML,需要另一个版本的保存函数,将结果保存为XML格式,而不是文本格式。
  清单13.xml2.php
  ...
  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);
  }
  复制($this->configFile,$this->configFile.'.bak');
  $doc->save($this->configFile);
  ...
  be
  代码创建了一个新的XML文档对象模型(Document Object Model,DOM),然后将$items备份中的所有数据都保存到这个模型中。以后完成这些,使用save方法将XML保存为一个文件。
  使用数据库
  最后的替代方式是使用一个数据库保存配置元素的值。那首先使用一个简单的模式来存储配置数据。下面是一个简单的模式。
  清单14.schema.sql
  DROP TABLE IF EXISTS设置;
  CREATE TABLE设置(
  id MEDIUMINT NOT NULL AUTO_INCRMENT,
  name TEXT,
  value TEXT,
  PRIMARY KEY(id)
  );
  这要求进行一些基于应用程序需求的调整。例如,如果让配置元素遵循每个用户进行存储,就需要添加ID用户作为附加的一列。
  为了读取及写入数据,我编写了如图15所示的更新过的配置类。
  清单15.db1.php
  <?php
  require_once('DB.php');
  $dsn='mysql://root:密码 localhost/config';
  $db=&DB::Connect($dsn,array());
  if(PEAR::isError($db)){die($db->getMessage());}
  类配置
  {
  private$configFile='config.xml';
  私有$items=array();
  函数__construct(){$this->parse();}
  function __get($id){return$this->items[$id];}
  函数__set($id,$v)
  {
  全局$db;
  $this->items[$id]=$v;
  $sth1=$db->prepare('从设置中删除名称=?');
  $db->执行($sth1,$id);
  if(PEAR::isError($db)){die($db->getMessage());}
  $sth2=$db->prepare('INSERT INTO设置(id,name,value)VALUES(0,?,?)');
  $db->执行($sth2,array($id,$v));
  if(PEAR::isError($db)){die($db->getMessage());}
  }
  函数parse()
  {
  全局$db;
  $doc=new DOMDocument();
  $doc->load($this->configFile);
  $cn=$doc->getElementsByTagName("config");
  $nodes=$cn->item(0)->getElementsByTagName("*");
  foreach($nodes as$node)
  $this->items[$node->nodeName]=$node->nodeValue;
  $res=$db->query('从设置中选择名称、值');
  if(PEAR::isError($db)){die($db->getMessage());}
  while($res->fetchInto($row)){
  $this->items[$row[0]]=$row[1];
  $
  c=
  新
  配置();
  echo($c->TemplateDirectory."\n");
  $c->TemplateDirectory='new foo';
  echo($c->模板目录.“\n”);
  ?文本>
  这实际上是一个混合的/数据库解决方案。请仔细观察解析方法。该类首先读取文本文件来获取初始值,然后更新读取数据库,然后将按键为最新的值。在设置一个值后,键就从数据库中删除掉,并添加一条具有更新过的值的新记录。
  观察配置类如何通过本文的多个版本来发挥作用是一件有趣的事,该类能从文本文件中、XML及数据库中读取数据,一直保持相同的接口。我鼓励您在开发中也使用具有相同稳定性的接口。对于对象的负载来说,这个工作具体是如何运行的并不明确的。关键是对象与光源之间的契约。
  什么是配置及怎样的配置
  在配置过多的配置选项与配置不足之间找到一个适当的中间点是一件困难的事。可以肯定的是,任何数据库配置(例如,数据库名称、用户用及密码)都应该是可配置的除此允许之外,我还有一些基本的推荐配置项。
  在高级设置中,每个功能都应该有一个独立的启用/禁用选项。根据其应用对程序的重要性来或取消这些选项。例如,在一个Web论坛应用程序中,延迟功能在影响状态下是启用的。但电子邮件通知在影响状态下却是禁用的,这似乎需要定制。
  用户界面(UI)因为选项应该全设置到一个位置上。界面的结构(例如,菜单位置、附加的菜单项、链接到界面特定元素的URL、使用的标志,诸如此类)全部应该设置到一个单一的位置上。我强烈建议不要将字体这些都应该通过样式样式表(Cascading Style Sheets,CSS)来设置,且配置系统指定应该使用哪个CSS文件。CSS是设置字体、样式、颜色等等的一种有效且灵活的方式。有许多出色的CSS工具,您的应用程序应该很好地利用CSS,而不是试图自行设置标准。在
  每个功能中,我建议设置3到10个配置选项。这些配置选项应该以一种有意义的明显的方式命名。如果配置选项能够通过UI设置,在文本文件、XML文件及数据库中的选项名称应该直接同界面元素的标题相关。另外,这些选项全部应该有明确的威胁总的来说
  ,下面这些选项应该是可配置的:电子邮件地址、CSS所使用的东西、从文件中引用的系统资源的位置以及图形元素的文件名。
  对于图形元素,你也许想要创建一个名为独立皮肤的配置文件类型,该类型中包含了对配置文件的设置,包括CSS文件的位置、图形的位置以及这些类型的东西。然后,让用户在多种皮肤文件中进行自定义这使得对应用程序的外观和感觉的大规模改变变得简单。这也为用户提供了一个机会,使应用程序能够在不同的产品安装间更换。皮肤页面并不涵盖这些皮肤文件,但您在这里学习的基础知识将使对皮肤文件的支持配置变得更加简单。
  结束语
  对于任何PHP应用程序来说都是至关重要的一个部分,一开始就应该成为设计的中心部分。我希望本文能够为您的实现配置架构提供一些帮助,应该允许各种配置选项有所指导。