我在很多项目中重用了Config类(底部的简化代码)。它通常在我的app bootstrap文件中实例化一次,并存储在一个容器类中,我通过静态“makeThis()”方法调用将其作为依赖项注入对象。
因为每次我的应用程序执行时它都会被实例化,所以我希望尽可能地简化它。重复:我希望这个过程尽可能高效。
我在实例化时在Config::$defaults
数组中指定了默认配置值。我想要特别优化的是load_conf_environment()
函数内的属性赋值。你会在这个函数中找到代码注释,进一步阐明我的要求。
下面的代码应该足够清楚(对任何能够提供有效答案的人)来说明该类的工作原理。我正在寻找一种富有想象力和有效率的方法来处理这种操作,如果你能支持你的主张,我并不反对大规模的重构。
最后,让我先发制人地排除一个建议:不,我对使用常量作为目录路径值不感兴趣。我曾经这样做但它使单元测试变得困难。
更新
gettype()
来确定默认值
值的类型,然后转换传递的值,但根据
php文档这不是一个好的解决方案。UPDATE2:
让配置值与默认值的变量类型相匹配是理想的情况,但我只是想到了我想要的那种方式......它甚至可能都不重要。也许只需要编码员有责任提供有效的配置值?有人对此有什么想法吗?
<?php
class Config
{
/**
* The base application directory in which all app_dirs reside
*/
protected $app_path;
/**
* Configuration directives
*
* @var array
* @access protected
*/
protected $vals = array();
/**
* List of default config directive values
*
* @var array
* @access protected
*/
protected $defaults;
/**
* Class constructor -- basically sets default values
*
* @param string $app_path Base application directory
*
* @return void
*/
public function __construct($app_path=NULL)
{
$this->defaults = array(
'debug' => FALSE,
'autoload' => FALSE,
'is_cli_app' => FALSE,
'is_web_app' => FALSE,
'smarty' => FALSE,
'phpar' => FALSE,
'front_url' => '',
'app_dir_bin' => 'bin',
'app_dir_conf' => 'conf',
'app_dir_docs' => 'docs',
'app_dir_controllers' => 'controllers',
'app_dir_lib' => 'lib',
'app_dir_models' => 'models',
'app_dir_test' => 'test',
'app_dir_vendors' => 'vendors',
'app_dir_views' => 'views',
'app_dir_webroot' => 'webroot',
);
if ($app_dir) {
$this->set_app_path($app_path);
}
}
/**
* Setter function for $app_path property
*
* @param string $app_path Path to the app on the server
*
* @return void
* @throws ConfigException On unreadable/nonexistent path
*/
public function set_app_path($app_path)
{
$app_path = dirname(realpath((string)$app_path));
if (is_readable($app_path)) {
$this->app_path = $app_path;
} else {
$msg = 'Specified app path directory could not be read';
throw new ConfigException($msg);
}
}
/**
* Set default values for uninitialized directives
*
* Called after $cfg file is read and applied to fill
* out any unspecified directives.
*
* @return void
*/
protected function set_defaults()
{
foreach ($this->defaults as $key => $val) {
if ( ! isset($this->vals[$key])) {
$this->vals[$key] = $this->is_app_dir($key)
? $this->app_dir . '/' . $this->defaults[$key]
: $this->defaults[$key];
}
}
}
/**
* (Re-)Loads configuration directives
*
* @param mixed $cfg Config array or directory path to config.php
*
* @throws ConfigException On invalid config array
* @return void
*/
public function load_conf_environment($cfg)
{
// Reset all configuration directives
$this->vals = array();
if ( ! is_array($cfg)) {
$cfg = $this->get_cfg_arr_from_file($cfg);
}
if (empty($cfg)) {
$msg = 'A valid $cfg array or environment path is required for ' .
'environment configuration';
throw new ConfigException($msg);
}
foreach ($cfg as $name => $val) {
// does a setter method exist for this directive?
$method = "set_$name";
if (method_exists($this, $method)) {
$this->$method($val);
continue;
}
/*
ASSIGN SPECIFIED DIRECTIVE VALUE
THE ASSIGNED VALUE SHOULD BE OF THE SAME TYPE SPECIFIED by $this->defaults
IF DIRECTIVE IS ONE OF THE 'app_dir' VARS:
- IF IT'S A VALID ABSOLUTE PATH, SET THAT AS THE VALUE
- OTHERWISE, app_dir PATHS SHOULD BE RELATIVE TO $this->app_path
I CONSIDERED USING gettype() TO DETERMINE THE DEFAULT VAR TYPE
AND THEN CAST THE VALUE, BUT THE DOCS SAY THIS IS A BAD IDEA.
*/
}
// set default vals for any directives that weren't specified
$this->set_defaults();
if ( ! $this->vals['is_cli_app'] && ! $this->vals['is_web_app']) {
$msg = 'App must be specified as either WEB or CLI';
throw new Rasmus\ConfigException($msg);
}
}
/**
* Load a configuration array from a specified file
*
* If no valid configuration file can be found,
* an empty array is returned. Valid config paths
*
* @param string $path Config environment directory path
*
* @return array List of config key=>value pairs
* @throws ConfigException On invalid environment path
*/
protected function get_cfg_arr_from_file($path)
{
$path = (string)$path;
$path = rtrim($path, DIRECTORY_SEPARATOR) . '/config.php';
if (is_readable($path)) {
require $path;
if (is_array($cfg) && ! empty($cfg)) {
return $cfg;
}
}
return array();
}
/**
* Has the specified config directive been loaded?
*
* @param string $directive Configuration directive name
*
* @return bool On whether or not a specific directive exists
*/
public function is_loaded($directive)
{
return isset($this->vals[$directive]);
}
/**
* Retrieves a key=>value list of config directives
*
* @return array List of configuration directives
*/
public function get_directives()
{
return $this->vals;
}
/**
* Magic method mapping object properties to $vals array
*
* @param string $name Object property name
*
* @return mixed
*/
public function __get($name)
{
if (isset($this->vals[$name])) {
return $this->vals[$name];
} else {
$msg = "Invalid property: $name is not a valid configuration directive";
throw new OutOfBoundsException($msg);
}
}
}
?>
答案 0 :(得分:1)
处理默认值
是否有理由为在中加载配置文件中的值后为需要的指令设置默认值?
由于默认值已经在__construct
中进行了硬编码,为什么不将它们移动到$vals
属性并避免以后需要设置它们?这样,您的load_conf_environment
方法将覆盖指令已存在的默认值。
验证类型
这是我将使用每个指令的setter方法强制执行的操作。每个setter都应使用is_*
函数或setter参数中的type-hinting验证值的类型,并相应地抛出异常。我可能会要求每个指令都有一个setter。这是更多的工作,但更清洁,100%可执行。
处理app_dir指令(一个选项)
set_app_path
,则运行vals属性并在app_dir指令上调用set_app_dir_directive
方法(见下文),以便它们以应用程序路径为前缀。制作新的set_app_dir_directive
方法:
public function set_app_dir_directive($key, $val)
{
$this->vals[$key] = $this->app_dir . '/' . $val;
}
// then...
public function load_conf_environment($cfg)
{
...
foreach ($cfg as $name => $val) {
$method = "set_$name";
if (method_exists($this, $method)) {
...
}
if ($this->is_app_dir_directive($name)) {
$this->set_app_dir_directive($name, $val);
continue;
}
// Continue storing vals.
...
}
}