null — 空值类型
概述
null 是 PHP 中一个特殊的标量值,表示一个变量没有值。null 是 PHP 类型系统中唯一只有一个可能值的类型。在数据库操作、可选参数、未初始化变量等场景中,null 扮演着重要的角色。正确理解和使用 null,有助于编写更健壮的代码。
前置知识
在阅读本节之前,你需要了解:
- PHP 变量的基本概念和赋值方式
isset()和empty()的区别- 可空类型(
?Type)的概念 - null 安全运算符(
??和?->,PHP 7.0+/8.0+)
基础概念
null 的三种产生方式
一个变量被认为是 null 类型,当且仅当它满足以下三种情况之一:
- 被赋值为字面量
null - 尚未被赋值
- 被
unset()销毁
php
<?php
declare(strict_types=1);
// 方式一:直接赋值为 null
$name = null;
// 方式二:声明但未赋值(自动为 null)
$undefined;
// 方式三:使用 unset()
$existing = 'hello';
unset($existing);
// 此时 $existing 为 nullnull 与 unset 的区别
unset() 并不是将变量设为 null,而是完全删除变量。两者的区别在于:
| 操作 | 变量是否存在 | isset() | is_null() | $var ?? 'default' |
|---|---|---|---|---|
$a = null | 是 | false | true | 'default' |
unset($a) | 否 | false | true(会触发警告) | 'default' |
isset 与 is_null 的关键区别
isset() 对于值为 null 的已存在变量返回 false,而 is_null() 返回 true。isset() 不会对不存在的变量产生警告,is_null() 在变量不存在时会产生警告(除非在 ?? 运算符中)。
语法与代码
is_null() 函数
php
<?php
declare(strict_types=1);
$a = null;
$b = 0;
$c = '';
$d = false;
var_dump(is_null($a)); // bool(true)
var_dump(is_null($b)); // bool(false)
var_dump(is_null($c)); // bool(false)
var_dump(is_null($d)); // bool(false)
// is_null() 等价于 $var === null
var_dump($a === null); // bool(true)null 安全运算符 ??
php
<?php
declare(strict_types=1);
// ?? 运算符:左值为 null 或不存在时返回右值
$username = $_GET['username'] ?? 'Guest';
$page = $_GET['page'] ?? 1;
$theme = $config['theme'] ?? 'light';
// 链式 null 合并
$value = $data['user']['profile']['avatar'] ?? '/default-avatar.png';
// ?? 与 isset() 的区别
// ?? 不会在变量不存在时触发警告
// isset() 也不触发警告
// 直接访问 $var[key] 在 var 不存在时会触发警告
// ?? 优先级低于三元运算符
$result = $a ?? $b ?: $c; // 等价于 ($a ?? $b) ?: $cnull 安全方法调用 ?->(PHP 8.0+)
php
<?php
declare(strict_types=1);
class User
{
public function __construct(
public readonly ?string $name = null,
public readonly ?Address $address = null,
) {}
public function getName(): ?string
{
return $this->name;
}
}
class Address
{
public function __construct(
public readonly ?string $city = null,
public readonly ?string $country = null,
) {}
public function getFullAddress(): string
{
return implode(', ', array_filter([$this->city, $this->country]));
}
}
// PHP 8.0+ null 安全运算符 ?->
$user = new User(name: 'Alice', address: null);
// 不使用 ?->:需要层层判断
if ($user !== null) {
if ($user->address !== null) {
echo $user->address->getFullAddress();
}
}
// 使用 ?->:简洁安全
echo $user?->address?->getFullAddress() ?? '地址未知';
// 输出: 地址未知可空类型声明
php
<?php
declare(strict_types=1);
// ?Type 语法:允许参数或返回值为 null
function findUserById(int $id): ?User
{
if ($id <= 0) {
return null; // 未找到用户时返回 null
}
return new User($id);
}
function greetUser(?User $user): string
{
if ($user === null) {
return '欢迎,访客!';
}
return "欢迎,用户 #{$user->id}!";
}
// 使用
echo greetUser(null); // "欢迎,访客!"
echo greetUser(new User(1)); // "欢迎,用户 #1!"
// 等价于联合类型
function findUserByIdAlt(int $id): User|null
{
return $id > 0 ? new User($id) : null;
}详细说明
null 在类型转换中的行为
| 转换目标 | null 的转换结果 |
|---|---|
(bool) | false |
(int) | 0 |
(float) | 0.0 |
(string) | ""(空字符串) |
(array) | [](空数组) |
(object) | stdClass 的空实例 |
php
<?php
declare(strict_types=1);
$null = null;
var_dump((bool)$null); // bool(false)
var_dump((int)$null); // int(0)
var_dump((float)$null); // float(0)
var_dump((string)$null); // string(0) ""
var_dump((array)$null); // array(0) { }
var_dump((object)$null); // object(stdClass)#1 {}null 与 empty() 的关系
empty() 在以下情况返回 true:
null、false、0、0.0、""、"0"、[]、未定义变量
php
<?php
declare(strict_types=1);
$values = [
null, false, 0, 0.0, "", "0", [], "hello"
];
foreach ($values as $value) {
$export = var_export($value, true);
$empty = empty($value) ? 'true' : 'false';
echo "empty({$export}) = {$empty}" . PHP_EOL;
}
// empty(NULL) = true
// empty(false) = true
// empty(0) = true
// empty(0.0) = true
// empty('') = true
// empty('0') = true
// empty(array()) = true
// empty('hello') = falseempty() 的隐患
empty() 对 "0" 也返回 true,这在处理表单输入时可能导致逻辑错误。当需要严格判断 null 时,应使用 === null 或 is_null()。
实战示例
Optional 模式的 null 安全包装
php
<?php
declare(strict_types=1);
/**
* 简单的 Optional 值容器
* 用于安全地处理可能为 null 的值
*/
final class Optional
{
private function __construct(
private readonly mixed $value
) {}
public static function of(mixed $value): self
{
return new self($value);
}
public function isPresent(): bool
{
return $this->value !== null;
}
public function isNull(): bool
{
return $this->value === null;
}
public function get(): mixed
{
if ($this->value === null) {
throw new RuntimeException('值为 null,无法获取');
}
return $this->value;
}
public function getOrElse(mixed $default): mixed
{
return $this->value ?? $default;
}
public function map(callable $callback): self
{
if ($this->value === null) {
return self::of(null);
}
return self::of($callback($this->value));
}
public function filter(callable $callback): self
{
if ($this->value === null || !$callback($this->value)) {
return self::of(null);
}
return self::of($this->value);
}
}
// 使用示例
$user = Optional::of(null);
$name = $user
->map(fn($u) => $u->name ?? 'Unknown')
->getOrElse('Guest');
echo $name; // "Guest"
$value = Optional::of(42)
->map(fn($n) => $n * 2)
->map(fn($n) => "Number: {$n}")
->get();
echo $value; // "Number: 84"注意事项
1. 不要滥用 null 作为错误指示
php
<?php
declare(strict_types=1);
// 不推荐:用 null 表示"出错"
function divide(int $a, int $b): ?float
{
if ($b === 0) return null; // 语义不清
return $a / $b;
}
// 推荐:抛出异常
function divideSafe(int $a, int $b): float
{
if ($b === 0) throw new DivisionByZeroError('除零错误');
return $a / $b;
}2. 类型声明中 null 的位置
php
<?php
declare(strict_types=1);
// PHP 8.0+ 允许 null 出现在联合类型中的任意位置
function process(int|string|null $value): void {}
// 但 ?Type 语法要求 Type 在前面
function processAlt(?int $value): void {}3. null 与数据库交互
php
<?php
declare(strict_types=1);
// PDO 默认将 PHP null 映射为 SQL NULL
$stmt = $pdo->prepare('INSERT INTO users (name, email) VALUES (:name, :email)');
$stmt->execute([
':name' => 'Alice',
':email' => null, // 映射为 SQL NULL
]);
// 查询结果中 SQL NULL 映射为 PHP null
$stmt = $pdo->query('SELECT name, email FROM users WHERE id = 1');
$row = $stmt->fetch();
var_dump($row['email']); // 可能为 null最佳实践
- 优先使用 ?? 运算符:替代冗长的
isset()+ 三元运算 - 使用 ?-> 链式调用:PHP 8.0+ 使用 null 安全运算符
- 可空类型用 ?Type:比
Type|null更简洁 - 语义区分:
null表示"无值",false表示"否",0表示"零" - 避免 null 参数:使用方法重载或默认值替代
- 返回值明确:函数要么返回有效值,要么返回 null,不要混用
下一节
下一节将详细介绍 bool 布尔类型,了解 true/false 的特性以及 PHP 的隐式转换规则。
进阶用法
调试与测试技巧
php
<?php
declare(strict_types=1);
// 单元测试辅助函数
function createTestResource(): mixed
{
return match (true) {
default => new stdClass(),
};
}
// 调试输出函数
function debugOutput(mixed , string = ''): void
{
= ? ": " : '';
.= print_r(, true);
fwrite(STDERR, . "\n");
}
// 性能基准测试
function benchmark(callable , int = 1000): float
{
= hrtime(true);
for ($i = 0; $i < $iterations; $i++) {
$fn();
}
return (hrtime(true) - $start) / 1e9;
}日志记录实践
php
<?php
declare(strict_types=1);
/**
* 简易日志记录器
*/
class SimpleLogger
{
private string $logFile;
private string $level = 'INFO';
public function __construct(string $logFile)
{
$this->logFile = $logFile;
}
public function info(string $message, array $context = []): void
{
$this->log('INFO', $message, $context);
}
public function warning(string $message, array $context = []): void
{
$this->log('WARNING', $message, $context);
}
public function error(string $message, array $context = []): void
{
$this->log('ERROR', $message, $context);
}
private function log(string $level, string $message, array $context): void
{
$timestamp = date('Y-m-d H:i:s');
$contextStr = $context ? ' ' . json_encode($context, JSON_UNESCAPED_UNICODE) : '';
$line = "[{$timestamp}] [{$level}] {$message}{$contextStr}\n";
file_put_contents($this->logFile, $line, FILE_APPEND | LOCK_EX);
}
}配置与环境检测
php
<?php
declare(strict_types=1);
// 环境检测工具
class EnvironmentChecker
{
public static function checkRequirements(array $requirements): array
{
$results = [];
foreach ($requirements as $name => $check) {
$results[$name] = is_callable($check) ? $check() : false;
}
return $results;
}
public static function getSystemInfo(): array
{
return [
'php_version' => PHP_VERSION,
'os' => PHP_OS,
'sapi' => PHP_SAPI,
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'loaded_extensions' => get_loaded_extensions(),
];
}
}常见问题排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 连接超时 | 网络问题/配置错误 | 检查配置,增加超时时间 |
| 权限不足 | 文件/目录权限 | 使用 chmod/chown 修正 |
| 性能下降 | 索引缺失/数据量大 | 添加索引,优化查询 |
| 数据不一致 | 并发冲突/事务残留 | 使用锁机制和事务 |
| 内存溢出 | 大数据集/未释放资源 | 增大内存限制,分批处理 |
故障排除步骤
- 检查错误日志和异常信息
- 确认配置和环境是否正确
- 使用调试工具逐步排查
- 参考官方文档查找已知问题
版本兼容性说明
| 功能 | 最低版本 | 说明 |
|---|---|---|
| 基础功能 | PHP 8.1 | 本文档基准版本 |
| 只读属性 | PHP 8.1 | public readonly 修饰符 |
| 枚举类型 | PHP 8.1 | enum 类型和 match 表达式 |
| Fiber | PHP 8.1 | 协程/轻量级并发 |
| 命名参数 | PHP 8.0 | foo(arg_name: value) |
| 联合类型 | PHP 8.0 | `int |
| Null 安全运算符 | PHP 8.0 | $obj?->method() |
| 析构器 promotion | PHP 8.0 | __construct(public $x) |
php
<?php
declare(strict_types=1);
// 版本兼容性检测
function ensureVersion(string $minVersion): void
{
if (version_compare(PHP_VERSION, $minVersion, '<')) {
throw new RuntimeException(
sprintf('需要 PHP %s+, 当前版本: %s', $minVersion, PHP_VERSION)
);
}
}
ensureVersion('8.1.0');