参考:什么是变量作用域?哪些变量可以从何处访问?什么是“未定义变量”错误?


167

注意:这是在PHP中处理变量范围的参考问题。请关闭所有适合此模式的问题,以作为该问题的重复。

什么是PHP中的“变量范围”?一个.php文件中的变量可以在另一个文件中访问吗?为什么有时会出现“未定义变量”错误?


1
如果您将其标题为“ Undefined variable”,则您会获得更多的点击量:)很好
戴尔

@戴尔,实际上没有。2年内2k观看次数是....
Pacerier 2015年

7
@Pacerier ...关于正确的时间发表随机评论?
Dale

@Pacerier我确实不确定您要用该评论说什么。“是……。”什么?!:P
deceze

@Dale,现在是正确的时机:尽管问题停滞了2年,但在GoogleDex中添加了“ 无状态 ” 一词后,其点击率在短短6个月内几乎达到了3倍。
Pacerier's

Answers:


188

什么是“可变范围”?

变量具有有限的“作用域”或“可从其访问的位置”。仅仅因为你写$foo = 'bar';一次的地方在你的应用程序并不意味着你可以参照$foo到处其他的应用程序中。该变量$foo在一定范围内有效,并且只有相同范围内的代码才能访问该变量。

如何在PHP中定义范围?

很简单:PHP具有函数作用域。这是PHP中存在的唯一一种范围分隔符。函数内部的变量仅在该函数内部可用。函数外部的变量在函数外部的任何位置可用,但在任何函数内部均不可用。这意味着PHP中有一个特殊的作用域:全局作用域。在任何函数外部声明的任何变量都在此全局范围内。

例:

<?php

$foo = 'bar';

function myFunc() {
    $baz = 42;
}

$foo全局范围内,$baz本地范围内myFunc。只有内部的代码myFunc可以访问$baz。只有外面的代码 myFunc访问$foo。双方都无法访问对方:

<?php

$foo = 'bar';

function myFunc() {
    $baz = 42;

    echo $foo;  // doesn't work
    echo $baz;  // works
}

echo $foo;  // works
echo $baz;  // doesn't work

范围和包含的文件

文件边界 不能分开范围:

a.php

<?php

$foo = 'bar';

b.php

<?php

include 'a.php';

echo $foo;  // works!

相同的规则适用于included代码,适用于任何其他代码:仅function s单独的范围。出于范围的目的,您可能会考虑包括复制和粘贴代码之类的文件:

c.php

<?php

function myFunc() {
    include 'a.php';

    echo $foo;  // works
}

myFunc();

echo $foo;  // doesn't work!

在上面的示例中,a.php被包含在内部myFunc,内部的任何变量a.php仅具有局部函数作用域。仅仅因为它们似乎处于全局范围内a.php并不一定意味着它们存在,而是实际上取决于包含/执行代码的上下文。

函数和类中的函数呢?

每个新的function声明都引入了一个新的作用域,就是这么简单。

函数内部的(匿名)函数

function foo() {
    $foo = 'bar';

    $bar = function () {
        // no access to $foo
        $baz = 'baz';
    };

    // no access to $baz
}

$foo = 'foo';

class Bar {

    public function baz() {
        // no access to $foo
        $baz = 'baz';
    }

}

// no access to $baz

范围有什么用?

处理范围问题似乎很烦人,但是有限的变量范围对于编写复杂的应用程序至关重要!如果您声明的每个变量在应用程序内的其他任何地方都可用,那么您将遍历所有变量,而没有真正的方法来跟踪什么更改。您只能为变量指定一个明智的名称,您可能要使用变量“$name在多个位置 ”。如果您只能在应用程序中使用一次唯一的变量名,则必须采用真正复杂的命名方案,以确保变量是唯一的,并且不会从错误的代码段中更改错误的变量。

观察:

function foo() {
    echo $bar;
}

如果没有范围,上述功能会做什么?哪里$bar来的?它处于什么状态?它甚至被初始化了吗?每次都要检查吗?这是无法维持的。这带我们去...

跨越范围边界

正确的方法:传入和传出变量

function foo($bar) {
    echo $bar;
    return 42;
}

变量$bar作为函数参数显式进入此范围。仅查看此函数,就可以清楚知道其使用值的来源。然后,它显式返回一个值。调用者有信心知道函数将使用哪些变量以及其返回值来自何处:

$baz   = 'baz';
$blarg = foo($baz);

将变量的范围扩展到匿名函数

$foo = 'bar';

$baz = function () use ($foo) {
    echo $foo;
};

$baz();

匿名函数$foo从其周围范围显式包括在内。请注意,这与全局范围不同。

错误的方法: global

如前所述,全局范围有些特殊,函数可以从中显式导入变量:

$foo = 'bar';

function baz() {
    global $foo;
    echo $foo;
    $foo = 'baz';
}

此函数使用并修改全局变量$foo不要这样做! (除非您真的真的真的知道自己在做什么,即使如此:不要!)

该函数的所有调用者看到的是:

baz(); // outputs "bar"
unset($foo);
baz(); // no output, WTF?!
baz(); // outputs "baz", WTF?!?!!

没有迹象表明此功能有任何副作用,但确实如此。由于某些函数不断修改并需要某些全局状态,因此这很容易成为一团糟。您希望函数是无状态的,仅对它们的输入起作用并返回定义的输出,无论您多次调用它们。

您应尽可能避免以任何方式使用全局范围;最肯定的是,您不应该将变量从全局范围“拉”到局部范围。


您只是说错了方法global,所以请告诉我们什么时候应该使用global?并请解释(略)是什么static..?

