停止在 PHP 中使用 `global`
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/12445972/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Stop using `global` in PHP
提问by Ilia Rostovtsev
I have a config.phpthat is included to each page. In config I create an array that looks something like:
我有一个config.php包含在每个页面中的内容。在配置中,我创建了一个类似于以下内容的数组:
$config = array();
$config['site_name'] = 'Site Name';
$config['base_path'] = '/home/docs/public_html/';
$config['libraries_path'] = $config['base_path'] . '/libraries';
//etc...
Then I have function.php, that is also included to almost each page, where I have to use global $configto get access to it - and thisis what I would like to get rid of!
然后我有function.php,几乎每个页面都包含它,我必须使用它global $config来访问它 -这就是我想摆脱的!
How do I access $configin the other parts of my code without using global?
如何$config在不使用的情况下访问代码的其他部分global?
Could anyone explain, WHYI shouldn't use globalin my example? Some say it's a bad tone, others say it's not secure?
谁能解释一下,为什么我不应该global在我的例子中使用?有人说这是一种不好的语气,有人说它不安全?
EDIT 1:
编辑 1:
Example of where and how I use it:
我在哪里以及如何使用它的示例:
function conversion($Exec, $Param = array(), $Log = '') {
global $config;
$cmd = $config['phppath'] . ' ' . $config['base_path'] . '/' . $Exec;
foreach ($Param as $s)
{
$cmd .= ' ' . $s;
}
}
EDIT 2:
编辑2:
Putting all of this in the class, as suggested by Vilx, would be cool but in this case, how would I tie it with the following loop that is extracting config keyand valuefrom database.
I oversimplified the idea of assigning $configarray, here is an example:
正如Vilx所建议的那样,将所有这些都放在类中会很酷,但在这种情况下,我将如何将它与以下提取配置key和value数据库的循环联系起来。
我过度简化了分配$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();
}
}
EDIT 3:
编辑 3:
Besides, I have to access other varsfrom functions that are set in config and it's few of them, e.g.:$db, $languageand etc.
此外,我有其他访问vars从在配置设置的功能,它是少数人,如:$db,$language等。
If I put them in the class will it really solve anything? If I use globalwhat does it really change?
如果我把它们放在课堂上,它真的能解决任何问题吗?如果我使用global它真的会改变什么?
EDIT 4:
编辑 4:
I read PHP global in functionswhere Gordonexplains in the very nice way why you shouldn't use global. I agree on everything but I don't use globalin my case to reassign the variables, which will result in, like he said, <-- WTF!!, ;)) yeah agree, it's crazy. But if I just need to access database from a function just by using global $dbwhere is the problem in this case? How do you do this otherwise, without using global?
我在函数中阅读了PHP global 中的内容,其中Gordon以非常好的方式解释了为什么不应该使用global. 我同意一切,但global在我的情况下我不使用重新分配变量,这将导致,就像他说的那样<-- WTF!!,;)) 是的,同意,这很疯狂。但是,如果我只需要通过使用函数来访问数据库,global $db在这种情况下问题出在哪里?否则你如何做到这一点,而不使用global?
EDIT 5:
编辑 5:
In the same PHP global in functions decezesays: "The one big reason against global is that it means the function is dependent on another scope. This will get messy very quickly."
在同一个 PHP 全局函数中deceze说:“反对 global 的一个重要原因是它意味着函数依赖于另一个作用域。这会很快变得混乱。”
But I'm talking here about basic 'INIT'. I basically set definebut use vars- well that is wrong in technical way. But your function is not depended on anything - but the name of one var $dbthat you could keep in mind? It's really global need to use $db, where is the DEPENDENCY here and how to use it otherwise?
但我在这里谈论的是基本的“INIT”。我基本上设置define但使用vars- 好吧,这在技术上是错误的。但是你的函数不依赖于任何东西——而是$db你可以记住的一个 var 的名称?这真的是全球需要使用$db,这里的依赖在哪里以及如何使用它?
P.S.I just had a thought, that we're facing the conflict here of two different minds, e.g.: mine(yet NOT well understanding object-oriented programming) and those who could be called gurus (from my current point of view) in OOP - what looks obvious for them for me arises new questions. I think that's why this question is being asked over and over again. Personally for me it's gotten more clear after all but still there are things to clarify.
PS我只是有一个想法,我们在这里面临着两种不同思想的冲突,例如:我的(但不太了解面向对象编程)和那些在OOP中可以被称为大师(从我目前的角度来看)的人- 对我来说看起来很明显的东西出现了新的问题。我认为这就是为什么一遍又一遍地问这个问题的原因。就我个人而言,它毕竟变得更加清晰,但仍有一些事情需要澄清。
回答by deceze
The point against globalvariables is that they couple code very tightly. Your entire codebaseis dependent on a) the variable name$configand b) the existence of that variable. If you want to rename the variable (for whatever reason), you have to do so everywherethroughout your codebase. You can also not use any piece of code that depends on the variable independently of it anymore.
反对global变量的一点是它们非常紧密地耦合代码。您的整个代码库取决于 a) 变量名称$config和 b) 该变量的存在。如果您想重命名变量(无论出于何种原因),您必须在整个代码库中的任何地方都这样做。您也不能再使用任何依赖于变量的代码。
Example with globalvariable:
global变量示例:
require 'SomeClass.php';
$class = new SomeClass;
$class->doSomething();
Anywhere in the above lines you may get an error because the class or some code in SomeClass.phpimplicitly depends on a global variable $config. There's no indication of this whatsoever though just looking at the class. To solve this, you have to do this:
上面几行中的任何地方都可能会出错,因为类或某些代码SomeClass.php隐式地依赖于全局变量$config。尽管只是看班级,但没有任何迹象表明这一点。要解决这个问题,你必须这样做:
$config = array(...);
require 'SomeClass.php';
$class = new SomeClass;
$class->doSomething();
This code may stillfail somewhere if you do not set the correct keys inside $config. Since it's not obvious what parts of the config array SomeClassneeds or doesn't need and when it needs them, it's hard to recreate the correct environment for it to run correctly. It also creates conflicts if you happened to already have a variable $configused for something else wherever you want to use SomeClass.
此代码可能仍然如果不设置内的正确密钥失败的地方$config。由于配置数组的哪些部分SomeClass需要或不需要以及何时需要它们并不明显,因此很难重新创建正确的环境以使其正确运行。如果您碰巧已经有一个变量$config用于其他想要使用的地方,它也会产生冲突SomeClass。
So instead of creating implicit, invisible dependencies, injectall dependencies:
因此,不要创建隐式的、不可见的依赖项,而是注入所有依赖项:
require 'SomeClass.php';
$arbitraryConfigVariableName = array(...);
$class = new SomeClass($arbitraryConfigVariableName);
$class->doSomething();
By passing the config array explicitly as a parameter, all the above problems are solved. It's as simple as handing the required information aroundinside your app. It also makes the structure and flow of the application and what talks to what much clearer. To get to this state if your application is currently a big ball of mud may take some restructuring.
通过显式地将 config 数组作为参数传递,解决了上述所有问题。这就像在您的应用程序内传递所需的信息一样简单。它还使应用程序的结构和流程以及什么对话变得更加清晰。如果您的应用程序目前是一个大泥球,要达到这种状态可能需要进行一些重组。
The bigger your codebase gets, the more you have to decouplethe individual parts from each other. If every part is dependent on every other part in your codebase, you simply cannot test, use or reuse any part of it individually. That simply devolves into chaos. To separate parts from each other, code them as classes or functions which take all their required data as parameters. That creates clean seams (interfaces) between different parts of your code.
您的代码库越大,您就越需要将各个部分彼此分离。如果每个部分都依赖于代码库中的所有其他部分,则您根本无法单独测试、使用或重用它的任何部分。那只会陷入混乱。要将部分彼此分开,请将它们编码为类或函数,这些类或函数将所有必需的数据作为参数。这会在代码的不同部分之间创建干净的接缝(接口)。
Trying to tie your question together into one example:
试图将您的问题合并为一个示例:
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);
I'll leave to implementation of the individual classes up as an exercise for the reader. When you try to implement them, you'll notice that they're very easy and clear to implement and do not require a single global. Every function and class gets all its necessary data passed in the form of function arguments. It should also be obvious that the above components can be plugged together in any other combination or that dependencies can easily be substituted for others. For example, the configuration does not need to come from the database at all, or the logger can log to a file instead of the database without Foo::conversionhaving to know about any of this.
我将把各个类的实现留给读者作为练习。当您尝试实现它们时,您会注意到它们实现起来非常容易和清晰,并且不需要单个global. 每个函数和类都获得以函数参数形式传递的所有必要数据。很明显,上述组件可以以任何其他组合插入在一起,或者依赖项可以很容易地替换为其他组件。例如,配置根本不需要来自数据库,或者记录器可以记录到文件而不是数据库,而Foo::conversion无需知道任何这些。
Example implementation for ConfigManager:
示例实现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;
}
}
It's a very simple piece of code that doesn't even do much. You may ask why you'd want this as object oriented code. The point is that this makes using this code extremely flexible, since it isolates it perfectly from everything else. You give one database connection in, you get one array with a certain syntax back. Input → Output. Clear seams, clear interfaces, minimal, well defined responsibilities. You can do the same with a simple function.
这是一段非常简单的代码,甚至没有做太多事情。您可能会问为什么要将其作为面向对象的代码。关键是这使得使用此代码非常灵活,因为它将它与其他任何东西完美地隔离开来。你输入一个数据库连接,你得到一个具有特定语法的数组。输入 → 输出。清晰的接缝、清晰的接口、最小的、明确定义的职责。你可以用一个简单的函数来做同样的事情。
The extra advantage an object has is that it even further decouples the code that calls loadConfigurationFromDatabasefrom any particular implementation of that function. If you'd just use a global function loadConfigurationFromDatabase(), you basically have the same problem again: that function needs to be defined when you try to call it and there are naming conflicts if you want to replace it with something else. By using an object, the critical part of the code moves here:
对象具有的额外优势是,它甚至进一步将调用loadConfigurationFromDatabase该函数的任何特定实现的代码解耦。如果你只是使用 global function loadConfigurationFromDatabase(),你基本上又会遇到同样的问题:当你尝试调用它时需要定义该函数,如果你想用其他东西替换它,就会出现命名冲突。通过使用对象,代码的关键部分移到这里:
$config = $configManager->loadConfigurationFromDatabase($db);
You can substitute $configManagerhere for any other objectthat also has a method loadConfigurationFromDatabase. That's "duck typing". You don't care what exactly $configManageris, as long as it has a method loadConfigurationFromDatabase. If it walks like a duck and quacks like a duck, it is a duck. Or rather, if it has a loadConfigurationFromDatabasemethod and gives back a valid config array, it's some sort of ConfigManager. You have decoupled your code from one particular variable $config, from one particular loadConfigurationFromDatabasefunction and even from one particular ConfigManager. All parts can be changed and swapped out and replaced and loaded dynamically from anywhere, because the code does not depend on any one particular other piece.
您可以$configManager在此处替换任何其他也有方法的对象loadConfigurationFromDatabase。那就是“鸭子打字”。你不在乎到底$configManager是什么,只要它有一个 method loadConfigurationFromDatabase。如果它走路像鸭子,叫起来像鸭子,那就是鸭子。或者更确切地说,如果它有一个loadConfigurationFromDatabase方法并返回一个有效的配置数组,它就是某种 ConfigManager。您已经将代码与一个特定的变量$config、一个特定的loadConfigurationFromDatabase函数甚至一个特定的函数分离ConfigManager。所有部分都可以从任何地方动态更改和换出、替换和加载,因为代码不依赖于任何一个特定的其他部分。
The loadConfigurationFromDatabasemethod itself also does not depend on any one particular database connection, as long as it can call queryon it and fetch results. The $dbobject being passed into it could be entirely fake and read its data from an XML file or anywhere else instead, as long as its interfacestill behaves the same.
该loadConfigurationFromDatabase方法本身也不依赖于任何一个特定的数据库连接,只要它可以调用query它并获取结果。$db传递给它的对象可能完全是假的,并从 XML 文件或其他任何地方读取其数据,只要其接口的行为仍然相同。
回答by Vilx-
I've solved this with a class:
我已经用一堂课解决了这个问题:
class Config
{
public static $SiteName = 'My Cool Site';
}
function SomeFunction
{
echo 'Welcome to ' , Config::$SiteName;
}
fcortes' suggestion to use constants is also a good one. I'd only like to suggest to give all constants a prefix, like CFG_SITE_NAME, so to avoid accidental name clashes with other constants.
fcortes 使用常量的建议也是一个很好的建议。我只想建议给所有常量一个前缀,比如CFG_SITE_NAME,以避免与其他常量的意外名称冲突。
回答by fcortes
For your case I would create an only file constants.phpwith definitions(if your purpose is these "variables" never be changed in execution time):
对于您的情况,我将创建一个constants.php带有定义的唯一文件(如果您的目的是这些“变量”在执行时永远不会更改):
define('SITE_NAME','site name');
define('BASE_PATH','/home/docs/public_html/');
...
Include this constants.phpin all files where you will need it:
将其包含constants.php在您需要的所有文件中:
include_once('constants.php');
回答by irezvin
There is a big discussing between object-oriented and procedural approaches (and more generally, between declarative and imperative ones) and each approach has its upsides and downsides.
在面向对象方法和过程方法之间(更一般地,在声明性方法和命令性方法之间)之间存在大量讨论,每种方法都有其优点和缺点。
I used 'Config' class that was a Singleton (an OOP version of global). It worked good for me until I had discovered a need to use several of previously-developed solutions together in one application - since all configs were global and referred by the same class (the same variable name, in your case) they conflicted and I had to switch to proper config every time I called code from other sub-application.
我使用了“配置”类,它是一个单例(全局的 OOP 版本)。它对我很有用,直到我发现需要在一个应用程序中一起使用几个以前开发的解决方案 - 因为所有配置都是全局的并且由同一个类(在你的情况下是同一个变量名)引用,它们发生冲突,我有每次我从其他子应用程序调用代码时切换到正确的配置。
You have two ways:
你有两种方法:
a) either design your application in a way you got used to and you are familiar with (that will be better because you already have experience in it and you can predict how long the development will take and what problems may or may not arise); and after you will stuck into limitations of your current approach, refactor to avoid the globals;
a) 要么以您习惯并熟悉的方式设计您的应用程序(这会更好,因为您已经拥有相关经验,并且您可以预测开发需要多长时间以及可能出现或不出现哪些问题);在您陷入当前方法的局限性之后,重构以避免全局变量;
b) look how its done in OOP frameworks (see at least three or four, i.e. Cake, CodeIgniter, Zend, Symfony, Flow3) and either borrow something, or switch to using a framework (or maybe you will be more sure that you do everything right).
b) 看看它是如何在 OOP 框架中完成的(至少看三四个,即 Cake、CodeIgniter、Zend、Symfony、Flow3),然后借用一些东西,或者改用一个框架(或者你可能会更确定你这样做了)一切正常)。
回答by Krycke
I created an easy small 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');
this can easly be refactored to have a function isSet( $key )or maybe a setAll( $array ).
这可以很容易地重构为具有一个函数isSet( $key )或者一个setAll( $array ).
EDIT: Now the syntax should be valid.
编辑:现在语法应该是有效的。
you can easily modify this class like follows:
你可以很容易地修改这个类,如下所示:
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');
}
You still need to include the file in any another file that uses configs, or use an autoloader.
您仍然需要将该文件包含在任何其他使用 configs 的文件中,或者使用autoloader。
EDIT 2:
编辑2:
It's pretty much the same as using a global, with the difference you don't need to state that you want to use it in the beginning of every function. If you want to use Configs globally, then the Configs have to be, in some way global. When putting something in the global scope, you need to argue if this can be dangerous information to an other class not meant to see this information... default configurations? I think it's safe to have in the global scope, and then you just need something that is easy to modify and customize.
这与使用全局变量几乎相同,不同之处在于您不需要在每个函数的开头声明要使用它。如果您想全局使用配置,那么配置必须在某种程度上是全局的。当将某些内容放入全局范围时,您需要争论这对于不打算查看此信息的其他类是否可能是危险信息......默认配置?我认为在全局范围内是安全的,然后你只需要一些易于修改和定制的东西。
If you decide that it's dangerous information, that should not be reachable for a class other then the class it's meant for, then you might want to check in to Dependency injection. With dependency injections a class will take an object in it's constructor, placing it privately in a variable to use. This object can be an object from a configuration class, and then you need a wrapper class creating first the configuration object, and then the Template object injecting the configurations. This is a design often seen in more complex design patterns, like for instance Domain Driven Design.
如果您认为这是危险信息,那么对于除它所针对的类之外的类来说,它不应该是可访问的,那么您可能需要检查Dependency injection。通过依赖注入,类将在其构造函数中获取一个对象,将其私下放置在一个变量中以供使用。该对象可以是来自配置类的对象,然后您需要一个包装类,首先创建配置对象,然后是注入配置的模板对象。这种设计经常出现在更复杂的设计模式中,例如领域驱动设计。
回答by canoodle
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
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
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
?>

