PHP全局函数

时间:2011-03-02 10:25:03

标签: php language-design

global keyword的效用是什么?

有没有理由更喜欢一种方法呢?

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

方法1:

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

方法2:

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

何时使用global

是有意义的

对我来说,appears to be dangerous ......但可能只是缺乏知识。我对记录感兴趣(例如代码示例,文档链接......)技术原因。

提前致谢!


恩惠

这是关于这个主题的一个很好的一般性问题,我(@Gordon)正在提供赏金以获得更多答案。无论您的答案是否与我的答案一致或给出不同的观点都无关紧要。由于global主题不时出现,我们可以使用一个好的“规范”答案来链接。

7 个答案:

答案 0 :(得分:154)

Globals是邪恶的

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环境。只需将请求抽象为对象并使用它即可。

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

以上所有情况严重阻碍了重复使用。 So is unit-testing

此外,当你加入全球范围时,你的功能签名就在撒谎

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是关于封装的,并且通过扩展到全局范围,您正在打破封装。您在框架中看到的所有这些单例和注册表都是代码味道,应该删除它们以支持依赖注入。解密你的代码。

更多资源:

答案 1 :(得分:35)

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

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

VS

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

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

即使包含global资源等全局内容,

$db也是一种糟糕的模式。 会在您想要重命名$db的那一天,但不能,因为您的整个应用程序取决于名称。

限制和分离变量的范围是必要,用于编写任何中途复杂的应用程序。

答案 2 :(得分:34)

全局不可避免。

这是一个古老的讨论,但我仍然想补充一些想法,因为我在上面提到的答案中想念它们。这些答案简化了全球化的过多,并提出了解决问题的解决方案。问题是:处理全局变量和使用关键字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();

有没有写过重定向到登录页面?这正在改变全球价值。 (然后你不是说&#39; WTF&#39;,我认为这是对你的应用程序的错误文档的良好反应。)全局变量的问题不是它们是全局的,你需要它们才能拥有有意义的应用问题是整个应用程序的复杂性,这可能使它成为一个噩梦。 会话是全局的,$ _POST是全局的,DRUPAL_ROOT是全局的,包含/ install.core.inc&#39;是一个不可修改的全球性。为了让这个功能发挥作用,在任何功能之外都有大的世界。

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

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

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

在两个实例中,$ z的值在特定函数内发生了变化。在这两种编程方式中,您可以在代码中的许多其他位置进行更改。你可以说使用全局你可以在任何地方调用$ z并在那里进行更改。是的你可以。但是你呢?如果在inapt地方完成,那么它是否应该被称为bug?

Bob Fanger评论xzyfer。

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

为了进一步了解何时使用全局变量和关键字global是一个问题,什么时候不能解释下一句,来自Wim de Bie写博客的时候: &#39;个人是的,私人没有&#39;。当一个函数为了自己的功能而改变全局变量的值时,我会调用私有变量和bug的私有用法。但是当全局变量的更改是为了整个应用程序的正确处理时,比如用户重定向到登录页面,那么在我看来,这可能是好的设计,而不是定义不好,当然也不是反模式。

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

答案 3 :(得分:14)

简单地说,现代PHP代码恕我直言global很少有理由而且从来都不是一个好理由。特别是如果您使用的是PHP 5.特别是如果您正在开发面向对象的代码。

Globals会对代码的可维护性,可读性和可测试性产生负面影响。 global的许多用法可以而且应该用依赖注入替换,或者只是将全局对象作为参数传递。

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

答案 4 :(得分:8)

在PHP中使用函数内部的全局关键字时不要犹豫。特别是不要把那些异乎寻常地讲道/大吼大叫的全球人如何“邪恶”等等。

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

案例:

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

到目前为止,Wordpress基本上是互联网的18.9%,它运行着无数巨头的巨大的megasites /应用程序,从路透社到索尼,再到纽约,再到CNN。

它做得很好。

在函数内部使用全局关键字可以将Wordpress从MASSIVE膨胀中释放出来,因为它具有庞大的生态系统。想象一下,每个函数都在询问/传递从另一个插件,核心和返回所需的任何变量。添加了插件相互依赖性,最终会成为变量的噩梦,或者是作为变量传递的数组的噩梦。一个地狱跟踪,一个地狱调试,一个地狱发展。由于代码膨胀和可变膨胀,内存占用空间巨大。更难写。

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

毫无意义,因为这个生态系统几乎占整个互联网的20%。显然,它可以工作,它可以完成它的工作。这意味着全局关键字的相同。

另一个很好的例子是“iframes是邪恶的”原教旨主义。十年前,使用iframe是异端邪说。互联网上有成千上万的人在讲道。然后来到Facebook,然后是社交,现在iframe到处都是从“喜欢”的盒子到身份验证,瞧 - 每个人都闭嘴。有些人仍然没有闭嘴 - 正当或错误。但是你知道什么,尽管有这样的意见,生活仍在继续,甚至那些十年前在iframe上宣传的人现在不得不用它们将各种社交应用程序整合到他们组织自己的应用程序中,而不说一句话。

...

编码器原教旨主义非常非常糟糕。我们中的一小部分人可以在一个坚实的单片公司中获得舒适的工作,该公司具有足够的影响力以承受信息技术的不断变化及其在竞争,时间,预算和其他考虑方面带来的压力,因此可以实践原教旨主义和严格遵守“邪恶”或“商品”。即使占领者年轻,也会让人联想起古老的年龄。

然而,对于大多数人而言,i.t。世界是一个不断变化的世界,他们需要开放和务实。没有原教旨主义的地方,在信息技术的前线战壕中留下诸如“邪恶”等令人愤慨的关键词。

只需使用对AT HAND问题最有意义的事物,并考虑近期,中期和长期的未来。不要回避使用任何特征或方法,因为它在任何给定的编码器子集中具有猖獗的意识形态仇恨。

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

答案 5 :(得分: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实现解决了大多数这些问题 你可以安全地传递物体,几乎没有惩罚。这不再是 必要的。

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

  5. 总而言之,如果你处于第1,2或3位,那么使用全局将是合理的。但是在其他情况下应该使用方法1。

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

答案 6 :(得分:5)

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

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

示例:

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

它可以用作Singleton pattern

的变体