PHP 日本大会 2024

变量作用域

变量的作用域是指定义它的上下文。PHP 具有函数作用域和全局作用域。任何在函数外部定义的变量都仅限于全局作用域。当包含文件时,它包含的代码将继承包含语句所在行的变量作用域。

示例 #1 全局变量作用域示例

<?php
$a
= 1;
include
'b.inc'; // 变量 $a 将在 b.inc 中可用
?>

在命名函数或匿名函数内部创建的任何变量都仅限于函数体的作用域。但是,箭头函数会绑定来自父作用域的变量,使其在函数体内部可用。如果文件包含发生在调用文件内的函数中,则被调用文件中包含的变量将可用,就像它们在调用函数内定义一样。

示例 #2 局部变量作用域示例

<?php
$a
= 1; // 全局作用域

function test()
{
echo
$a; // 变量 $a 未定义,因为它引用的是 $a 的局部版本
}
?>

上面的示例将生成一个未定义的变量E_WARNING(或在 PHP 8.0.0 之前为E_NOTICE)。这是因为 echo 语句引用的是$a变量的局部版本,并且在此作用域内尚未为其赋值。请注意,这与 C 语言略有不同,因为在 C 语言中,除非局部定义明确地覆盖,否则全局变量会自动对函数可用。这可能会导致一些问题,因为人们可能会无意中更改全局变量。在 PHP 中,如果要在函数中使用全局变量,则必须在函数内部声明为全局变量。

global关键字

global关键字用于将全局作用域中的变量绑定到局部作用域。该关键字可以与变量列表或单个变量一起使用。将创建一个局部变量,引用同名的全局变量。如果全局变量不存在,则将在全局作用域中创建该变量并赋值为null

示例 #3 使用global

<?php
$a
= 1;
$b = 2;

function
Sum()
{
global
$a, $b;

$b = $a + $b;
}

Sum();
echo
$b;
?>

上面的示例将输出

3

通过在函数内声明$a$b为全局变量,所有对这两个变量的引用都将指向全局版本。函数可以操作的全局变量数量没有限制。

访问全局作用域变量的第二种方法是使用 PHP 定义的特殊$GLOBALS数组。前面的示例可以改写为

示例 #4 使用$GLOBALS代替 global

<?php
$a
= 1;
$b = 2;

function
Sum()
{
$GLOBALS['b'] = $GLOBALS['a'] + $GLOBALS['b'];
}

Sum();
echo
$b;
?>

$GLOBALS数组是一个关联数组,全局变量的名称是键,该变量的内容是数组元素的值。请注意$GLOBALS如何在任何作用域中存在,这是因为$GLOBALS是一个超全局变量。以下是一个演示超全局变量功能的示例

示例 #5 演示超全局变量和作用域的示例

<?php
function test_superglobal()
{
echo
$_POST['name'];
}
?>

注意 在函数外部使用global关键字不是错误。如果从函数内部包含文件,则可以使用它。

使用static变量

变量作用域的另一个重要特性是static变量。静态变量仅存在于局部函数作用域中,但在程序执行离开此作用域时不会丢失其值。考虑以下示例

示例 #6 演示需要静态变量的示例

<?php
function test()
{
$a = 0;
echo
$a;
$a++;
}
?>

此函数非常无用,因为每次调用它时,它都会将$a设置为0并打印0$a++递增变量毫无用处,因为一旦函数退出,$a变量就会消失。为了创建一个不会丢失当前计数的有用计数函数,$a变量被声明为静态变量

示例 #7 静态变量的使用示例

<?php
function test()
{
static
$a = 0;
echo
$a;
$a++;
}
?>

现在,$a仅在第一次调用函数时初始化,并且每次调用test()函数时,它都会打印$a的值并递增它。

静态变量还提供了一种处理递归函数的方法。以下简单函数使用静态变量$count递归计数到 10,以知道何时停止

示例 #8 带有递归函数的静态变量

<?php
function test()
{
static
$count = 0;

$count++;
echo
$count;
if (
$count < 10) {
test();
}
$count--;
}
?>

在 PHP 8.3.0 之前,静态变量只能使用常量表达式初始化。从 PHP 8.3.0 开始,也允许使用动态表达式(例如函数调用)

示例 #9 声明静态变量

<?php
function foo(){
static
$int = 0; // 正确
static $int = 1+2; // 正确
static $int = sqrt(121); // 从 PHP 8.3.0 版本开始正确

$int++;
echo
$int;
}
?>

从 PHP 8.1.0 版本开始,当使用静态变量的方法被继承(但未被重写)时,继承的方法现在将与父方法共享静态变量。这意味着方法中的静态变量的行为现在与静态属性相同。

从 PHP 8.3.0 版本开始,静态变量可以使用任意表达式进行初始化。这意味着例如可以使用方法调用来初始化静态变量。

