PHP 全局函数

PHP global in functions

提问人:Pascal Qyy 提问时间:3/2/2011 最后编辑:starballPascal Qyy 更新时间:12/19/2022 访问量:28520

问:

global 关键字的用处是什么?

有什么理由偏爱一种方法而不是另一种方法吗?

  • 安全?
  • 性能?
  • 别的东西?

方法1:

function exempleConcat($str1, $str2)
{
  return $str1.$str2;
}

方法2:

function exempleConcat()
{
  global $str1, $str2;
  return $str1.$str2;
}

什么时候使用有意义?global

对我来说,这似乎很危险......但这可能只是缺乏知识。我对文档(例如代码示例、文档链接等)技术原因感兴趣。


赏金

这是一个关于这个话题的很好的一般问题,我(@Gordon)正在提供赏金以获得额外的答案。你的答案是与我的观点一致还是给出不同的观点并不重要。由于这个话题时不时地出现,我们可以使用一个好的“规范”答案来链接。global

PHP 语言设计

评论

2赞 JohnP 3/2/2011
看看这个链接: stackoverflow.com/questions/1557787 本页右下角有很多相关文章
1赞 Pascal Qyy 3/6/2011
所以,我看不懂任何亲全球的关键词。1)为什么它在这里。2)人们为什么使用它?
0赞 Ólafur Waage 3/2/2011
这不是您问题的直接答案,但请阅读这个较旧的 SO 问题
0赞 PeeHaa 1/14/2012
@G.Qyy 为什么会有?人们为什么要使用它?他们不使用它(我希望至少):Pgoto
0赞 Pascal Qyy 4/12/2015
去年年底(12月14日),有人对这个问题投了反对票。我很想知道为什么,因为所有的观点,包括负面的观点,都很有趣。在这种情况下,比以往任何时候都更重要!我将非常感谢有关它的任何线索。

答:

6赞 Bob Fanger 3/2/2011 #1

使用 global 关键字制作 concat 函数是没有意义的。

它用于访问全局变量,例如数据库对象。

例:

function getCustomer($id) {
  global $db;
  $row = $db->fetchRow('SELECT * FROM customer WHERE id = '.$db->quote($id));
  return $row;
}

它可以用作单例模式的变体

评论

0赞 Nir Alfasi 8/14/2012
“这毫无意义” - 实际上确实如此:一个示例是在不使用 OOP 的情况下实现查找表。
15赞 xzyfer 3/2/2011 #2

简而言之,恕我直言,在现代 PHP 代码中很少有理由,也从来没有一个好的理由。特别是如果你使用的是 PHP 5。如果你正在开发面向对象的代码,那就更特别了。global

全局变量对代码的可维护性、可读性和可测试性产生负面影响。可以并且应该用依赖注入或简单地将全局对象作为参数传递来替换。global

function getCustomer($db, $id) {
    $row = $db->fetchRow('SELECT * FROM customer WHERE id = '.$db->quote($id));
    return $row;
}
36赞 deceze 3/2/2011 #3

反对的一个重要原因是,这意味着该函数依赖于另一个作用域。这很快就会变得一团糟。global

$str1 = 'foo';
$str2 = 'bar';
$str3 = exampleConcat();

与。

$str = exampleConcat('foo', 'bar');

要求 和 在调用范围内设置函数才能正常工作意味着引入不必要的依赖项。如果不在函数中重命名这些变量,则无法再重命名此作用域中的这些变量,因此在使用此函数的所有其他作用域中也是如此。这很快就会陷入混乱,因为您正在尝试跟踪您的变量名称。$str1$str2

global即使包括资源等全球事物,也是一个不好的模式。有一天,您想重命名但无法重命名,因为您的整个应用程序都依赖于该名称。$db$db

限制和分离变量的范围对于编写任何半复杂的应用程序都是必不可少的

评论

