变量作用域
变量的作用域是指变量可以被访问的代码区域。PHP 有两种主要作用域:全局作用域和函数作用域。理解作用域规则是避免变量访问错误、编写正确程序的关键。
前置知识
- 已掌握 变量基础 的命名和赋值
- 了解 PHP 函数的基本概念
- 熟悉
global和static关键字的基本用途
基础概念
PHP 中的作用域规则相对简单:在函数之外定义的变量属于全局作用域,在函数内部定义的变量属于局部作用域。PHP 没有块级作用域(与 Java、C++ 等语言不同),在 if、for、while 等块内定义的变量在块外依然可见。
PHP 的作用域类型
| 作用域类型 | 说明 | 关键字 |
|---|---|---|
| 全局作用域 | 函数外定义的变量 | - |
| 局部作用域 | 函数内定义的变量 | - |
| 静态作用域 | 函数内声明为 static 的变量 | static |
| 超级全局作用域 | 任何位置都可访问的预定义变量 | $_GET, $_POST 等 |
全局作用域与局部作用域
全局变量
在函数之外定义的变量默认仅存在于全局作用域中。函数内部无法直接访问全局变量:
<?php
declare(strict_types=1);
$a = 1; // 全局作用域
function test(): void
{
// 访问全局变量 $a 会触发未定义变量警告
// PHP 8.0+:E_WARNING;PHP 8.0 前:E_NOTICE
// echo $a;
echo "In function: a is not accessible\n";
}
test();
echo $a . "\n"; // 1(全局作用域内正常访问)局部变量
在函数内部创建的变量仅在该函数执行期间存在,函数执行完毕后变量即被销毁:
<?php
declare(strict_types=1);
function createLocalVariable(): void
{
$localVar = "I am local";
echo "Inside function: {$localVar}\n";
}
createLocalVariable();
// echo $localVar; // Error: Undefined variable没有块级作用域
与 C/Java 的区别
PHP 没有块级作用域。if、for、while 等语句块内定义的变量在块外依然可用。这与其他语言(如 Java、C++、JavaScript 的 let)不同。
<?php
declare(strict_types=1);
// PHP 没有块级作用域
for ($i = 0; $i < 3; $i++) {
$loopVar = "iteration {$i}";
}
echo $loopVar . "\n"; // iteration 2(块外仍然可用)
if (true) {
$blockVar = "from if block";
}
echo $blockVar . "\n"; // from if block(块外仍然可用)global 关键字
使用 global 声明全局变量
在函数内部使用 global 关键字可以访问全局变量。global 会创建一个指向同名全局变量的引用:
<?php
declare(strict_types=1);
$a = 1;
$b = 2;
function sum(): void
{
global $a, $b;
$b = $a + $b;
}
sum();
echo $b . "\n"; // 3(全局变量 $b 被函数修改)global 创建引用
global 关键字的底层实现是创建一个引用。如果在函数内使用 global 导入了变量后又重新赋值引用,需要注意行为差异:
<?php
declare(strict_types=1);
// global 的引用行为示例
function testGlobalRef(): void
{
global $obj;
$new = new stdClass();
$obj = &$new; // 注意:这不会修改全局 $obj
}
function testGlobalNoRef(): void
{
global $obj;
$new = new stdClass();
$obj = $new; // 正确赋值
}
$obj = null;
testGlobalRef();
var_dump($obj); // NULL(引用赋值未生效)
testGlobalNoRef();
var_dump($obj); // object(stdClass)谨慎使用 global
过度使用 global 会降低代码的可维护性和可测试性。推荐通过参数传递和返回值来实现函数间的数据交换,而不是依赖全局变量。
$GLOBALS 超级全局变量
使用 $GLOBALS 替代 global
$GLOBALS 是一个关联数组,键名为变量名(不含 $),值为变量的内容。它是一个超级全局变量,在任何作用域中都可访问:
<?php
declare(strict_types=1);
$a = 1;
$b = 2;
function sumUsingGlobals(): void
{
$GLOBALS["b"] = $GLOBALS["a"] + $GLOBALS["b"];
}
sumUsingGlobals();
echo $b . "\n"; // 3PHP 8.1+ 的变化
从 PHP 8.1.0 起,$GLOBALS 变为只读。不再支持通过 $GLOBALS 来写入全局变量:
<?php
declare(strict_types=1);
// PHP 8.1+ 中以下代码会报错
// $GLOBALS["newVar"] = "value"; // Fatal errorstatic 静态变量
基本用法
静态变量仅在函数作用域内存在,但当函数执行结束后其值不会丢失。下次调用该函数时,静态变量保留上次的值:
<?php
declare(strict_types=1);
// 不使用 static:每次调用都重置为 0
function counterWithoutStatic(): int
{
$count = 0;
$count++;
return $count;
}
echo counterWithoutStatic() . "\n"; // 1
echo counterWithoutStatic() . "\n"; // 1(总是 1)
// 使用 static:保持上次值
function counterWithStatic(): int
{
static $count = 0;
$count++;
return $count;
}
echo counterWithStatic() . "\n"; // 1
echo counterWithStatic() . "\n"; // 2
echo counterWithStatic() . "\n"; // 3静态变量与递归
静态变量常用于递归函数中控制终止条件:
<?php
declare(strict_types=1);
function recursiveCount(int $max, int $current = 1): void
{
static $depth = 0;
$depth++;
echo str_repeat(" ", $depth - 1) . "Level {$depth}\n";
if ($current < $max) {
recursiveCount($max, $current + 1);
}
$depth--;
}
recursiveCount(4);静态变量的初始化表达式(PHP 8.3+)
版本说明
PHP 8.3.0 之前,静态变量只能使用常量表达式初始化。从 PHP 8.3.0 起,允许使用动态表达式(如函数调用)初始化静态变量。
<?php
declare(strict_types=1);
function getConfigValue(): int
{
static $timeout = sqrt(121); // PHP 8.3+ 允许
static $count = 0; // 所有版本允许
static $sum = 1 + 2; // 所有版本允许(常量表达式)
$timeout++;
return $timeout;
}
echo getConfigValue() . "\n"; // 12(sqrt(121) = 11, 然后 +1)继承方法中的静态变量(PHP 8.1+)
从 PHP 8.1.0 起,继承(而非覆盖)的方法中的静态变量在父类和子类之间共享:
<?php
declare(strict_types=1);
class Foo
{
public static function counter(): int
{
static $count = 0;
$count++;
return $count;
}
}
class Bar extends Foo
{
// 继承了 counter() 方法
}
echo Foo::counter() . "\n"; // 1
echo Foo::counter() . "\n"; // 2
echo Bar::counter() . "\n"; // 3(PHP 8.1+ 共享静态变量)
echo Bar::counter() . "\n"; // 4
// PHP 8.1 之前:Bar::counter() 返回 1 和 2(各自独立)详细说明
超级全局变量
超级全局变量(superglobals)在代码的任何位置都可访问,无需 global 声明:
| 变量 | 说明 |
|---|---|
$GLOBALS | 所有全局变量的引用 |
$_SERVER | 服务器和执行环境信息 |
$_GET | HTTP GET 请求参数 |
$_POST | HTTP POST 请求参数 |
$_FILES | HTTP 文件上传变量 |
$_COOKIE | HTTP Cookie 数据 |
$_SESSION | 会话变量 |
$_REQUEST | $_GET + $_POST + $_COOKIE |
$_ENV | 环境变量 |
<?php
declare(strict_types=1);
// 超级全局变量在任何作用域内都可直接访问
function showServerInfo(): void
{
echo "Server: " . ($_SERVER["SERVER_NAME"] ?? "CLI") . "\n";
echo "Request URI: " . ($_SERVER["REQUEST_URI"] ?? "N/A") . "\n";
}
showServerInfo();include 文件中的作用域
当 include 一个文件时,被包含文件继承包含语句所在位置的变量作用域:
<?php
declare(strict_types=1);
// main.php
$appName = "MyApp";
$appVersion = "2.0";
// $appName 和 $appVersion 会传递到 included.php
include __DIR__ . "/included.php";如果 include 在函数内部执行,被包含文件只继承函数作用域内的变量:
<?php
declare(strict_types=1);
function renderTemplate(string $file, string $title): void
{
// $file 和 $title 在函数作用域内
// 被包含文件只能访问 $file 和 $title
include $file;
}匿名函数与箭头函数的作用域
普通匿名函数(闭包)需要使用 use 关键字从父作用域继承变量。而箭头函数(PHP 7.4+)自动捕获父作用域变量:
<?php
declare(strict_types=1);
$multiplier = 3;
// 匿名函数:需要 use 关键字
$double = function (int $x) use ($multiplier): int {
return $x * $multiplier;
};
// 箭头函数:自动捕获父作用域变量
$triple = fn(int $x): int => $x * $multiplier;
echo $double(5) . "\n"; // 15
echo $triple(5) . "\n"; // 15实战示例
使用依赖注入替代 global
<?php
declare(strict_types=1);
// 不推荐:使用 global
$config = ["debug" => true];
function getConfigBad(): array
{
global $config;
return $config;
}
// 推荐:通过参数传递
function getConfigGood(array $config): array
{
return $config;
}
// 更推荐:使用类和依赖注入
class AppConfiguration
{
public function __construct(
private readonly array $settings = []
) {
}
public function get(string $key, mixed $default = null): mixed
{
return $this->settings[$key] ?? $default;
}
}
$appConfig = new AppConfiguration(["debug" => true, "cache_ttl" => 3600]);
echo $appConfig->get("debug") ? "true" : "false"; // true静态变量实现单例计数器
<?php
declare(strict_types=1);
class RequestCounter
{
private static ?int $requestCount = null;
public static function increment(): int
{
if (self::$requestCount === null) {
self::$requestCount = 0;
}
self::$requestCount++;
return self::$requestCount;
}
public static function getCount(): int
{
return self::$requestCount ?? 0;
}
public static function reset(): void
{
self::$requestCount = null;
}
}
echo RequestCounter::increment() . "\n"; // 1
echo RequestCounter::increment() . "\n"; // 2
echo RequestCounter::increment() . "\n"; // 3
echo RequestCounter::getCount() . "\n"; // 3闭包捕获变量的注意事项
<?php
declare(strict_types=1);
// use 默认按值捕获
$counter = 0;
$increment = function () use ($counter): int {
return ++$counter;
};
echo $increment() . "\n"; // 1
echo $increment() . "\n"; // 1(counter 是副本)
// use 按引用捕获
$counter = 0;
$incrementRef = function () use (&$counter): int {
return ++$counter;
};
echo $incrementRef() . "\n"; // 1
echo $incrementRef() . "\n"; // 2(共享同一个 $counter)注意事项
1. 避免滥用 global
global 关键字会引入隐式依赖,降低代码的可测试性和可维护性。
2. 注意 $GLOBALS 的只读限制
PHP 8.1+ 中 $GLOBALS 不可写,旧代码需要迁移。
3. 静态变量初始化只执行一次
<?php
declare(strict_types=1);
function testStaticInit(): void
{
static $val = rand(1, 100);
echo $val . "\n";
}
testStaticInit(); // 某个随机数
testStaticInit(); // 同一个随机数(初始化只执行一次)4. 匿名函数中的静态变量
如果匿名函数每次被重新创建,其内部静态变量也会重新初始化:
<?php
declare(strict_types=1);
function createCounter(): callable
{
return function (): int {
static $count = 0;
return ++$count;
};
}
$counter1 = createCounter();
$counter2 = createCounter();
echo $counter1() . "\n"; // 1
echo $counter1() . "\n"; // 2
echo $counter2() . "\n"; // 1(独立的静态变量)
echo $counter2() . "\n"; // 2最佳实践
- 通过参数传递替代 global:避免使用全局变量,通过参数和返回值传递数据
- 使用类属性替代全局状态:将全局状态封装在类的静态属性中
- 优先使用箭头函数:简单闭包使用
fn,自动捕获父作用域 - 静态变量用于特定场景:如计数器、递归深度跟踪等
- 使用类型声明:函数参数和返回值使用严格类型
- 初始化所有变量:在使用前显式赋初值
- 使用
readonly属性(PHP 8.1+):防止意外修改类属性
下一节
了解了变量作用域之后,接下来将学习可变变量——动态确定变量名的特殊用法。请阅读 可变变量。