数据库中的PHP应用程序设置


PHP Application settings in database

如何在保持DRY的情况下访问对象中的应用程序设置?

我指的不是数据库凭据或API密钥之类的东西,这些都保存在环境变量中。我这里所指的设置是:

WORK_DAY_START_TIME     04:00:00
MAX_HOURS_PER_DAY       13
ALLOW_DAY_CROSSOVER     false

设置影响业务逻辑,对应用程序是全局的,并且在不同的组织中是不同的。目标用户是可能不懂技术的管理人员,因此有一个允许他们更改设置的用户界面。

这些值被持久化到数据库中(尽管持久化介质在这里无关紧要,因为我可以很容易地从XML/INI/JSON/YAML文件中读取)。

下面是一个简单的类,用于访问设置(它有点多,但对于这个问题的目的,它是足够的):

<?php
class Settings implements 'ArrayAccess
{
    private $settings = array();
    public function __construct() {
        // get settings from DB and put them in $this->settings
    }
    public function offsetGet($key) {
        return $this->settings[$key];
    }
}
// In the entry point file:
$settings = new Settings();

我在检查东西时需要这个值:

class Shift extends EntityObject
{
    private function isValid() {
        // For illustrative purposes only
        return !$settings['ALLOW_DAY_CROSSOVER'] && $this->start < $settings['WORK_DAY_START_TIME'] && $this->end < $settings['WORK_DAY_START_TIME'];
    }
}

以下是我想到的一些选项:

  1. global $settings或其替代$GLOBALS['settings']。这依赖于一个全局变量,所以不太好。
  2. 使Settings为单例。几乎相同的问题,很难做单元测试。
  3. 在每个类的构造函数中,实例化一个Settings来获取设置。这似乎有很多开销,特别是当设置已经加载时。
  4. Settings的实例传递到需要设置的每个对象的构造函数中(所以基本上所有对象)。这听起来像依赖注入,如果我理解正确,但它似乎重复?

我该如何处理这个问题?

我想如果我使用Zend_Config,我也会遇到同样的问题。

一个相关的问题是$settings array或Config Class存储项目设置?

  1. 使用全局变量。这是一个坏主意,原因有很多。首先,它打破了封装,这是面向对象编程的全部要点。它还隐藏了一个依赖项。事实是你的类需要设置来执行它的工作,但这一事实是不明显的没有阅读所有的代码。单元测试也变得更加困难,因为您必须在运行每个测试之前考虑全局状态。参见幽灵般的远距离动作

  2. 设置为单例。这基本上会遇到全局变量所具有的所有相同问题……它只是被包装在对象符号中。关于为什么你不应该使用单例的一个很好的观察在这个Clean Code Talk

  3. 为每个方法调用实例化一个新的设置对象可能是一个很好的解决方案,因为这些设置文件有许多不同的变体。

  4. 将依赖项注入构造函数是一个好习惯。它使你的类需要一个设置对象来执行它的工作。这种选择需要考虑一些因素,通常在构建过程中为您的类提供合作者可能会很棘手。使用依赖注入容器可以使这个过程简单得多。

另一件需要注意的事情是,你的名称设置并不是非常具有描述性,也没有真正传达这个对象的职责是什么。您可能希望使用更具描述性的类名来准确地传达对象是什么。

<?php
class EmployeeShiftPolicy {
    private $max_hours_per_shift;
    private $allow_day_crossover;
    private $workday_start_time;
    private $workday_end_time;
    public function __construct(
                                $max_hours,
                                $allow_crossover,
                                'DateTimeInterface $workday_start,
                                'DateTimeInterface $workday_end){
        $this->max_hours_per_shift = $max_hours;
        $this->allow_day_crossover = $allow_crossover;
        $this->workday_start_time  = $workday_start;
        $this->workday_end_time  = $workday_end;
    }
    public function getMaxHoursPerShift(){
        return $this->max_hours_per_shift;
    }
    // other accessors
    public function validateShiftProposal('DateTimeInterface $startTime, 'DateTimeInterface $endTime){
        ...
    }
}

那么当你构造一个移位时,你就可以为它提供一个合适的策略。例如,一个周末,一个学龄儿童,一个有工作限制的人……等。

希望这将提供一些指导您如何建模您的领域。

你的方法很有趣。

  1. 同意,不要使用$GLOBALS
  2. 同意,单例不是面向对象的
  3. 同意,难以维护
  4. 很好的方法,DI对于不同的实现很方便。您现在有了一个实现,但是DI使您的应用程序更具可扩展性/可伸缩性。这个选项非常面向对象!
  5. (会话是一种选择。易于单元测试和存储在内存中(如果你有足够的内存),例如memcached或redis,它非常快。