174

注意:这是一个在 PHP 中处理变量范围的参考问题。请关闭符合此模式的许多问题中的任何一个作为此问题的副本。

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

4

3 回答 3

196

什么是“可变范围”?

变量具有有限的“范围”,或“可以访问它们的位置”。仅仅因为您在应用程序的某个地方编写$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

范围和包含的文件

文件边界不分隔范围:

一个.php

<?php

$foo = 'bar';

b.php

<?php

include 'a.php';

echo $foo;  // works!

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

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?!?!!

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

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

于 2013-06-06T10:20:35.760 回答
12

尽管无法从外部访问在函数范围内定义的变量,但这并不意味着在该函数完成后您不能使用它们的值。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

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

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

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

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

于 2017-02-16T21:06:08.787 回答
2

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

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

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

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

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

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


当前超全局变量列表:

  • $GLOBALS- 当前脚本中的所有全局变量
  • $_SERVER- 关于服务器和执行环境的信息
  • $_GET- 在 URL 的查询字符串中传递的值,与用于请求的 HTTP 方法无关
  • $_POST- 在 HTTP POST 请求中传递的值具有application/x-www-form-urlencodedmultipart/form-dataMIME 类型
  • $_FILESmultipart/form-data- 以MIME 类型在 HTTP POST 请求中传递的文件
  • $_COOKIE- 与当前请求一起传递的 Cookie
  • $_SESSION- PHP 内部存储的会话变量
  • $_REQUEST- 通常是 and 的组合$_GET$_POST但有时是$_COOKIES。内容由 中的指令request_order确定php.ini
  • $_ENV- 当前脚本的环境变量
于 2019-05-13T14:18:16.400 回答