在PHP中停止使用`global`

我有一个config.php包含到每个页面。 在configuration我创build一个数组,看起来像这样:

 $config = array(); $config['site_name'] = 'Site Name'; $config['base_path'] = '/home/docs/public_html/'; $config['libraries_path'] = $config['base_path'] . '/libraries'; //etc... 

然后,我有function.php ,这也包括几乎每个页面,我必须使用global $config来访问它 – 就是我想摆脱!

如何在不使用global情况下访问我的代码的其他部分$config

任何人都可以解释, 为什么我不应该在我的例子中使用global ? 有人说这是一个不好的语调,别人说这不安全?

编辑1:

我在哪里以及如何使用它的例子:

 function conversion($Exec, $Param = array(), $Log = '') { global $config; $cmd = $config['phppath'] . ' ' . $config['base_path'] . '/' . $Exec; foreach ($Param as $s) { $cmd .= ' ' . $s; } } 

编辑2:

像Vilx所build议的那样把所有这些都放在课上,但是在这种情况下,我怎么把它和下面这个从数据库中提取configurationkeyvalue循环绑定在一起。
我简单地分配$config数组的想法,这里是一个例子:

 $sql = "SELECT * from settings"; $rsc = $db->Execute($sql); if ( $rsc ) { while(!$rsc->EOF) { $field = $rsc->fields['setting_options']; $config[$field] = $rsc->fields['setting_values']; @$rsc->MoveNext(); } } 

编辑3:

另外,我必须从configuration中设置的函数访问其他的vars ,这些vars很less,例如: $db$language等等。

如果我把它们放在课堂上真的能解决什么问题吗? 如果我使用global ,它真的改变了什么?

编辑4:

我读了Gordon用非常好的方式解释为什么你不应该使用global 函数 。 我同意一切,但我不使用global在我的情况下重新分配variables,这将导致,就像他所说, <-- WTF!! ,;))是啊同意,这是疯了。 但是,如果我只是需要从一个函数访问数据库只是通过使用global $db在这种情况下,问题在哪里? 你如何做到这一点,而不使用global

编辑5:

在相同的PHP函数中, deceze说: “反对全局的一个重要原因是它意味着函数依赖于另一个范围,这将很快变得混乱。

但是我在这里谈论基本的“INIT”。 我基本上define但使用vars – 这是错误的技术方式。 但是你的函数不是依赖于任何东西 – 而是你可以记住的一个var $db的名字? 这真的是全球需要使用$db ,这里的依存在哪里,以及如何使用它呢?

PS我只是有一个想法,我们在这里面对两个不同的头脑的冲突,例如: 我的 (但不是很好理解面向对象编程)和那些谁可以称为大师(从我目前的观点)在面向对象 – 对我来说显而易见的是出现了新的问题。 我想这就是为什么这个问题一遍又一遍地被问到。 就我个人而言,毕竟还是比较清楚,但还是有事情要澄清。

反对globalvariables的一点是它们非常紧密地耦合在一起。 你的整个代码库依赖于a)variables名称 $config和b)该variables的存在。 如果你想重命名这个variables(无论出于何种原因),你必须在你的代码库中的任何地方这样做。 你也可以不再使用任何独立于variables的代码片段。

globalvariables示例:

 require 'SomeClass.php'; $class = new SomeClass; $class->doSomething(); 

在上述任何地方,你可能会得到一个错误,因为SomeClass.php的类或者一些代码隐式地依赖于一个全局variables$config 。 虽然只是看着课堂,但没有任何迹象表明这一点。 要解决这个问题,你必须这样做:

 $config = array(...); require 'SomeClass.php'; $class = new SomeClass; $class->doSomething(); 

如果你没有在$config设置正确的密钥,这个代码可能仍然会失败。 由于SomeClass需要或不需要configuration数组的哪些部分并不明显,因此很难重新创build正确的运行环境。 如果你碰巧已经有一个variables$config用于别的什么地方,你想使用SomeClass它也会产生冲突。

因此,而不是创build隐式的,不可见的依赖关系, 注入所有的依赖关系:

 require 'SomeClass.php'; $arbitraryConfigVariableName = array(...); $class = new SomeClass($arbitraryConfigVariableName); $class->doSomething(); 

通过显式传递configuration数组作为参数,解决了上述所有问题。 就像在应用程序中传递所需的信息一样简单。 它也使应用程序的结构和stream程更清晰。 如果你的申请目前是一个大泥潭,可能需要进行一些重组。


