参考:什么是变量范围,哪些变量可以从何处访问,什么是“未定义的变量”错误?

Reference: What is variable scope, which variables are accessible from where and what are "undefined variable" errors?

提问人:deceze 提问时间:6/6/2013 最后编辑:Rahuldeceze 更新时间:6/4/2019 访问量:58529

问:

注意:这是在PHP中处理变量作用域的参考问题。请关闭适合此模式的众多问题中的任何一个,作为此模式的副本。

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

PHP 作用域

评论


答:

204赞 deceze 6/6/2013 #1

什么是“可变范围”?

变量的“范围”或“可访问它们的地方”是有限的。仅仅因为您在应用程序中的某个地方编写过一次,并不意味着您可以从应用程序内的其他任何地方引用。该变量具有特定的有效范围,并且只有同一范围内的代码才能访问该变量。$foo = 'bar';$foo$foo

PHP中如何定义范围?

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

例:

<?php

$foo = 'bar';

function myFunc() {
    $baz = 42;
}

$foo全局范围内,在局部范围内。只有里面的代码才能访问 。只有外部代码才能访问 。任何一方都无法访问另一方:$bazmyFuncmyFunc$bazmyFunc$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!

适用于 d 代码的规则与适用于任何其他代码的规则相同:只有单独的范围。出于范围的目的,您可以考虑包含复制和粘贴代码等文件:includefunction

c.php

<?php

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

    echo $foo;  // works
}

myFunc();

echo $foo;  // doesn't work!

在上面的例子中,被包含在里面,里面的任何变量都只有局部函数作用域。仅仅因为它们似乎在全局范围内并不一定意味着它们是,这实际上取决于包含/执行代码的上下文。a.phpmyFunca.phpa.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?!?!!

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

应尽可能避免以任何方式使用全局范围;当然,您不应该将变量从全局作用域“拉”到局部作用域。

评论

0赞 6/28/2015
你刚才说全局的方式是错误的,所以请告诉我们什么时候应该使用?请解释一下(一点)什么是..?globalstatic
0赞 deceze 6/28/2015
@stack 没有“正确”的方法。这总是错的。传递函数参数是正确的。 在手册中解释得很好,与范围没有太大关系。简而言之,它可以被认为是一个“作用域全局变量”。我在这里 kunststube.net/static 扩展了它的用法。globalstatic
0赞 Arthur Tarasov 8/3/2017
我的简单想法是,如果一个 php 变量足够重要,值得一个全局地位,它就应该在数据库中有一个列。也许这有点矫枉过正,但这是一种万无一失的方法,符合我平庸的编程机智
0赞 deceze 8/3/2017
@Arthur 那里有很多东西要解开......ಠ_ಠ 这肯定不是我赞同的方法。
1赞 deceze 6/30/2021
@Teemu 不,因为这是通过可配置名称显式传递的。不过,它可能会导致奇怪的副作用,并且仅在特定情况下才有意义,例如,您具有“主要”和“次要”返回值。preg_match
12赞 Alex Myznikov 2/17/2017 #2

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

什么是“静态变量”?

静态变量不同于函数作用域中定义的普通变量,以防当程序执行离开此作用域时它不会丢失值。让我们考虑以下使用静态变量的示例:

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

如果我们定义了 with,那么每次回显值将与传递给函数的参数相同。使用允许构建这个简单的计数器,而无需额外的解决方法。$counterstatic$numstatic

静态变量用例

  1. 在对函数的后续调用之间存储值。
  2. 在递归调用之间存储值,当没有办法(或没有 purpose) 将它们作为参数传递。
  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

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

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

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

后者实际上允许使用闭包而不是静态变量。使用什么总是由开发人员决定,但应该提到的是,静态变量在处理递归时绝对有用,值得开发人员注意。

2赞 miken32 5/13/2019 #3

我不会发布这个问题的完整答案,因为现有的答案和 PHP 手册很好地解释了其中的大部分内容。

但有一个主题被遗漏了,那就是超全局变量,包括常用的、、等。这些变量是数组,在任何作用域中始终可用,无需声明。$_POST$_GET$_SESSIONglobal

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

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

在PHP中,“全局变量是坏的”的一般规则通常被修改为“全局变量是坏的,但超全局变量是好的”,只要人们没有滥用它们。(所有这些变量都是可写的,所以如果你真的很糟糕,它们可以用来避免依赖注入。

不保证这些变量存在;管理员可以使用 中的 variables_order 指令禁用其中的部分或全部,但这不是常见的行为。php.ini


当前超全局变量列表:

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