@stack没有“正确”的方式global。总是错的。传递函数参数是正确的。static在手册中有很好的解释,与范围无关。简而言之,可以将其视为“作用域全局变量”。我在这里kunststube.net/static扩展了它的用法。
deceze

我的简单想法是,如果php变量足够重要以应具有全局状态,则应在数据库中保留一列。也许这是一个矫kill过正的方法,但它是一种愚蠢的方法,适合我平庸的编程技巧
Arthur Tarasov

@Arthur那里有很多东西可以解开……ಠ_ಠ这肯定不是我会认可的方法。
deceze

@deceze今天在这里发生了一点争执stackoverflow.com/q/51409392-OP提到重复项(这里)没有提及include_once,可能require_once还应该添加到某个地方;只是说。OP也投票重开他们的问题。他们的职位会是特例吗,应该怎么办?
Funk Forty Niner,

10

尽管无法从外部访问在函数范围内定义的变量,这并不意味着您无法在函数完成后使用它们的值。PHP有一个众所周知的static关键字,该关键字在面向对象的PHP中广泛用于定义静态方法和属性,但请记住,static也可以在函数内部使用它们来定义静态变量。

什么是“静态变量”?

静态变量不同于在函数范围内定义的普通变量,以防程序执行离开该范围时不释放值。让我们考虑以下使用静态变量的示例:

function countSheep($num) {
 static $counter = 0;
 $counter += $num;
 echo "$counter sheep jumped over fence";
}

countSheep(1);
countSheep(2);
countSheep(3);

结果:

1 sheep jumped over fence
3 sheep jumped over fence
6 sheep jumped over fence

如果$counter没有定义,static则每次回显的值都将与$num传递给函数的参数相同。使用static可以构建此简单计数器,而无需其他解决方法。

静态变量用例

  1. 在随后的函数调用之间存储值。
  2. 在没有办法(或没有目的)将它们作为参数传递时,在两次递归调用之间存储值。
  3. 缓存值,通常最好一次检索一次。例如,读取服务器上不可变文件的结果。

技巧

静态变量仅存在于局部函数范围内。不能在已定义函数的外部访问它。因此,您可以确保在下一次调用该函数之前,其值将保持不变。

静态变量只能定义为标量或标量表达式(自PHP 5.6起)。至少在撰写本文时,为其分配其他值不可避免会导致失败。不过,您只需要在代码的下一行即可执行此操作:

function countSheep($num) {
  static $counter = 0;
  $counter += sqrt($num);//imagine we need to take root of our sheep each time
  echo "$counter sheep jumped over fence";
}

结果:

2 sheep jumped over fence
5 sheep jumped over fence
9 sheep jumped over fence

静态函数是同一类对象的方法之间的“共享”。通过查看以下示例,很容易理解:

class SomeClass {
  public function foo() {
    static $x = 0;
    echo ++$x;
  }
}

$object1 = new SomeClass;
$object2 = new SomeClass;

$object1->foo(); // 1
$object2->foo(); // 2 oops, $object2 uses the same static $x as $object1
$object1->foo(); // 3 now $object1 increments $x
$object2->foo(); // 4 and now his twin brother

这仅适用于相同类的对象。如果对象来自不同的类(甚至彼此扩展),则静态var的行为将符合预期。

静态变量是在函数调用之间保持值的唯一方法吗?

在函数调用之间保留值的另一种方法是使用闭包。在PHP 5.3中引入了闭包。用两个词可以使您将对函数范围内的某些变量集的访问限制为另一个匿名函数,这将是访问它们的唯一方法。处于闭包变量中可能会模仿(或多或少成功)OOP概念,例如结构化编程中的“类常量”(如果它们是通过闭包传递的)或“私有属性”(如果是通过引用传递的)。

后者实际上允许使用闭包代替静态变量。使用什么始终由开发人员决定,但是应该指出的是,在使用递归时,静态变量绝对有用,并且值得开发人员注意。


2

我不会对这个问题给出完整的答案,因为现有的问题和PHP手册在解释大多数问题方面做得很好。

但是,这是漏掉了一个主题是的超全局变量,包括常用的$_POST$_GET$_SESSION等这些变量始终可用,在任何范围内,没有一个数组global声明。

例如,此函数将打印出运行PHP脚本的用户名。该变量对函数可用,没有任何问题。

<?php
function test() {
    echo $_ENV["user"];
}

只要不误用“全局变量是坏的”的一般规则,在PHP中通常将其修改为“全局变量是坏的,但超级全局变量是好的”。(所有这些变量都是可写的,因此如果您确实很糟糕,可以将它们用于避免依赖项注入。)

这些变量不能保证存在。管理员可以通过禁用某些或所有这些variables_order指令php.ini,但是这是不常见的行为。


当前的超全局变量列表:

  • $GLOBALS -当前脚本中的所有全局变量
  • $_SERVER -有关服务器和执行环境的信息
  • $_GET -在网址的查询字符串中传递的值,无论用于请求的HTTP方法如何
  • $_POST-在HTTP POST请求中使用application/x-www-form-urlencodedmultipart/form-dataMIME类型传递的值
  • $_FILES-在HTTP POST请求中以multipart/form-dataMIME类型传递的文件
  • $_COOKIE -Cookie与当前请求一起传递
  • $_SESSION -PHP内部存储的会话变量
  • $_REQUEST-通常是$_GET和的组合$_POST,但有时是$_COOKIES。内容由确定的request_order指令php.ini
  • $_ENV -当前脚本的环境变量
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.