您的代码库越大,您必须各个部分彼此解耦 。 如果每个部分都依赖于代码库中的其他部分,那么您不能单独testing,使用或重用其中的任何部分。 这只是陷入混乱。 为了将各部分彼此分离,将它们编码为将所有所需数据作为参数的类或函数。 这会在代码的不同部分之间创build干净的接口(接口)。


试图把你的问题结合到一个例子中:

 require_once 'Database.php'; require_once 'ConfigManager.php'; require_once 'Log.php'; require_once 'Foo.php'; // establishes a database connection $db = new Database('localhost', 'user', 'pass'); // loads the configuration from the database, // the dependency on the database is explicit without `global` $configManager = new ConfigManager; $config = $configManager->loadConfigurationFromDatabase($db); // creates a new logger which logs to the database, // note that it reuses the same $db as earlier $log = new Log($db); // creates a new Foo instance with explicit configuration passed, // which was loaded from the database (or anywhere else) earlier $foo = new Foo($config); // executes the conversion function, which has access to the configuration // passed at instantiation time, and also the logger which we created earlier $foo->conversion('foo', array('bar', 'baz'), $log); 

我将会把这些单独的课程作为练习的读者。 当你试图实现它们时,你会注意到它们非常容易和清晰的实现,并且不需要一个global 。 每个函数和类都以函数参数的forms获取所有必要的数据。 同样显而易见的是,上述组件可以以任何其他组合方式组合在一起,或者依赖关系可以轻易地替代其他组件。 例如,configuration根本不需要来自数据库,或者logging器可以login到文件而不是数据库,而Foo::conversion必须知道这些。


ConfigManager示例实现:

 class ConfigManager { public function loadConfigurationFromDatabase(Database $db) { $result = $db->query('SELECT ...'); $config = array(); while ($row = $result->fetchRow()) { $config[$row['name']] = $row['value']; } return $config; } } 

这是一个非常简单的代码,甚至没有太多的。 你可能会问,为什么你要这个面向对象的代码。 关键是,这使得使用这个代码非常灵活,因为它完全隔离它的一切。 你给一个数据库连接,你得到一个具有一定语法的数组。 input→输出。 清晰的接缝,清晰的界面,最小的,明确的责任。 你可以用一个简单的函数来做同样的事情。

对象具有的额外优点是,它甚至可以进一步将调用loadConfigurationFromDatabase的代码与该函数的任何特定实现分离开来。 如果你只是使用一个全局function loadConfigurationFromDatabase() ,你基本上又遇到了同样的问题:当你试图调用它的时候需要定义这个函数,如果你想用别的东西来replace它,会产生命名冲突。 通过使用一个对象,代码的关键部分移动到这里:

 $config = $configManager->loadConfigurationFromDatabase($db); 

你可以把$configManagerreplace成任何其他也有方法loadConfigurationFromDatabase 对象 。 这是“鸭子打字”。 你不关心$configManager是什么,只要它有一个方法loadConfigurationFromDatabase 。 如果它像鸭子一样走路,像鸭子一样嘎嘎叫,那它就是一只鸭子。 或者说,如果它有一个loadConfigurationFromDatabase方法并返回一个有效的configuration数组,它就是某种ConfigManager。 你已经从一个特定的variables$config ,从一个特定的loadConfigurationFromDatabase函数,甚至从一个特定的ConfigManager解耦你的代码。 所有的零件都可以被更换和换出,并且可以在任何地方dynamicreplace和加载,因为代码不依赖于任何一个特定的其他零件。

loadConfigurationFromDatabase方法本身也不依赖于任何一个特定的数据库连接,只要它可以调用query并获取结果即可。 传入它的$db对象可能是完全假的,只要它的接口仍然performance相同,就可以从XML文件或其他任何地方读取它的数据。

我已经解决了这个类:

 class Config { public static $SiteName = 'My Cool Site'; } function SomeFunction { echo 'Welcome to ' , Config::$SiteName; } 

fcortesbuild议使用常量也是一个好的select。 我只想build议给所有的常量一个前缀,如CFG_SITE_NAME ,以避免与其他常量的意外名称冲突。

对于你的情况,我会创build一个唯一的文件constants.php与定义 (如果你的目的是这些“variables”永远不会改变执行时间):

 define('SITE_NAME','site name'); define('BASE_PATH','/home/docs/public_html/'); ... 

