变量的作用域是定义该变量的上下文。PHP 有函数作用域和全局作用域。在函数之外定义的任何变量都仅限于全局作用域。当包含文件时,该文件中的代码继承了包含语句所在行的变量作用域。
示例 #1 全局变量作用域示例
<?php
$a = 1;
include 'b.inc'; // 变量 $a 将在 b.inc 内可用
?>
在命名函数或匿名函数内创建的任何变量都仅限于函数主体的作用域。然而,箭头函数会绑定父级作用域中的变量,使其在函数体内可用。如果在函数内部 include 文件,那么包含文件中的变量将如同在调用函数内部定义一样可用。
示例 #2 局部变量作用域的示例
<?php
$a = 1; // 全局作用域
function test()
{
echo $a; // $a 变量 $a 未定义,因为它引用了 $a 的局部版本
}
?>
下面的示例会生成未定义变量 E_WARNING
(PHP 8.0.0 之前是 E_NOTICE
)诊断提示。这是因为
echo 语句引用了局部版本的变量 $a,而且在这个作用域内,它并没有被赋值。注意这与
C 语言有一点点不同,因为 C 中的全局变量会自动提供给函数,除非被局部定义特别覆盖。这可能引起一些问题,有些人可能不小心就改变了一个全局变量。PHP
中全局变量在函数中使用时必须声明为 global。
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 没有限制。
在全局作用域内访问变量的第二个办法,是用特殊的 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
关键字不是错误。如果文件是从函数内部 include 的,则可以使用它。
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() 函数时被初始化,之后每次调用 test() 函数都会输出 $a 的值并加一。
静态变量也提供了一种处理递归函数的方法。以下这个简单的函数递归计数到 10,使用静态变量 $count 来判断何时停止:
示例 #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;
}
?>
匿名函数内的静态变量仅在该特定函数实例中存在。如果每次调用时都重新创建匿名函数,则静态变量将重新初始化。
示例 #10 匿名函数内的静态变量
<?php
function exampleFunction($input) {
$result = (static function () use ($input) {
static $counter = 0;
$counter++;
return "Input: $input, Counter: $counter\n";
});
return $result();
}
// 调用 exampleFunction 将重新创建匿名函数,因此静态
// 变量不会保留其值。
echo exampleFunction('A'); // 输出:Input: A, Counter: 1
echo exampleFunction('B'); // 输出:Input: B, Counter: 1
?>
从 PHP 8.1.0 开始,当继承(不是覆盖)使用有静态变量的方法时,继承的方法将会跟父级方法共享静态变量。这意味着方法中的静态变量现在跟静态属性有相同的行为。
自 PHP 8.3.0 起,可以使用任意表达式初始化静态变量。这意味着也说,像是方法调用可用于初始化静态变量。
示例 #11 在继承方法中使用静态变量
<?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)
?>
global
和 static
变量的引用
对于变量的
static 和
global
定义是以引用的方式实现的。例如,在一个函数作用域内部用
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()
函数时不会记住该引用。