1赞 Casey Dwayne 7/22/2014
对不起,为什么我一定要重命名?这是PDO,到处传。当我可以单独更新连接信息时,为什么会更改它?$db
3赞 deceze 7/22/2014
@kcd 因为有一天你意识到依赖注入是多么伟大,并想重构你的应用程序?因为有一天你需要将你的东西与其他一些使用全局变量的东西集成在一起?因为有一天你会发现单元测试,并且需要一次管理多个数据库连接?很多很多原因。$db
1赞 deceze 3/19/2021
@Lukas 函数定义了自己的参数名称,例如 .它按位置接受参数,即传递给的第一个参数将进入其中。调用方的参数名称是什么并不重要:,甚至 ;一切都会像里面一样结束.如果你重命名调用方作用域中的变量,那么你需要在那里重命名它,但这仅限于调用方的作用域,这就是重点。将值作为函数参数传递可以解耦名称。function foo($db) { ... }foo(...)$dbfoo($base)foo($connection)foo(new mysqli(...))$dbfoo
1赞 deceze 3/19/2021
@Lukas 是的,你是对的,如果你重命名一个变量,那么你需要在引用这个变量的代码的每个部分重命名它。诀窍是尽可能地限制变量的范围,这样代码中引用同一变量的地方就不会那么多。它最多在一个函数中,函数应该尽可能小(如果它超过几十行,也许可以将其分解为更小的函数);或者它是一个具有需要重命名属性的类,并且您也尝试使类尽可能小。
1赞 deceze 3/19/2021
@Lukas 对于全球来说,这是一个糟糕的论点。是的,在这个特定示例中,它是 1 对 2 的更改,但这会在整个代码库中快速摊销。
166赞 Gordon 3/2/2011 #4

全局变量是邪恶的

对于关键字以及从局部范围到全局范围的所有其他内容(静态、单例、注册表、常量)都是如此。你不想使用它们。函数调用不应该依赖于外部的任何内容,例如global

function fn()
{
    global $foo;              // never ever use that
    $a = SOME_CONSTANT        // do not use that
    $b = Foo::SOME_CONSTANT;  // do not use that unless self::
    $c = $GLOBALS['foo'];     // incl. any other superglobal ($_GET, …)
    $d = Foo::bar();          // any static call, incl. Singletons and Registries
}

所有这些都会使你的代码依赖于外部。这意味着,您必须知道应用程序所处的完整全局状态,然后才能可靠地调用其中任何一个。没有该环境,该函数就无法存在。

使用超全局变量可能不是一个明显的缺陷,但是如果您从命令行调用代码,则没有 or .如果你的代码依赖于这些输入,那么你就把自己限制在了 Web 环境中。只需将请求抽象到一个对象中,然后改用它即可。$_GET$_POST

在耦合硬编码类名(static、常量)的情况下,如果没有该类可用,您的函数也无法存在。当它是来自同一命名空间的类时,这不是一个问题,但是当你从不同的命名空间开始混合时,你就会造成一团糟。

上述所有因素都严重阻碍了重用。单元测试也是如此

此外,当您耦合到全局范围时,您的函数签名是在撒谎

function fn()

是一个骗子,因为它声称我可以调用该函数而无需向它传递任何东西。只有当我看到函数体时,我才知道我必须将环境设置为某种状态。

如果您的函数需要参数才能运行,请显式执行并传入:

function fn($arg1, $arg2)
{
    // do sth with $arguments
}

从签名中清楚地传达了它需要被调用的内容。它不依赖于处于特定状态的环境。你不必这样做

$arg1 = 'foo';
$arg2 = 'bar';
fn();

这是一个拉入(全局关键字)与推入(参数)的问题。当您推入/注入依赖项时,函数不再依赖外部。当你这样做时,你不必有一个变量在外面的某个地方保持 1。但是,当你在函数中拉入全局时,你会耦合到全局作用域,并期望它在某处定义一个变量。然后,该功能不再独立。fn(1)$one

