PHP全局函数


100

什么是效用全球关键字

是否有任何理由偏爱一种方法而不是另一种方法?

  • 安全?
  • 性能?
  • 还要别的吗?

方法1:

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

方法2:

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

什么时候使用有意义global

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

提前致谢!


赏金

这是关于该主题的一个很好的一般性问题,我(@Gordon)提供了赏金以获取其他答案。您的答案是否与我的观点一致或给出不同的观点都无关紧要。由于该global主题时不时出现,因此我们可以使用一个很好的“规范”答案进行链接。


2
看一下这个链接:stackoverflow.com/questions/1557787 此页面右下角有很多相关文章
JohnP 2011年

这不是您问题的直接答案,但请阅读此较早的SO问题
奥拉维尔Waage

1
因此,我看不到任何亲全局关键字。1)为什么在这里。2)为什么人们使用它?
Pascal Qyy 2011年

@ G.Qyy为什么会出现goto?人们为什么使用它?他们不使用它(至少希望如此):P
PeeHaa'1

去年年底(12月14日),有人否决了这个问题。我非常想知道为什么,因为所有观点,包括负面观点,都很有趣。在这种情况下比以往任何时候都更多!我会很感激关于它的任何线索。
Pascal Qyy 2015年

Answers:


158

全球人是邪恶的

对于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
}

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

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

在耦合硬编码的类名(静态,常量)的情况下,如果没有可用的类,您的函数也将不存在。当它来自相同名称空间的类时,这没什么问题,但是当您从不同名称空间开始混合时,您正在创建混乱的混乱。

以上所有因素严重限制了重用。单元测试也是如此

另外,当您耦合到全局范围时,您的函数签名就在说谎

function fn()

是骗子,因为它声称我可以在不传递任何函数的情况下调用该函数。只有当我看到函数主体时,我才需要将环境设置为特定状态。

如果您的函数需要运行参数,请将其显式并将其传递给:

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

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

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

这是引入(全局关键字)与引入(参数)的问题。推入/注入依赖项时,该函数不再依赖外部。当您执行此操作时,您不必fn(1)在外部的某个地方拥有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!!

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

当使用OOP时,这变得更加严重,因为OOP与封装有关,并且通过扩展到全局范围,您正在破坏封装。您在框架中看到的所有这些Singletons和Registries是代码气味,应删除它们以利于依赖注入。解耦您的代码。

更多资源:


10
为什么PHP会实现这样的事情?有实用程序吗?我总是对很多人每次都用PHP进行危险的实现感到惊讶……我很难相信没有逻辑上的原因!
Pascal Qyy 2011年

5
我希望您可以使Globals变得更大。
柯米特(Kermit)2013年

3
哇,终于有人很好地解释了为什么全球人是邪恶的……我一直听说他们是邪恶的,而且我看到了一些非常具体的例子来说明为什么,但这确实是一个很好且全面的解释,说明了为什么。+1
Wingblade

我真的很迟,我有点理解您的意思,但是mysqli连接如何?应该每次将它们作为参数传递,还是全局$ link;在你眼里
Mave

2
你是对的,除了常量。它们不代表应用程序的“状态”,可以从函数中引用它们。如果函数从内部使用常量,则该函数不会“说谎”。我同意这意味着程序员在某一时刻对外部有所了解,但这对于常量是一个非常可以接受的折衷。另外,认真地说,这没什么大不了的。
塞巴斯

35

反对的一个重要原因global是它意味着功能依赖于另一个范围。这将很快变得混乱。

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

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

要在函数的调用范围中进行要求$str1$str2进行设置,就意味着您引入了不必要的依赖关系。您不能再在此范围内重命名这些变量,而不能在函数中重命名它们,因此也不能在使用此函数的所有其他范围内重命名。当您试图跟踪变量名时,这很快就会陷入混乱。

global即使包括诸如$db资源之类的全局事物,这也是一个糟糕的模式。有来的一天,当你想重命名$db,但不能,因为你的整个应用程序依赖于名字。

限制和分离变量的范围对于编写任何中途复杂的应用程序至关重要


1
抱歉,为什么我一定要重命名$db?是PDO,无处不在。当我可以单独更新连接信息时,为什么要更改它?
Casey Dwayne 2014年

