【问题标题】:php application global settingsphp应用程序全局设置
【发布时间】:2011-10-14 11:25:28
【问题描述】:

我已经阅读了我在 StackOverflow 上找到的关于该主题的几乎所有问题,但找不到直接的答案。

这是我的代码:

应用类

<?php
    class Application extends Settings {
        public function __construct($env, $cacheDir, $configFile) {
            self::$_env = $env;
            self::$_cacheDir = $cacheDir;
            self::$_config = $this->loadConfig($configFile) // reads configs from xml file into Config object
        }

        // other methods
    }
?>

设置类:

<?php
class Settings {
    protected static $_env = null;
    protected static $_cacheDir = null;
    protected static $_config = null;

    public static function getEnv() {
        return self::$_env;
    }

    public static function getCacheDir() {
        return self::$_cacheDir;
    }

    public static function getConfig() {
        return self::$_config;
    }
}
?>

我可以从代码中的任何位置访问设置,如下所示:

<?php
var_dump(Settings::getEnv());
?>

我想从许多不同的地方访问设置。所有值只能设置一次并且不能被覆盖(因此使用 __set 方法的注册表不起作用,因为我可以在应用程序的任何阶段从任何位置设置任何值)

问题:

像这样存储全局设置是一种好习惯吗?这种方法有什么缺点? 也许有更好的方法来做到这一点?

感谢您的回答

【问题讨论】:

  • 你是一个类的有效全局状态,引入了一个不完全是单例,但有点像它。它有它的用途,也有它的优点和缺点。我,我更喜欢依赖注入,因为这类结构很难编写测试。
  • @Wrikken 我完全同意你的看法。但我看不到没有单例重写我的代码的方法

标签: php settings global-variables


【解决方案1】:

您可以发布更多代码吗?只是为了显示您如何访问这些设置。

无论如何,您可以创建一个 Boostrap 类。这个引导类将做任何必要的事情来为您的应用程序提供工作环境(因此,将引导代码从应用程序和设置移出到这个类)。

它还可以实例化一个设置对象,它应该是一个单例。

在 Settings 对象中,您可以使用魔术方法(__call、__get)来访问不同的设置,例如 Settings::getSettings()->getConfigDirectory()。这个神奇的方法会从调用中去掉“get”字,并尝试给一个具有给定名称的资源(在这种情况下,一个名为“ConfigDirectory”的设置)。

这类似于 Zend Framework 在其 Zend_Application、Zend_Bootstrap 和 Zend_Config 类中所做的事情,您可能需要查看它们以获得一些想法。

附带说明,我不明白(从概念上讲)为什么应用程序应该扩展设置。一个应用程序应该有一些设置,但这与扩展它们有很大的不同。

【讨论】:

  • 设置类没有任何理由成为单例,也没有任何理由让他用魔法方法乱扔那个类。如果您真的认为应该这样做,请详细说明您的设计决策。
  • 然后请说明您自己的解决方案
  • @Gordon:我完全同意,单身人士很糟糕,期间。如果我想同时拥有两种不同的配置怎么办,因为只能有一个对象,所以我不能用单例来实现。至于魔术__get 和__set 方法。它们在配置类中很有用,但我会让它们保持简单。
  • 单身不好?同时有2种不同的配置?从来没有听说过这样的事情:) 但是,我可以讨论魔术方法的使用(它们根本不会“乱扔”任何东西)。但是前两个陈述......我没有看到任何有效的论据可以让你们两个证明它们的合理性。
  • 这里有一些单身人士:stackoverflow.com/questions/4595964/who-needs-singletons/…。确保您遵循该答案中的链接,尤其是链接到 Clean Code 讨论的最后一个链接。
【解决方案2】:

您的Application 类不应扩展Settings,因为这两个类之间没有关系。相反,您应该使用dependency injection 将设置包含到Application 类中。下面有一个例子,我建议阅读依赖注入。

class Settings {
    // public to simplify example, you can add setters and getters
    public $_env = null;
    public $_cacheDir = null;
    public $_config = null;
}

