我已经阅读了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方法的注册表不起作用,因为我可以在应用程序进程的任何阶段从任何位置设置任何值)
问题:
存储像这样的全局设置是一种好习惯。这种方法的缺点是什么? 也许有更好的方法来做到这一点?
感谢您的回答
答案 0 :(得分:4)
就像Wrikken在您的问题的评论中指出的那样,您正在将Global State引入您的应用程序。引用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
关键字放入类中一样应该是信息专家。
答案 1 :(得分:1)
你可以发布更多代码吗?只是为了说明你是如何访问这些设置的。
无论如何,你可以创建一个Boostrap类。这个引导类将为您的应用程序提供必要的工作环境(从而将引导代码从应用程序和设置移出到此类)。
它还可以实例化一个Settings对象,该对象应该是一个单例。
在Settings对象中,您可以使用魔术方法(__call,__ get)来访问不同的设置,例如Settings :: getSettings() - &gt; getConfigDirectory()。这个神奇的方法将从调用中删除“get”字,并尝试给出具有给定名称的资源(在这种情况下,名为“ConfigDirectory”的设置)。
这与Zend Framework在Zend_Application,Zend_Bootstrap和Zend_Config类中的作用类似,您可能需要查看它们以获得一些想法。
作为附注,我没有看到(从概念上讲)应用程序应该扩展设置的原因。应用程序应该有一些设置,但这与扩展它们完全不同。
答案 2 :(得分:1)
由于两个类之间没有关系,因此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
类。
bootstrap类的基本示例:
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,让bootstrap调用任何以init开头的方法等等......