3
@kcd因为有一天您意识到依赖注入的重要性,并希望重组您的应用程序?因为有一天,您需要将您的内容与其他使用全局$db变量的内容进行集成?因为有一天您会发现单元测试,并且需要一次管理多个数据库连接?很多很多原因。
deceze

35

全球人是不可避免的。

这是一个古老的讨论,但是我仍然想补充一些想法,因为我在上述答案中想念它们。这些答案简化了整体问题,提供了根本无法解决问题的解决方案。问题是:处理全局变量和使用关键字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'是不可修改的全局变量。为了使该功能发挥作用,任何功能之外都存在广阔的世界。

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

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

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

在这两种情况下,$ z的值都在特定函数内改变。可以通过两种编程方式在代码的其他许多地方进行这些更改。您可以说使用global可以在任何地方调用$ z并在那里进行更改。是的你可以。但是你会吗?当在不适当的地方进行操作时,不应该将其称为错误吗?

Bob Fanger在xzyfer上发表了评论。

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

为了进一步了解使用globals和关键字global何时会出现问题,何时不使用,我将介绍下一个句子,该句子是Wim de Bie在撰写博客时写的:“个人是,私人否”。当一个函数由于其自身的功能而更改全局变量的值时,我将这种私有使用称为全局变量和错误。但是,如果要对全局变量进行更改以对整个应用程序进行适当处理,例如将用户重定向到登录页面,那么在我看来,这可能是好的设计,从定义上来说不是坏的,而且肯定不是反模式。

回顾戈登的答案,deceze和xzyfer:他们都以“ private yes”(和错误)为例。这就是为什么他们反对使用全局变量的原因。我也会的 但是,它们不带有“个人是,私人否”的示例,就像我在此答案中多次提到的那样。


Drupal代码示例不使用全局变量,而是使用常量。一个非常重要的区别是常量一旦定义就无法重新定义。另外,您不能只比较函数xyzsetZ。第一个更改全局状态,第二个更改类方法,并且仅更改其调用实例的状态。
2014年

@Arjen:如果您在Drupal 7.14中搜索关键字global,那么您将获得数百次点击。对于公共设置者来说,这是一个老问题:将它们公开后,您就无法控制更改位置。建议不要使用它们或将其声明为私有,因此以后不能添加。
Loek Bergman 2014年

@Arjan:由于我的名字拼写错误,您没有收到我对您的回复的任何通知。现在你会的。:-)
Loek Bergman 2014年

@LoekBergman:global在drupal 7.26(最新版本)中,该单词大约有400个匹配项,其中一些匹配项是在注释中,而另一些则似乎是多年未使用的代码。我肯定希望他们不会global在drupal 8中使用s。–
Arjan

@LoekBergman请使用setter和getter。设置不需要花费很多时间,并允许其他使用您的代码并可能扩展您的类的人员获得更多控制权。公开参数后,就可以了。您无法选择以后再隐藏它。
mAsT3RpEE 2014年

15

简而言之global,现代PHP代码IMHO中很少有理由,而从来没有一个好的理由。尤其是在使用PHP 5的情况下。在开发面向对象的代码时尤其如此。

全局变量会对代码的可维护性,可读性和可测试性产生负面影响。globalcan的许多用法都应该被依赖注入或简单地将全局对象作为参数传递。

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

10

不要犹豫,不要在PHP函数中使用global关键字。尤其不要带那些过分鼓吹/大喊大叫的全球人如何“邪恶”之类的人。

首先,因为您所使用的完全取决于情况和问题,并且没有一种解决方案/方式可以在编码中做任何事情。完全排除了诸如“邪恶”之类的无法定义,主观,宗教形容词的谬误。

例子:

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

截至目前,Wordpress基本上占互联网的18.9%,其运行着庞大的大型网站/应用程序,从路透社到索尼,再到NYT到CNN,无数巨头。

它做得很好。

在函数内部使用全局关键字使Wordpress摆脱了巨大的麻烦,鉴于其庞大的生态系统,这种情况会发生。想象每个函数都在询问/传递另一个插件,核心和返回所需要的任何变量。加上插件的相互依赖关系,最终将导致变量的噩梦,或作为变量传递的数组的噩梦。要跟踪的地狱,要调试的地狱,要发展的地狱。由于代码过大和变量过大,也造成了巨大的内存占用。也很难写。