将这个constants.php包含在你需要的所有文件中:

 include_once('constants.php'); 

在面向对象和程序方法(更一般地说,在声明式和命令式之间)之间有一个大的讨论,每种方法都有其优点和缺点。

我使用了一个Singleton(全局OOP版本)的“Config”类。 直到我发现需要在一个应用程序中同时使用几个先前开发的解决scheme之后,它才奏效,因为所有的configuration都是全局的,并且被相同的类(相同的variables名,在你的情况下)引用,每次从其他子应用程序调用代码时切换到正确的configuration。

你有两种方法:

a)以您熟悉的方式devise您的应用程序(这样做会更好,因为您已经有了这方面的经验,可以预测开发将花费多长时间,哪些问题可能会出现,也可能不会出现)。 并且在你将陷入目前的方法的局限之后,重构以避免全局性;

b)看看它是如何在OOP框架中完成的(至less可以看到三个或四个,即Cake,CodeIgniter,Zend,Symfony,Flow3),或者借用一些东西,或者转向使用框架(或者你会更确定你一切正确)。

我创造了一个简单的小class:

 class Config { private static $config = array(); public static function set( $key, $value ) { self::$config[$key] = $value; } public static function get( $key ) { return isset( self::$config[$key] ) ? self::$config[$key] : null; } } Config::set( 'my_config', 'the value' ); echo 'the config value is: ' . Config::get('my_config'); 

这可以很容易被重构为有一个函数isSet( $key )或者可能是一个setAll( $array )

编辑:现在的语法应该是有效的。

你可以很容易地修改这个类,如下所示:

 class Config { private static $config = array(); public static function set( $key, $value ) { self::$config[$key] = $value; } public static function get( $key ) { return isset( self::$config[$key] ) ? self::$config[$key] : null; } public static function setAll( array $array ) { self::$config = $array; } public static function isKeySet( $key ) { return isset( self::$config[ $key ] ); } } Config::setAll( array( 'key' => 'value', 'key2' => array( 'value', 'can be an', 'array' ) ) ); Config::set( 'my_config', 'the value' ); if( Config::isKeySet( 'my_config' ) ) { echo 'the config value is: ' . Config::get('my_config'); } 

您仍然需要将文件包含在使用configuration的任何其他文件中,或使用自动装载器 。

编辑2:

这与使用全局函数非常相似,不同之处在于,您不需要在每个函数的开头都声明要使用它。 如果你想在全球使用configuration,那么configuration必须以某种方式全局化。 当把东西放在全球范围,你需要争论,如果这可能是危险的信息,其他类不意味着看到这个信息…默认configuration? 我认为在全球范围内是安全的,然后你只需要一些易于修改和定制的东西。

如果您决定这是危险的信息,那么对于其他类别而言是不可达的,那么您可能需要检查dependency injection 。 使用dependency injection时,类将在其构造函数中使用一个对象,将其私人放置在要使用的variables中。 这个对象可以是一个configuration类的对象,然后你需要一个包装类,首先创buildconfiguration对象,然后用Template对象注入configuration。 这是一个经常在更复杂的devise模式中看到的devise,例如Domain Driven Design。

config.php文件

 <?php class config { private static $config = array(); public static function set( $key, $value ) { self::$config[$key] = $value; } public static function get( $key ) { if( config::isKeySet( $key ) ) { return isset( self::$config[$key] ) ? self::$config[$key] : null; } } public static function setAll( array $array ) { self::$config = $array; } public static function isKeySet( $key ) { return isset( self::$config[ $key ] ); } } // set valuable values config::setAll( array( 'key' => 'value', 'key2' => array( 'value', 'can be an', 'array' ), 'database' => array( "username" => "root", "password" => "root") ) ); config::set( 'my_config', 'the value' ); ?> 

config.usage.php

 <?php require_once 'config.php'; $database_credentials = config::get('database'); echo 'the config value for username is ' . $database_credentials['username']; echo '<br> the config value for password is ' . $database_credentials['password']; function additionalFunctionality($database_credentials) { echo '<br> the config value for password is ' . $database_credentials['password']; } ?> 

config.usage.too.php

 <?php require_once 'config.php'; // put this first require_once 'config.usage.php'; // include some functionality from another file $database_credentials = Config::get('database'); echo 'the config value for username is ' . $database_credentials['username']; additionalFunctionality($database_credentials); // great ?>