【问题标题】:PHP Application settings in database数据库中的 PHP 应用程序设置
【发布时间】:2015-08-22 04:31:59
【问题描述】:

如何在保持 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 or Config Class to store project settings?

【问题讨论】:

  • 您尝试过 $_SESSION 吗?如果您不想使用 $_SESSION,您始终可以使用返回数组的 php 文件。另一种选择是使用像memcached.org 这样的缓存服务器。没有其他办法
  • 我会选择选项 4。根据定义,设置是您的类(或设置中的特定值)的依赖项。除了注入依赖之外,做任何事情都会隐藏这个事实。

标签: php design-patterns configuration


【解决方案1】:
  1. 使用全局。由于许多原因,这是一个坏主意。首先,它打破了封装,这是面向对象编程的重点。它还隐藏了一个依赖项。事实是您的类需要设置来执行它的工作,但如果不阅读所有代码,这一事实并不明显。单元测试也变得更加困难,因为您必须在运行每个测试之前考虑全局状态。另请参阅spooky action at a distance

  2. 将设置设为单例。这基本上会遇到全局变量所具有的所有相同问题......它只是包装在对象表示法中。这个Clean Code Talk

  3. 很好地说明了为什么你不应该使用单例
  4. 为每个方法调用实例化一个新的设置对象可能是一个很好的解决方案,因为这些设置文件有许多不同的变体。

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

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

<?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){
        ...
    }
}

然后,当您构建班次时,您可以为其提供适当的策略。即周末一份,学龄儿童一份,有工作限制的人一份……等等。

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

【讨论】:

    【解决方案2】:

    非常有趣,你的方法。

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

    【讨论】:

    • 会话只是另一种全局状态,它们的缺点与选项 1 和 2 相同
    • 橙丸,我明白了。但是例如phpunit,您可以轻松地对会话进行单元测试。结合速度,那就太好了。但是,最佳设计的选项 4。
    • 全局状态是全局状态,在谈论测试时,速度应该是您最不关心的问题。通常,如果速度是一个很大的问题,那是因为您没有进行单元测试......您为了测试而不是小型孤立测试而构建和拆除应用程序的大部分。
    猜你喜欢
    • 1970-01-01
    • 2017-06-24
    • 1970-01-01
    • 2016-10-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-14
    相关资源
    最近更新 更多