示例 #10 在继承的方法中使用静态变量

<?php
class Foo {
public static function
counter() {
static
$counter = 0;
$counter++;
return
$counter;
}
}
class
Bar extends Foo {}
var_dump(Foo::counter()); // int(1)
var_dump(Foo::counter()); // int(2)
var_dump(Bar::counter()); // int(3), PHP 8.1.0 之前为 int(1)
var_dump(Bar::counter()); // int(4), PHP 8.1.0 之前为 int(2)
?>

使用 globalstatic 变量的引用

PHP 通过引用的方式实现了变量的 staticglobal 修饰符。例如,使用 global 语句在函数作用域内导入的真正的全局变量实际上会创建一个对全局变量的引用。这可能会导致意想不到的行为,以下示例对此进行了说明。

<?php
function test_global_ref() {
global
$obj;
$new = new stdClass;
$obj = &$new;
}

function
test_global_noref() {
global
$obj;
$new = new stdClass;
$obj = $new;
}

test_global_ref();
var_dump($obj);
test_global_noref();
var_dump($obj);
?>

上面的示例将输出

NULL
object(stdClass)#1 (0) {
}

类似的行为也适用于 static 语句。引用不会被静态地存储。

<?php
function &get_instance_ref() {
static
$obj;

echo
'Static object: ';
var_dump($obj);
if (!isset(
$obj)) {
$new = new stdClass;
// 为静态变量赋值引用
$obj = &$new;
}
if (!isset(
$obj->property)) {
$obj->property = 1;
} else {
$obj->property++;
}
return
$obj;
}

function &
get_instance_noref() {
static
$obj;

echo
'Static object: ';
var_dump($obj);
if (!isset(
$obj)) {
$new = new stdClass;
// 将对象赋值给静态变量
$obj = $new;
}
if (!isset(
$obj->property)) {
$obj->property = 1;
} else {
$obj->property++;
}
return
$obj;
}

$obj1 = get_instance_ref();
$still_obj1 = get_instance_ref();
echo
"\n";
$obj2 = get_instance_noref();
$still_obj2 = get_instance_noref();
?>

上面的示例将输出

Static object: NULL
Static object: NULL

Static object: NULL
Static object: object(stdClass)#3 (1) {
  ["property"]=>
  int(1)
}

此示例演示了当为静态变量赋值引用时,当第二次调用 &get_instance_ref() 函数时,它不会被“记住”。

添加注释

用户贡献的注释 5 条注释

dodothedreamer at gmail dot com
13 年前
请注意,与 Java 和 C++ 不同,在循环或 if 语句等代码块内声明的变量也可以在代码块外部识别和访问,因此
<?php
for($j=0; $j<3; $j++)
{
if(
$j == 1)
$a = 4;
}
echo
$a;
?>

将打印 4。
warhog at warhog dot net
18 年前
在类方法中使用静态作用域关键字的一些有趣的行为(使用 PHP5 测试)。

<?php

class sample_class
{
public function
func_having_static_var($x = NULL)
{
static
$var = 0;
if (
$x === NULL)
{ return
$var; }
$var = $x;
}
}

$a = new sample_class();
$b = new sample_class();

echo
$a->func_having_static_var()."\n";
echo
$b->func_having_static_var()."\n";
// 这将输出(预期):
// 0
// 0

$a->func_having_static_var(3);

echo
$a->func_having_static_var()."\n";
echo
$b->func_having_static_var()."\n";
// 这将输出:
// 3
// 3
// 你可能期望:
// 3
// 0

?>

人们可能会期望输出“3 0”,因为你可能认为 `$a->func_having_static_var(3);` 只会改变“在”`$a` 中函数的静态 `$var` 的值——但顾名思义,这些是类方法。拥有一个对象只是一个属性的集合,函数仍然存在于类中。因此,如果在函数内将变量声明为静态的,那么它对于整个类及其所有实例都是静态的,而不是针对每个对象。

也许发布这个没有意义……因为如果你想要我期望的行为,你可以简单地使用对象本身的变量

<?php
class sample_class
{ protected $var = 0;
function
func($x = NULL)
{
$this->var = $x; }
}
?>

我相信所有正常思考的人都不会尝试用静态关键字来实现这一点,对于那些尝试过的人(像我一样),这个注释可能会有帮助。
andrew at planetubh dot com
15 年前
我花了比预期更长的时间才弄明白这一点,我认为其他人可能会觉得它有用。

我创建了一个函数 (safeinclude),用于包含文件;它在实际包含文件之前进行处理(确定完整路径、检查文件是否存在等)。

问题:由于包含操作发生在函数内部,包含文件中的所有变量都继承了函数的变量作用域;由于包含的文件可能需要也可能不需要在其他地方声明的全局变量,因此会产生问题。

