php配置模式
如果您计划将您PHP应用程序交付给其他人或公司使用,则需要使其可配置。 至少,您希望允许用户以安全的方式设置数据库登录名和密码,以使资料在外部不可见。
本文介绍了几种存储配置设置和编辑它们的技术。 此外,它还提供了有关使哪些元素可配置以及如何避免构建太多或太少的可配置性陷阱的一些指导。
使用INI文件进行配置
PHP附带了对内置配置文件的支持。这是通过php.ini文件中看到的初始化文件(INI)机制进行的,该机制定义了常量,例如数据库连接超时或会话的存储方式。 如果愿意,可以在此php.ini文件中允许应用程序的自定义配置。 为了演示,我将这一行添加到php.ini文件中:
myapptempdir=foo
然后,我编写一个小PHP脚本来读取配置项,如清单1所示。
清单1. ini1.php
<?php
function get_template_directory()
{
$v = get_cfg_var( "myapptempdir" );
return ( $v == null ) ? "tempdir" : $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 );
return ( $c == null ) ? $dv : $c;
}
function 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
# Specify the location of the temporary directory
#
$TEMPLATE_DIRECTORY = "tempdir";
?>
使用此常量的代码如下所示。
清单5. php.php
<?php
require_once 'config.php';
function get_template_directory()
{
global $TEMPLATE_DIRECTORY;
return $TEMPLATE_DIRECTORY;
}
echo( get_template_directory()."\n" );
?>
代码首先需要在配置文件(config.php)中,然后直接使用常量。
使用此技术有很多优点。 首先,如果有人浏览到config.php,则该页面为空白。 因此,您可以将config.php与Web应用程序的根目录放在同一文件中。 其次,它可以在任何编辑器中进行编辑,甚至在某些编辑器中,您甚至可以获得语法着色和检查。
缺点是这是一种类似于INI文件的只读技术。 从该文件中获取数据很简单,但是很难(即使在某些情况下也无法)调整PHP文件中的数据。
接下来的替代方案显示了如何编写本质上可读写的配置系统。
文字档
前面的两个示例都适合于只读配置项目,但是如何读写配置参数呢? 首先,使用清单6中的文本配置文件。
清单6. config.txt
# My application's configuration file
Title=My App
TemplateDirectory=tempdir
该文件格式与INI文件相同,但我为此编写了自己的附件。 为此,我创建了自己的Configuration
类,如下所示。
清单7. text1.php
<?php
class Configuration
{
private $configFile = 'config.txt';
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[1] ] = $found[2];
}
}
fclose( $fh );
}
}
$c = new Configuration();
echo( $c->TemplateDirectory."\n" );
?>
代码要做的第一件事是创建一个Configuration
对象。 然后,该构造函数读取config.txt文件,并使用已解析的文件内容设置本地$items
变量。
然后,脚本将查找TemplateDirectory
,该TemplateDirectory
未在对象上本地定义。 因此,将$id
设置为'TemplateDirectory'
调用魔术__get method
,并且该__get
方法从$items
数组返回该键的值。
此__get
方法特定于PHP V5,因此此脚本必须与PHP V5一起运行。 实际上,本文中的所有脚本都需要PHP V5。
在命令行上运行此脚本时,看到以下结果:
% php text1.php
tempdir
%
如预期的那样,对象将读取config.txt文件,然后为TemplateDirectory
项吐出正确的值。
但是如何设置配置值呢? 这带有该类的新方法和一些新的测试代码,如下所示。
清单8. text2.php
<?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 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( '/^#/', $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 = new Configuration();
echo( $c->TemplateDirectory."\n" );
$c->TemplateDirectory = 'foobar';
echo( $c->TemplateDirectory."\n" );
$c->save();
?>
这个新的save
功能使用config.txt文件玩一个棘手的游戏。 我不只是用更新的项目重写文件(这会删除任何注释),还读取文件并从$items
数组动态重写内容。 这样,注释将保留在文件中。
当我在命令行上运行脚本并打印文本配置文件的内容时,我看到如下所示的输出。
清单10.保存功能输出
% php text3.php
tempdir
foobar
% cat config.txt
# My application's configuration file
Title=My App
TemplateDirectory=foobar
%
现在,原始config.txt文件已更新为新值。
XML配置文件
尽管文本文件易于理解和编辑,但它们不如XML文件流行。 另外,XML文件的优点是可以为他们提供许多了解标签,特殊字符转义等内容的编辑器。 那么配置文件的XML版本是什么样的呢? 清单11将配置文件显示为XML。
清单11. config.xml
<?xml version="1.0"?>
<config>
<Title>My App</Title>
<TemplateDirectory>tempdir</TemplateDirectory>
</config>
清单12显示了使用XML加载配置设置的Configuration
类的更新版本。
清单12. xml1.php
<?php
class Configuration
{
private $configFile = 'config.xml';
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(0)->getElementsByTagName( "*" );
foreach( $nodes as $node )
$this->items[ $node->nodeName ] = $node->nodeValue;
}
}
$c = new Configuration();
echo( $c->TemplateDirectory."\n" );
?>
看起来XML还有另一个好处:代码比文本版本更简洁明了。 要保存XML,我需要另存为XML而不是文本的save
函数版本。
清单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 );
}
copy( $this->configFile, $this->configFile.'.bak' );
$doc->save( $this->configFile );
}
...
此代码创建一个新的XML文档对象模型(DOM),然后将$items
数组中的所有数据添加到其中。 完成之后,使用save
方法将XML保存到文件中。
进入数据库
最后一种选择是使用数据库保存配置元素的值。 从一个简单的模式开始,以存储配置数据。 一个简单的架构如下所示。
清单14. schema.sql
DROP TABLE IF EXISTS settings;
CREATE TABLE settings (
id MEDIUMINT NOT NULL AUTO_INCREMENT,
name TEXT,
value TEXT,
PRIMARY KEY ( 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()); }
class Configuration
{
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( 'DELETE FROM settings WHERE name=?' );
$db->execute( $sth1, $id );
if (PEAR::isError($db)) { die($db->getMessage()); }
$sth2 = $db->prepare(
'INSERT INTO settings ( id, name, value ) VALUES ( 0, ?, ? )' );
$db->execute( $sth2, 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(0)->getElementsByTagName( "*" );
foreach( $nodes as $node )
$this->items[ $node->nodeName ] = $node->nodeValue;
$res = $db->query( 'SELECT name,value FROM settings' );
if (PEAR::isError($db)) { die($db->getMessage()); }
while( $res->fetchInto( $row ) ) {
$this->items[ $row[0] ] = $row[1];
}
}
}
$c = new Configuration();
echo( $c->TemplateDirectory."\n" );
$c->TemplateDirectory = 'new foo';
echo( $c->TemplateDirectory."\n" );
?>
这实际上是文本/数据库混合解决方案。 如果仔细看一下parse
方法,您会发现该类首先读取文本文件中的初始值,然后读取数据库以将键更新为最新值。 然后,在设置值时,将从数据库中删除键,并使用更新后的值添加新记录。
有趣的是,本文中Configuration
类是如何经历了这么多版本的-从文本文件,XML,然后从数据库读取-始终保持相同的接口。 我鼓励您在开发接口时寻求相同的稳定性。 对象的客户不应该确切地知道工作是如何完成的。 重要的是对象与其客户之间的合同。
什么以及如何配置
在配置选项太多和配置选项太少之间找到一个快乐的中介可能很困难。 当然,任何数据库配置(例如,数据库名称,数据库用户名和密码)都应该是可配置的。 但除此之外,我还有一些基本建议。
在较高级别上,每个功能都应具有单独的启用/禁用选项。 应根据功能是否在应用程序中为中心启用或禁用这些选项。 例如,在Web论坛应用程序中,默认情况下应启用审核功能。 但是默认情况下,电子邮件通知应该处于关闭状态,因为它可能需要自定义。
用户界面(UI)选项应全部设置在一个位置。 界面的结构(例如,菜单的位置,其他菜单项,链接到界面某些元素的URL,要使用的徽标等)都应该在一个位置设置。 也就是说,我强烈建议您不要将字体,颜色或样式项指定为配置项。 这些都应使用级联样式表(CSS)进行设置,并且配置系统应指定要使用CSS文件。 CSS是一种设置字体,样式,颜色等的有效而灵活的方法。 有许多出色CSS工具,您的应用程序应合理地使用CSS,而不是尝试设置自己的标准。
在每个功能中,建议您在三个到10个配置选项之间进行选择。 这些名称应以立即有意义的方式命名。 如果可以通过UI设置配置选项,则文本文件,XML文件或数据库中选项的名称应直接与界面元素的标题相关。 此外,所有选项都应具有合理的默认值。
通常,这些项目应该是可配置的:电子邮件地址,要使用CSS,从文件引用的任何系统资源的位置以及图形元素的文件名。
对于图形元素,您可能希望创建一种称为外观的单独类型的配置文件,其中包括配置文件集,包括CSS文件的位置,图形的位置以及这些类型的东西。 然后,让用户在多个外观文件之间进行选择。 这使得可以轻松更改应用程序的外观。 它还为应用程序的用户提供了在产品安装之间交换外观的机会。 本文没有涉及这些皮肤文件,但是您在此处学习的基础知识应该使对皮肤文件的支持更加容易。
结论
可配置性是任何PHP应用程序的重要组成部分,从一开始就应该是设计的核心部分。 我希望本文为您提供了一些有关如何实现配置体系结构的选项,并就允许使用哪些配置选项提供了一些指导。
翻译自: https://www.ibm.com/developerworks/opensource/library/os-php-config/index.html
php配置模式