PHP应用程序全局设置

发布于 2024-11-26 21:41:43 字数 1173 浏览 0 评论 0原文

我已经阅读了在 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 方法的注册表不起作用,因为我可以在应用程序过程的任何阶段的任何位置设置任何值)

问题:

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

谢谢您的回答

I have read almost all question I have found on StackOverflow on this topic, but could not find a straight answer.

Here is my code:

Application class

<?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
    }
?>

Settings class:

<?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;
    }
}
?>

I access settings from anywhere in my code like this:

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

I want to access Settings form many different places. All values can be set only once and cannot be overwritten (so registry with __set methods do not work, because I can set any value from any place in any stage of application process)

Questions:

Is it good practice to store global settings like this. What downsides of this method?
Maybe there's a much better way to do this?

Thank you for your answers

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

睫毛上残留的泪 2024-12-03 21:41:43

就像 Wrikken 在对你的问题的评论中指出的那样,你正在将全局状态引入到你的应用程序中。引用 Martin Fowler 关于 Global State 的内容(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 或 Factory 模式替换 Router 中的闭包。我刚刚使用了最简单的方法。这样(希望)更容易理解。您可以检查 http://silex-project.org/ 使用更复杂但类似的方法。

这种方法的主要好处是,每个组件从一开始就可以通过 依赖注入。这将使对代码进行单元测试变得更容易,因为它更容易模拟依赖项并实现测试隔离。

另一个好处是,您可以将构建图和协作者图分开,这样您就不会混淆这些 责任(就像您对Singleton 或以其他方式将 new 关键字放入应该成为信息专家。

Like Wrikken pointed out in the comment to your question, you are introducing Global State to your application. Quoting Martin Fowler on Global State (PoEAA, pg. 482f):

Remember that any global data is always guilty until proven innocent.

which in a nutshell means: avoid it. I leave it up to you to research on that topic though because it's out of scope for this question to discuss it in detail.

Now, for a better alternative

Let's assume you route all traffic to an index.php. You could then simply bootstrap/build all the components you need to fulfill the request inside that file. For instance, like this:

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);

Granted, you rather want to include the autoloader from a separate file (because you want to autogenerate it with something like https://github.com/theseer/Autoload). And of course you could replace the closures in the Router with Builder or Factory patterns. I just used the simplest thing possible. It's (hopefully) easier to understand this way. You can check http://silex-project.org/ for a micro-framework using a more sophisticated but similar approach.

The main benefit of this approach is that every component will get what it needs right from the start through Dependecy Injection. This will make it easier to unit-test your code because its so much easier to mock dependencies and achieve test-isolation.

Another benefit is that you keep construction graph and collaborator graph separate, so you dont mix up those responsibility (like you would with a Singleton or otherwise putting a new keyword into classes that are supposed to be Information Experts.

生活了然无味 2024-12-03 21:41:43

您的 Application 类不应扩展 Settings,因为这两个类之间没有关系。相反,您应该使用 依赖注入 将设置包含到 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 开头的任何方法,等等......

Your Application class should not extend Settings as there is no relationship between the two classes. Instead you should use dependency injection to include the settings into the Application class. There is an example of this below and I recommend reading up on dependency injection.

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);

As mentioned by marcelog in another answer you could use a bootstrap class to handle the injection of the config, as well as other objects, into your Application class.

A basic example of a bootstrap class:

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();

These are very basic examples and there is nothing stopping you from writing magic getters and setters, having the bootstrap call any method that begins with init, etc...

初吻给了烟 2024-12-03 21:41:43

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

无论如何,您可以创建一个 Boostrap 类。该引导类将执行为您的应用程序提供工作环境所需的任何操作(因此,将引导代码从应用程序和设置移至此类)。

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

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

这与 Zend Framework 在 Zend_Application、Zend_Bootstrap 和 Zend_Config 类中所做的类似,您可能想检查它们以获取一些想法。

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

Can you post some more code? just to show how are you accessing those settings.

anyway, you could create a Boostrap class. This bootstrap class will do anything necessary to have working environment for your application (thus, moving out the bootstrapping code from the application and settings, to this class).

it can also instantiate a Settings object, which should be a singleton.

in the Settings object, you can use magic methods (__call, __get) to access the different settings, like Settings::getSettings()->getConfigDirectory(). This magic method will strip the "get" word from the call and try to give a resource with the given name (in this case, a setting named "ConfigDirectory").

This is similar to what Zend Framework does in their Zend_Application, Zend_Bootstrap, and Zend_Config classes, you might want to check them out to get some ideas.

as a side note, i don't see (conceptually speaking) why an application should extend settings. An application should have some settings, but that's quite different from extending them.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文