更糟糕的是,当你在函数中更改全局变量时,你的代码很快就会完全无法理解,因为你的函数到处都是副作用。

在没有更好的例子的情况下,考虑

function fn()
{
    global $foo;
    echo $foo;     // side effect: echo'ing
    $foo = 'bar';  // side effect: changing
}

然后你做到了

$foo = 'foo';
fn(); // prints foo
fn(); // prints bar <-- WTF!!

没有办法从这三条线看到它发生了变化。为什么使用相同参数调用相同的函数会突然更改其输出或更改全局状态中的值?函数应该对定义的输入 Y 执行 X。$foo

当使用 OOP 时,这种情况会变得更加严重,因为 OOP 是关于封装的,并且通过触及全局范围,您正在破坏封装。您在框架中看到的所有这些单例和注册表都是代码气味,应该删除它们以支持依赖注入。解耦代码。

更多资源:

评论

12赞 Pascal Qyy 3/6/2011
为什么PHP实现这样的事情?有实用程序吗?我总是对很多人每次都使用的 PHP 中的危险实现感到惊讶......我很难相信没有合乎逻辑的理由!
7赞 Kermit 4/18/2013
我希望你能把 Globals is evil 做大。
3赞 Wingblade 8/27/2013
哇,终于有人很好地解释了为什么全局是邪恶的......我总是听说他们是,我看到一些非常非常具体的例子来解释为什么,但这确实是对一般原因的一个很好的全面解释。+1
0赞 Mave 10/13/2013
我真的迟到了,我有点明白你在说什么,但是mysqli连接呢?这些应该每次都作为参数传递,还是全局$link;允许在你眼里吗?
4赞 Sebas 2/13/2016
你是对的,除了常量。它们不代表应用程序的“状态”,可以从函数中引用它们。如果函数从内部使用常量,则该函数不会“撒谎”。我同意这意味着程序员在某一时刻对外部有所了解,但对于常数是什么来说,这是一个非常可以接受的权衡。另外,说真的,这没什么大不了的。
40赞 Loek Bergman 2/4/2013 #5

全局变量是不可避免的。

这是一个古老的讨论,但我仍然想补充一些想法,因为我在上面提到的答案中错过了它们。这些答案简化了什么是全球太多,并提出了根本不能解决问题的解决方案。问题是:处理全局变量和关键字 global 的正确方法是什么?为此,我们首先必须检查和描述什么是全局。

看看 Zend 的这段代码 - 请理解,我并不是说 Zend 写得不好:

class DecoratorPluginManager extends AbstractPluginManager
{
/**
 * Default set of decorators
 *
 * @var array
 */
protected $invokableClasses = array(
    'htmlcloud' => 'Zend\Tag\Cloud\Decorator\HtmlCloud',
    'htmltag'   => 'Zend\Tag\Cloud\Decorator\HtmlTag',
    'tag'       => 'Zend\Tag\Cloud\Decorator\HtmlTag',
   );

这里有很多看不见的依赖关系。这些常量实际上是类。 您还可以在此框架的某些页面中看到require_once。Require_once 是全局依赖关系,因此会产生外部依赖关系。对于一个框架来说,这是不可避免的。如何在没有大量外部代码的情况下创建像 DecoratorPluginManager 这样的类?没有很多附加功能,它就无法运行。使用 Zend 框架,您是否曾经更改过接口的实现?接口实际上是一个全局接口。

另一个全球使用的应用程序是Drupal。他们非常关心正确的设计,但就像任何大型框架一样,他们有很多外部依赖。看看这个页面中的全局变量:

/**
 * @file
 * Initiates a browser-based installation of Drupal.
 */

/**
 * Root directory of Drupal installation.
 */
define('DRUPAL_ROOT', getcwd());

/**
 * Global flag to indicate that site is in installation mode.
 */
define('MAINTENANCE_MODE', 'install');

// Exit early if running an incompatible PHP version to avoid fatal errors.
if (version_compare(PHP_VERSION, '5.2.4') < 0) {
  print 'Your PHP installation is too old. Drupal requires at least PHP 5.2.4. See the     <a     href="http://drupal.org/requirements">system requirements</a> page for more     information.';
  exit;
}

// Start the installer.
require_once DRUPAL_ROOT . '/includes/install.core.inc';
install_drupal();

有没有写过重定向到登录页面?这正在改变全球价值。(然后你不是说“WTF”吗,我认为这是对你的应用程序的不良文档的良好反应。全局变量的问题不在于它们是全局变量,而是您需要它们才能拥有有意义的应用程序。问题在于整个应用程序的复杂性,这可能使它成为处理的噩梦。 会话是全局的,$_POST 是全局的,DRUPAL_ROOT 是全局的,includes/install.core.inc' 是不可修改的全局。在任何功能之外都存在着广阔的世界,为了让该功能完成其工作而需要它。

Gordon 的回答是不正确的,因为他高估了函数的独立性,称函数为骗子是过于简单化的情况。函数不会说谎,当你看他的例子时,函数设计不当——他的例子是一个错误。(顺便说一句,我同意这个结论,即应该解耦代码。 欺骗的答案并不是对情况的正确定义。函数总是在更广泛的范围内起作用,而他的例子太简单了。我们都会同意他的观点,即该函数完全无用,因为它返回一个常量。无论如何,这个功能都是糟糕的设计。如果你想证明这种做法是坏的,请提供一个相关的例子。在整个应用程序中重命名变量并没有什么大不了的,因为有一个好的 IDE(或工具)。问题是关于变量的作用域,而不是函数的作用域差异。函数在流程中有一个适当的时间来执行其角色(这就是首先创建它的原因),并且在那个适当的时间,它可能会影响整个应用程序的功能,因此也适用于全局变量。 xzyfer 的答案是一个没有论证的陈述。如果您具有过程函数或 OOP 设计,则全局变量也同样存在于应用程序中。接下来的两种更改全局值的方法本质上是相同的:

function xzy($var){
 global $z;
 $z = $var;
}

function setZ($var){
 $this->z = $var;
}

在这两种情况下,$z的值在特定函数中都发生了变化。在这两种编程方式中,您都可以在代码中的一堆其他地方进行这些更改。你可以说,使用global,你可以打电话给$z任何地方,然后在那里改变。是的, 你可以的。但你会吗?当在不恰当的地方完成时,它不应该被称为错误吗?

Bob Fanger 对 xzyfer 的评论。

那么任何人都应该使用任何东西,尤其是关键字“全球”吗?不,但就像任何类型的设计一样,尝试分析它所依赖的内容以及依赖于它的内容。试着找出它何时变化以及如何变化。更改全局值应该只发生在那些可以随每个请求/响应而变化的变量上。也就是说,仅属于流程功能流的那些变量,而不是其技术实现。将 URL 重定向到登录页面属于进程的功能流,该流程是用于技术实现接口的实现类。您可以在应用程序的不同版本中更改后者,但不应在每个请求/响应中更改它们。

为了进一步理解何时使用全局变量和关键字 global 有问题,何时不是问题,我将介绍下一句话,这句话来自 Wim de Bie 在撰写博客时: “个人是,私人不是”。当一个函数为了它自己的功能而改变全局变量的值时,我将其称为全局变量的私有使用和错误。但是,当全局变量的更改是为了正确处理整个应用程序时,例如将用户重定向到登录页面,那么在我看来,这可能是好的设计,而不是从定义上讲是坏的,当然也不是反模式。

回想起 Gordon、deceze 和 xzyfer 的答案:他们都有“私人是”(和错误)作为例子。这就是为什么他们反对使用全局变量。我也会这样做。然而,他们并没有像我在这个答案中多次做过的那样,带有“个人是,私人不是”的例子。

评论

0赞 Arjan 1/27/2014
Drupal 代码示例不使用全局变量,而是使用常量。一个非常重要的区别是,一个常量一旦被定义,就不能重新定义。此外,您不能只比较函数和 .第一个更改全局状态,第二个是类方法,仅更改调用它的实例的状态。xyzsetZ
0赞 Loek Bergman 1/27/2014
@Arjen:如果你在Drupal 7.14中搜索关键词global,那么你会得到数百次点击。这是公共设置器的一个老问题:一旦你公开了它们,你就无法控制它们被更改的地方。建议不要使用它们或将它们声明为私有,因此以后无法添加。
0赞 Loek Bergman 1/28/2014
@Arjan:由于我拼写错误,你没有收到我给你回复的任何通知。现在你会的。:-)
0赞 Arjan 1/28/2014
@LoekBergman:在drupal 7.26(这是最新版本)中,这个词大约有400次点击,其中一些点击在评论中,而其他一些似乎在多年未被触及的代码中。我当然希望他们不会在 drupal 8 中使用 s。globalglobal
0赞 mAsT3RpEE 2/6/2014
@LoekBergman 请使用 setter 和 getters。设置不需要太多时间,并允许使用您的代码并可能扩展您的类的其他人拥有更多控制权。一旦你把一个参数公开了,就是这样。您以后无法选择隐藏它。
6赞 mAsT3RpEE 2/6/2014 #6

我想每个人都已经阐述了全球的消极方面。因此,我将添加积极因素以及正确使用全局变量的说明:

  1. 全局变量的主要目的是在函数之间共享信息。回到什么时候 没有什么比类更重要了,PHP代码由一堆函数组成。有时 您需要在函数之间共享信息。通常,全局用于 这样做是有风险的,因为数据是全局的。

    现在,在一些快乐的幸运傻瓜开始评论依赖注入之前,我开始了关于依赖注入的评论 想问你,像例子这样的函数的用户怎么会知道 函数的所有依赖项。还要考虑依赖关系可能因版本和服务器而异
    。依赖注入的主要问题 是依赖关系必须事先知道。在无法做到这一点的情况下 或者不需要的全局变量是实现这一目标的唯一方法。
    get_post(1)

    由于创建了类,现在可以很容易地将常用函数分组到一个类中 并共享数据。通过像 Mediators 这样的实现,即使是不相关的对象也可以共享 信息。这不再是必需的。

  2. 全局变量的另一个用途是用于配置目的。主要是在开头 在加载任何自动加载程序、建立数据库连接等之前编写脚本。

    在加载资源期间,全局变量可用于配置数据(即 要使用的数据库、库文件所在的位置、服务器的 URL 等)。最好的 执行此操作的方法是使用函数,因为这些值不会经常更改 并且可以很容易地放在配置文件中。define()

  3. 全局变量的最终用途是保存公共数据(即 CRLF、IMAGE_DIR、IMAGE_DIR_URL)、 人类可读的状态标志(即 ITERATOR_IS_RECURSIVE)。这里全局变量用于存储 旨在在整个应用程序范围内使用的信息,允许它们被更改,以及 让这些更改在应用程序范围内显示。

  4. 单例模式在 php4 期间在 php 中变得流行,当一个对象的每个实例 占用了内存。单例通过只允许一个实例来帮助节省 ram 要创建的对象。在引用之前,即使是依赖注入也是一个糟糕的 想法。

    PHP 5.4+ 中对象的新 php 实现解决了其中的大部分问题 您可以安全地传递物体,而几乎不会再受到任何惩罚。这不再是 必要。

    单例的另一个用途是只有一个对象实例的特殊实例 必须一次存在,该实例可能在脚本执行之前/之后存在,并且 该对象在不同的脚本/服务器/语言等之间共享。这里有一个 单例模式很好地解决了该解决方案。

因此,总而言之,如果您处于位置 1、2 或 3,那么使用全局是合理的。但是,在其他情况下,应使用方法 1。

请随时更新应使用全局变量的任何其他实例。

10赞 unity100 8/23/2014 #7

不要犹豫,不要在 PHP 的函数中使用全局关键字。特别是不要接受那些古怪地宣扬/大喊全球如何“邪恶”之类的人。

首先,因为你使用的东西完全取决于情况和问题,而且在编码中没有一种解决方案/方法可以做任何事情。完全撇开无法定义的、主观的、宗教形容词(如“邪恶”)的谬误。

举个例子:

Wordpress 及其生态系统在其功能中使用全局关键字。是否是代码 OOP,是否是 OOP。

截至目前,Wordpress 基本上占互联网的 18.9%,它运行着从路透社到索尼、纽约时报到 CNN 等无数巨头的大型网站/应用程序。

它做得很好。

在函数中使用全局关键字使 Wordpress 免于大规模膨胀,鉴于其庞大的生态系统,这种情况会发生。想象一下,每个函数都在从另一个插件 core 请求/传递所需的任何变量,然后返回。加上插件的相互依赖关系,最终将导致变量的噩梦,或者作为变量传递的数组的噩梦。一个地狱要跟踪,一个地狱要调试,一个地狱要开发。由于代码膨胀和变量膨胀,也占用了大量内存。也更难写。

可能会有人站出来批评 Wordpress、它的生态系统、他们的做法以及这些部分发生的事情。

毫无意义,因为这个生态系统几乎占整个互联网的 20%。显然,它确实有效,它完成了它的工作,甚至更多。这意味着全局关键字也是如此。

另一个很好的例子是“iframes是邪恶的”原教旨主义。十年前,使用 iframe 是异端邪说。有成千上万的人在互联网上宣扬反对他们。然后是 Facebook,然后是社交,现在 iframe 无处不在,从“喜欢”框到身份验证,瞧——每个人都闭嘴。有些人仍然没有闭嘴——无论对错。但你知道吗,尽管有这样的观点,生活仍在继续,即使是十年前反对 iframe 的人现在也不得不使用它们将各种社交应用程序集成到他们组织自己的应用程序中,而一言不发。

......

编码员原教旨主义是非常非常糟糕的东西。我们当中有一小部分人可能会在一家坚实的单一公司中得到一份舒适的工作,这家公司有足够的影响力来承受信息技术的不断变化及其在竞争、时间、预算和其他方面带来的压力,因此可以实行原教旨主义并严格遵守感知到的“邪恶”或“商品”。舒适的姿势让人想起老年,即使居住者很年轻。

然而,对于大多数人来说,IT世界是一个不断变化的世界,他们需要思想开放和务实。原教旨主义没有立足之地,在信息技术的前线战壕中抛开“邪恶”等令人发指的关键词。

只要使用任何对手头的问题最有意义的东西,并适当考虑近期、中期和长期的未来。不要回避使用任何功能或方法,因为它在任何给定的编码器子集中都对它有猖獗的意识形态敌意。

他们不会做你的工作。你会的。根据您的情况行事。

评论

3赞 Pascal Qyy 10/2/2014
+1 表示反原教旨主义等等,但只是说“很多人使用它/它工作/等”只是一种“民众的论点”,一种基本的诡辩。大多数人认为或做一件事并不能证明他们是对的!在人群中,如果出现危险,大多数人都会做傻事,有些人会被别人踩死。他们把脚踩在这个五岁小女孩的脸上是对的,因为他们认为他们必须绝对推开一扇门,只有当它被拉开才能逃离火灾时才会打开?我不这么认为。。。
1赞 unity100 10/3/2014
当然,大多数人做某事本身并不能验证任何事情。但是,案例是软件。如果大多数人都在这样做,并且这些人创建的大多数应用程序和服务都保持良好(WordPress 对许多其他人来说),那么这意味着它们可以使用。