大多数地方(包括这里)似乎通过以下方法解决此问题:
<?php
// 在 include 之前声明
global $myVar;
// 或在包含文件中声明
$nowglobal = $GLOBALS['myVar'];
?>

但是,为了在这种情况下使其有效(其中一个标准 PHP 文件包含在从另一个 PHP 脚本调用的函数中;其中访问可能存在的任何全局变量非常重要)……对 'safeinclude' 包含的每个 PHP 文件中的每个变量都采用上述方法并不实际,静态命名“global $this”方法中的每个可能的变量也不实际。(主要是因为代码是模块化的,'safeinclude' 旨在通用)

我的解决方案:因此,为了使我的所有全局变量都可用于我的 safeinclude 函数包含的文件,我必须在我的 safeinclude 函数中添加以下代码(在使用变量或包含文件之前)

<?php
foreach ($GLOBALS as $key => $val) { global $$key; }
?>

因此,完整的代码如下所示(非常基本的模型)

<?php
function safeinclude($filename)
{
// 此行获取所有全局变量,并设置它们在函数中的作用域:
foreach ($GLOBALS as $key => $val) { global $$key; }
/* 预处理:验证文件名输入,确定文件的完整路径,检查文件是否存在等。这显然不是必需的,但我发现这些步骤很有用。 */
if ($exists==true) { include("$file"); }
return
$exists;
}
?>

在上面,'exists' 和 'file' 在预处理中确定。file 是文件的完整服务器路径,如果文件存在,则 exists 设置为 true。当然,这个基本模型可以扩展。在我的模型中,我添加了额外的可选参数,以便我可以调用 safeinclude 来查看文件是否存在,而无需实际包含它(以利用我的路径/等预处理,而不是只调用文件是否存在函数)。

这是一个非常简单的方法,我在网上找不到;我唯一能找到的其他方法是使用 PHP 的 eval()。
gried at NOSPAM dot nsys dot by
8 年前
事实上,所有变量都表示指向分配给该变量数据的内存区域的地址的指针。当您通过引用赋值某个变量值时,实际上是将源变量的地址写入接收变量。当您在函数中声明某个变量为全局变量时,也会发生同样的情况,它会接收与函数外部的全局变量相同的地址。如果您考虑上述解释,很明显,同时混合使用用关键字 global 声明的变量和超全局数组的变量是一个非常糟糕的主意。在某些情况下,它们可能指向不同的内存区域,从而导致头痛。考虑以下代码

<?php

error_reporting
(E_ALL);

$GLOB = 0;

function
test_references() {
global
$GLOB; // 使用关键字 global 获取全局变量的引用,此时局部变量 $GLOB 指向与全局变量 $GLOB 相同的地址
$test = 1; // 声明一些局部变量
$GLOBALS['GLOB'] = &$test; // 使全局变量引用此局部变量,使用超全局数组,此时全局变量 $GLOB 指向新的内存地址,与局部变量 $test 相同

$GLOB = 2; // 通过前面设置的局部表示设置全局变量的新值,写入旧地址

echo "全局变量的值(通过关键字 global 设置的局部表示): $GLOB <hr>";
// 通过局部表示检查全局变量 => 2(OK,获取了刚刚写入的值,因为使用了旧地址来获取值)

echo "全局变量的值(通过超全局数组 GLOBALS): $GLOBALS[GLOB] <hr>";
// 使用超全局数组检查全局变量 => 1(获取局部变量 $test 的值,使用了新地址)

echo "局部变量 \$test 的值: $test <hr>";
// 检查使用超全局数组与全局变量链接的局部变量 => 1(其值未受影响)

global $GLOB; // 使用关键字 global 更新对全局变量的引用,此时我们更新局部变量 $GLOB 中保存的地址,它将获得与局部变量 $test 相同的地址
echo "全局变量的值(通过关键字 global 设置的更新后的局部表示): $GLOB <hr>";
// 通过局部表示检查全局变量 => 1(也是局部变量 $test 的值,使用了新地址)
}

test_references();
echo
"函数外部的全局变量的值: $GLOB <hr>";
// 检查函数外部的全局变量 => 1(等于函数中局部变量 $test 的值,全局变量也指向新地址)
?>
larax at o2 dot pl
18 年前
关于使用全局变量的更复杂的情况..

假设我们有两个文件
a.php
<?php
function a() {
include(
"b.php");
}
a();
?>

b.php
<?php
$b
= "something";
function
b() {
global
$b;
$b = "something new";
}
b();
echo
$b;
?>

您可能会认为此脚本将返回“something new”,但不会,它将返回“something”。为了使其正常工作,您必须在 $b 定义中添加 global 关键字,在上面的示例中,它将是

global $b;
$b = "something";
To Top