class Application {
    protected $config;

    public function setConfig($config) {
        $this->config = $config;
    }

}



$app = new Application();

$config = new Settings();

$config->_env = 'dev';
$config->_cacheDir = '/my/dir';
$config->_config = array(/* Config here */);

$app->setConfig($config);

正如 marcelog 在另一个答案中提到的,您可以使用引导类来处理将配置以及其他对象注入到您的 Application 类中。

引导类的基本示例:

class Bootstrap {

    protected $application;

    public function __construct(Application $app) {
        $this->application = $app;
    }

    // connivence method
    public function init() {
        $this->initSettings();
    }

    public function initSettings() {
        $settings = new Settings();
        $settings->_env = 'dev';
        $settings->_cacheDir = '/my/dir';

        $config = array(); // load config from file here
        $settings->_config = config;
        $this->application->setSettings($settings);
    }

    // other init methods
}

$app = new Application();

$bootstrap = new Bootstrap($app);

$bootstrap->init();

这些是非常基本的示例,没有什么能阻止您编写魔法 getter 和 setter,让引导程序调用任何以 init 开头的方法等等......

【讨论】:

  • 我应该如何从我的应用程序的其他文件中访问这些设置?我应该将应用程序类作为参考传递给每个需要一些设置的类吗?
  • 这在很大程度上取决于您的应用程序布局和您需要设置对象的类,通常应该只有少数几个地方需要注入设置对象。给我一些其他类需要设置信息的例子,我会尽力提供帮助。 :)
【解决方案3】:

就像 Wrikken 在对您的问题的评论中指出的那样,您正在将全局状态引入您的应用程序。引用 Martin Fowler 的全球状态(PoEAA,第 482f 页):

请记住,在被证明无罪之前,任何全局数据都是有罪的。

简而言之就是:避免它。我把它留给你来研究这个话题,因为这个问题超出了详细讨论的范围。

现在,寻找更好的选择

假设您将所有流量路由到 index.php。然后,您可以简单地引导/构建完成该文件中的请求所需的所有组件。例如,像这样:

spl_autoload_register(
    function($className) {
        static $classMap = array(
            'request' => '/path/from/here/to/Request.php',
             … more mapping
        );
        require __DIR__ . $classMap[strtolower($className)];
    }
);

$config  = parse_ini_file(__DIR__ . '/path/from/here/to/config.ini');
foreach($config['env'] as $key => $val) {
    ini_set($key, $val);
}

$router = new Router;
$router->registerActionForRoute(
    '/product/list', 
    function($request, $response) use ($config) {
        return new ProductListAction(
            $request, $response
            new ProductMapper(
                new ProductGateway(
                    new MySqli($config['db']['host'], …),
                    new Cache($config['cache'], …)
                ),
                new ProductBuilder;
            )
        );
    }
);
$router->registerActionForRoute(…);
$router->execute(new Request($_GET, $_POST, $_SERVER), new Response);

当然,您宁愿从一个单独的文件中包含自动加载器(因为您想使用 https://github.com/theseer/Autoload 之类的东西自动生成它)。当然,您可以用Builder 或工厂模式替换路由器中的闭包。我刚刚使用了simplest thing possible。 (希望)这种方式更容易理解。您可以使用更复杂但类似的方法检查 http://silex-project.org/ 以获取微框架。

这种方法的主要好处是每个组件都会从一开始就通过Dependecy Injection 得到它需要的东西。这将使您更容易对代码进行单元测试,因为它更容易模拟依赖项并实现测试隔离。

另一个好处是您将构造图和协作者图分开,因此您不会混淆那些 responsibility(就像您使用 Singleton 或以其他方式将 new 关键字放入应该是信息的类中一样专家。

【讨论】:

    猜你喜欢
    • 2010-09-05
    • 1970-01-01
    • 2014-01-12
    • 2011-08-15
    • 2015-05-26
    • 2019-01-06
    • 2013-03-14
    • 2012-03-13
    • 2011-11-19
    相关资源
    最近更新 更多