在PHP中,全局变量被认为是一种不好的编程实践吗?如果是,为什么?

93
function foo () {
    global $var;
    // rest of code
}
在我的小型PHP项目中,我通常采用过程化的方式。我通常有一个包含系统配置的变量,当我需要在函数中访问此变量时,我会使用global $var;
这种做法是否不好?

21
全局变量是不良实践的代名词。 - L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳
2
请参见https://dev59.com/mG435IYBdhLWcg3w3EIi - Maxime Pacary
2
尝试进行单元/验收测试,你很快就会发现全局变量为什么是个问题:当你多次执行操作时,它们会使你的代码变得不可靠。 - Kzqai
6个回答

105

当人们在其他语言中谈论全局变量时,它的含义与在PHP中稍有不同。这是因为在PHP中变量并不是真正的全局的。一个典型的PHP程序的作用域是一个HTTP请求。会话变量实际上比PHP中的“全局”变量具有更广泛的作用域,因为它们通常包含许多HTTP请求。

通常(总是?)您可以像这样在方法中调用成员函数preg_replace_callback():

preg_replace_callback('!pattern!', array($obj, 'method'), $str);

更多信息请参阅回调函数

重点是对象已经被添加到了PHP中,并且在某些方面会导致一些尴尬。

不要过分关注将其他语言的标准或结构应用到PHP上。另一个常见的陷阱是通过在所有东西上堆叠对象模型来将PHP变成一个纯粹的OOP语言。

像任何其他东西一样,使用“全局”变量、过程式代码、特定框架和OOP是因为它有意义,解决了问题,减少了需要编写的代码量,或者使其更易于维护和理解,而不是因为您认为自己应该这样做。


8
需要翻译的内容:It should be noted that PHP 5.3 does address some of this with lambda functions allowing you to avoid using function declared in global scope for callbacks. +1 for maintainable, readable code advicePHP 5.3值得一提的是,它引入了lambda函数,允许你避免使用在全局范围内声明的函数作为回调。对于可维护和易读的代码建议加一分。 - Jonathan Fingland
在调用 preg_replace_callback() 时,你不能使用形式为 array($obj, 'callbackMethod') 的回调函数吗?(我知道,我已经陷入了这个面向对象编程的陷阱中...) - grossvogel
29
问题不是“全局变量是否应该使用?”。对于这个问题的答案是,“如果有必要,偶尔可以使用。”问题是它们是否是不良实践。答案是,“有时候是的”。对于发帖者的小项目,可能并没有什么坏处 - 但是对于具有许多团队成员和许多移动部件的较大项目,大量使用全局变量将使代码难以调试,几乎不可能重构,并且阅读起来也很困难。你可以偶尔使用它们,..当然可以 - 它们有点糟糕,..是的! - eddiemoya
@eddiemoya 说得好,Eddie。有很多人为使用全局变量等不良实践辩护。你应该像避开瘟疫一样避免它们。任何像样的软件工程学位都会向你灌输这个观念... 讲师们并不是为了恶作剧而告诉你这些... 他们知道这是几十年经验的结晶。在可能的情况下,应该使用成员函数来访问所需的值,例如在WordPress中使用get_query_var()。 - user2098467
可能是 PHP 标签下最具误导性的答案。 - Your Common Sense

28

全局变量如果不小心使用会使问题更难以找到。假设您请求一个PHP脚本,并收到警告,指出您正在尝试访问某个函数中不存在的数组索引。

如果您要访问的数组是函数内部的局部变量,您可以检查该函数,看看是否在那里犯了错误。这可能是函数输入有问题,因此您需要检查调用该函数的地方。

但如果该数组是全局的,则需要检查使用该全局变量的所有地方,而且不仅如此,还必须确定全局变量的参考顺序。

如果代码片段中有全局变量,则难以隔离该代码的功能。为什么要隔离功能?这样您就可以测试它并在其他地方重复使用。如果您有一些不需要测试且不需要重复使用的代码,则可以使用全局变量。


但是错误通常显示脚本中断的文件/行,所以...我在这里看不到问题。 - samayo
10
脚本出错的地方 != 错误发生的地方。 - HonoredMule

19

我同意被接受的答案。我想再添加两点:

  1. 使用前缀以便立即将其识别为全局变量(例如 $g_)

  2. 在一个位置中声明它们,不要在代码中随意分散它们。


1
是的,我总是在打算全局使用的变量前加下划线。 - KRTac
10
@KRTac 但是通常理解 $_testVariable 是一个私有变量 - 这是一种非正式的标准来定义私有变量,而不是全局变量。 - Aditya M P
7
通常的做法是使用全大写字母来定义全局变量。例如:$DB = 'foo'; - pixeline

