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

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

PHP 中的“变量作用域”是什么?是一个变量的变量。另一个可访问的 php 文件?为什么我有时会得到 “未定义变量”错误?

56768 次浏览

什么是“变量范围”?

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

PHP 中如何定义范围?

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

例如:

<?php


$foo = 'bar';


function myFunc() {
$baz = 42;
}

$foo全球性的范围内,$bazmyFunc内的 本地范围内。只有 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的独立作用域。出于作用域的考虑,您可以考虑包含像复制和粘贴代码这样的文件:

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

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

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

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

如果我们定义了没有 static$counter,那么每次回显的值将与传递给函数的 $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

这只适用于同一类的对象。如果对象来自不同的类(甚至相互扩展) ,那么静态 vars 的行为将如预期的那样。

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

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

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

我不会张贴一个完整的答案的问题,因为现有的和 PHP 手册做了很好的工作来解释大部分。

但遗漏了 超全球,包括常用的 $_POST$_GET$_SESSION等。这些变量是数组,它们在任何范围内都是可用的,没有 global声明。

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

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

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

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


当前超级全球化的列表:

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