正在搜索在配置类中设置默认值的更好方法?

时间:2011-12-08 21:06:26

标签: php

我在很多项目中重用了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);
    }
  }
}
?>

1 个答案:

答案 0 :(得分:1)

处理默认值
是否有理由为中加载配置文件中的值后为需要的指令设置默认值?

由于默认值已经在__construct中进行了硬编码,为什么不将它们移动到$vals属性并避免以后需要设置它们?这样,您的load_conf_environment方法将覆盖指令已存在的默认值。

验证类型
这是我将使用每个指令的setter方法强制执行的操作。每个setter都应使用is_*函数或setter参数中的type-hinting验证值的类型,并相应地抛出异常。我可能会要求每个指令都有一个setter。这是更多的工作,但更清洁,100%可执行。

处理app_dir指令(一个选项)

  1. 在构造中,如果调用set_app_path,则运行vals属性并在app_dir指令上调用set_app_dir_directive方法(见下文),以便它们以应用程序路径为前缀。
  2. 制作新的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.
            ...
        }
    }