可能有人会批评Wordpress,其生态系统,他们的做法以及这些部分中发生的事情。

毫无意义,因为这个生态系统几乎占整个互联网的20%。显然,它确实可以工作,可以完成工作以及更多。这与global关键字的含义相同。

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

......

编码原教旨主义是非常非常糟糕的事情。在我们当中,只有一小部分人可能会在一家坚实的整体公司中担任舒适的工作,该公司具有足够的影响力,可以承受信息技术的不断变化以及它在竞争,时间,预算和其他方面所带来的压力,因此可以实践原教旨主义和严格遵守感知到的“邪恶”或“商品”。这些舒适的位置让人想起老年人,即使乘员还很年轻。

但是对于大多数人来说,IT世界是一个不断变化的世界,在这些世界中,他们需要开放思维和务实。根本主义无处可去,在信息技术的前线不留诸如“邪恶”之类的离谱关键词。

只需使用最能解决问题的方法,并适当考虑近期,中期和长期的未来。不要回避使用任何功能或方法,因为在任何给定的编码器子集中,它在思想上都存在着强烈的敌意。

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


3
+1代表反原教旨主义等等,但是仅仅说“很多人使用它/它起作用/等等”只是一种“自发的争论”,这是一种基本的诡辩。大多数人认为或做一件事的事实并不能证明他们是对的!在人群中,如果出现危险,大多数人都会做愚蠢的事情,而有些人会被其他人踩死。他们只是因为认为必须将门拉开才能逃脱火灾而绝对推开那扇门,才将脚踩在这个五岁小女孩的脸上吗?我不这么认为…
Pascal Qyy 2014年

1
大多数人当然做某事本身并不能验证任何事情。但是,情况是软件。如果大多数人都这样做,并且这些人创建的大多数应用程序和服务运行良好(对许多其他人来说都是WordPress),则意味着它们可以被使用。
unity100 2014年

7

我认为每个人都已经就全球化的负面方面做了很多阐述。因此,我将添加肯定内容以及正确使用全局变量的说明:

  1. 全局变量的主要目的是在职能之间共享信息。回到当没有什么像一个类时,php代码由一堆函数组成。有时您需要在功能之间共享信息。通常情况下,使用全局变量可以使数据具有全局性,从而可能会破坏数据。

    现在,在一些快乐的简单人开始对依赖注入进行评论之前,我想问一下像示例get_post(1)这样的函数的用户如何知道该函数的所有依赖关系。还应考虑
    到,版本之间以及版本与服务器之间的依赖关系可能会有所不同。依赖项注入的主要问题是必须事先知道依赖项。在不可能做到这一点或不需要的全局变量是实现此目标的唯一方法的情况下。

    由于创建了类,因此现在可以将常见功能轻松地分组到一个类中并共享数据。通过像Meditor这样的实现,甚至不相关的对象也可以共享信息。这不再是必需的。

  2. 全局变量的另一个用途是用于配置目的。通常在脚本开始之前,在加载任何自动加载器,建立数据库连接等之前。

    在加载资源期间,可以使用全局变量来配置数据(即,要使用哪个数据库来存储库文件所在的位置,服务器的url等)。最好的方法是使用define()函数,因为这些值不会经常更改,并且可以轻松地放置在配置文件中。

  3. 全局变量的最终用途是保存公用数据(即CRLF,IMAGE_DIR,IMAGE_DIR_URL),人类可读的状态标志(即ITERATOR_IS_RECURSIVE)。在这里,全局变量用于存储打算在整个应用程序范围内使用的信息,从而允许对其进行更改并使这些更改在整个应用程序范围内出现。

  4. 当对象的每个实例占用内存时,单例模式在php4期间在php中变得很流行。单例仅允许创建一个对象实例来帮助节省内存。在引用之前,甚至依赖注入也将是一个坏主意。

    来自PHP 5.4+的对象的新php实现解决了这些问题中的大多数,您可以安全地传递对象,而几乎不需要付出任何代价。这不再是必需的。

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

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

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


6

使用global关键字创建concat函数没有任何意义。

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

例:

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

可以用作Singleton模式的变体


“没有意义”-实际上确实如此:一个示例将在不使用OOP的情况下实现查找表。
尼尔·阿尔法西
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.