12
谁能反驳经验、学位和软件工程?我不行。但是,对于开发面向对象的单页面PHP应用程序而言,我的想法是:只有在无需担心命名空间冲突时,我才会更加乐在其中地构建整个应用程序。现在很多人不再从头开始构建应用程序了。他们有工作、期限、奖金或声誉需要关注。这些人倾向于使用大量的预构建代码来降低风险,而根本不敢冒险使用全局变量。
即使只在程序的全局区域使用全局变量,也可能不好,但是让我们不要忘记那些只想“玩一下并使其正常工作”的人。
如果这意味着在全局命名空间中使用少量变量(<10)仅在程序的全局区域中使用,那么就这样吧。是的,是的,MVC、依赖注入、外部代码等等。但是,如果您已将99.99%的代码封装到命名空间和类中,并且外部代码被限制在沙盒中,则如果您使用全局变量,世界不会结束(我重申,世界不会结束)。
通常,我不会说使用全局变量是“不良实践”。我会说,在程序的全局区域之外使用全局变量(标志等)是“自讨苦吃”的,(从长远来看)是“不明智的”,因为您很容易失去它们的状态。此外,我会说,“您学得越多,就越不依赖于全局变量”,因为您已经体验到了与使用全局变量相关的错误跟踪的“乐趣”。这本身会激励您找到另一种解决同样问题的方法。巧合的是,这往往会推动PHP程序员学习如何使用命名空间和类(静态成员等)。
计算机科学的领域是广阔的。如果我们因为将其标记为“不良”而吓退每个人,那么他们就失去了真正理解标记背后原因的乐趣。

如果必须使用全局变量,请尝试在不使用它们的情况下解决问题。只有你真正理解问题的本质,而不仅仅是问题的描述,才能更好地处理冲突、测试和调试。


1
谢谢你,我很欣赏这里的实用智慧。作为一位没有得到年长者太多支持或必须证明自己身份的新手,我也许不会说出你所说的那些话,但最终来看,我认为如果需要的话,它是一个很好的工具,但不应该依赖它作为你的主要工具,正如你所指出的(学习和扩展解决问题的方法,这是一个美妙的旅程)。 - Frankenmint
1
你用不同的角度把它讲得很好。我们需要“失败”才能学习,并且需要了解为什么“坏习惯”被认为是不好的,才能真正理解它。 - Prid

10

以下是从已结束的SO文档测试版中转载的

我们可以用下面的伪代码来说明这个问题

function foo() {
     global $bob;
     $bob->doSomething();
}

这里的第一个问题很显然:

$bob 是从哪里来的?

你感到困惑了吗?好的。你刚刚学会了全局变量为什么容易混淆且不被推荐使用。如果这是一个真实的程序,那么下一步有趣的事情就是去追踪所有 $bob 的实例并希望你找到了正确的(如果 $bob 在所有地方都被使用则会更糟)。更糟糕的是,如果其他人定义了 $bob (或者你忘记了并重新使用了该变量),你的代码可能会出错(在上面的代码示例中,使用错误的对象或根本没有对象将导致致命错误)。由于几乎所有 PHP 程序都使用像 include('file.php'); 这样的代码,因此随着添加更多文件,维护这样的代码的工作将呈指数级增长。

我们如何避免全局变量?

避免全局变量的最佳方法是一种称为依赖注入的理念。这是我们将需要的工具传递到函数或类中的方法。

function foo(\Bar $bob) {
    $bob->doSomething();
}

这样做会更易于理解和维护。不需要猜测$bob在哪里被设置,因为调用者有责任知道(它正在传递给我们所需的信息)。更好的是,我们可以使用类型声明来限制传递的内容。因此,我们知道$bobBar类的实例或Bar子类的实例,这意味着我们知道我们可以使用该类的方法。结合标准的自动加载程序(自PHP 5.3以来可用),我们现在可以追踪Bar的定义位置。PHP 7.0或更高版本包括了扩展的类型声明,您还可以使用标量类型(如intstring)。


一个不用到处传递 $bob 的替代方法是将类 Bar 设为单例,将 Bar 的一个实例静态地存储在 Bar 本身中,并使用静态方法来实例化 / 获取对象。然后,每当需要时,您只需 $bob = Bar::instance(); 即可。 - Scoots
1
请注意,单例被认为是一种反模式。依赖注入可以避免这些问题。 - Machavity
2
那篇你提供链接的帖子中存在一定程度的争议(例如,最高评分的评论和第二高赞的答案都强烈反对被接受的答案),这让我倾向于认为使用单例应该根据具体情况而定,而不是轻率地驳回它。 - Scoots

1

作为:

global $my_global; 
$my_global = 'Transport me between functions';
Equals $GLOBALS['my_global']

是一种不好的做法(例如 Wordpress 的 $pagenow)... 嗯

考虑一下这个:

$my-global = 'Transport me between functions';

PHP错误 但是:

$GLOBALS['my-global'] = 'Transport me between functions';

这并不是错误,连字符不会与像$pagenow这样的“普通”用户声明变量冲突。而使用大写字母表示正在使用的超全局变量,在代码中易于识别,或者可以通过文件中查找进行跟踪。

如果我懒得为单个解决方案构建所有类,我会使用连字符,例如:

$GLOBALS['PREFIX-MY-GLOBAL'] = 'Transport me ... ';

但在更广泛的使用情况下,我使用一个全局数组:

$GLOBALS['PREFIX-MY-GLOBAL']['context-something'] = 'Transport me ... ';
$GLOBALS['PREFIX-MY-GLOBAL']['context-something-else']['numbers'][] = 'Transport me ... ';

对我来说,后者是关于“可乐轻”目标或用途的良好实践,而不是每次都使用单例类来“缓存”一些数据。如果我错了或漏掉了什么愚蠢的东西,请发表评论...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接