Skip to content

null — 空值类型

概述

null 是 PHP 中一个特殊的标量值,表示一个变量没有值。null 是 PHP 类型系统中唯一只有一个可能值的类型。在数据库操作、可选参数、未初始化变量等场景中,null 扮演着重要的角色。正确理解和使用 null,有助于编写更健壮的代码。

前置知识

在阅读本节之前,你需要了解:

  • PHP 变量的基本概念和赋值方式
  • isset()empty() 的区别
  • 可空类型(?Type)的概念
  • null 安全运算符(???->,PHP 7.0+/8.0+)

基础概念

null 的三种产生方式

一个变量被认为是 null 类型,当且仅当它满足以下三种情况之一:

  1. 被赋值为字面量 null
  2. 尚未被赋值
  3. unset() 销毁
php
<?php
declare(strict_types=1);

// 方式一:直接赋值为 null
$name = null;

// 方式二:声明但未赋值(自动为 null)
$undefined;

// 方式三:使用 unset()
$existing = 'hello';
unset($existing);
// 此时 $existing 为 null

null 与 unset 的区别

unset() 并不是将变量设为 null,而是完全删除变量。两者的区别在于:

操作变量是否存在isset()is_null()$var ?? 'default'
$a = nullfalsetrue'default'
unset($a)falsetrue(会触发警告)'default'

isset 与 is_null 的关键区别

isset() 对于值为 null 的已存在变量返回 false,而 is_null() 返回 trueisset() 不会对不存在的变量产生警告,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) ?: $c

null 安全方法调用 ?->(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

  • nullfalse00.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') = false

empty() 的隐患

empty()"0" 也返回 true,这在处理表单输入时可能导致逻辑错误。当需要严格判断 null 时,应使用 === nullis_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

最佳实践

  1. 优先使用 ?? 运算符:替代冗长的 isset() + 三元运算
  2. 使用 ?-> 链式调用:PHP 8.0+ 使用 null 安全运算符
  3. 可空类型用 ?Type:比 Type|null 更简洁
  4. 语义区分null 表示"无值",false 表示"否",0 表示"零"
  5. 避免 null 参数:使用方法重载或默认值替代
  6. 返回值明确:函数要么返回有效值,要么返回 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 修正
性能下降索引缺失/数据量大添加索引,优化查询
数据不一致并发冲突/事务残留使用锁机制和事务
内存溢出大数据集/未释放资源增大内存限制,分批处理

故障排除步骤

  1. 检查错误日志和异常信息
  2. 确认配置和环境是否正确
  3. 使用调试工具逐步排查
  4. 参考官方文档查找已知问题

版本兼容性说明

功能最低版本说明
基础功能PHP 8.1本文档基准版本
只读属性PHP 8.1public readonly 修饰符
枚举类型PHP 8.1enum 类型和 match 表达式
FiberPHP 8.1协程/轻量级并发
命名参数PHP 8.0foo(arg_name: value)
联合类型PHP 8.0`int
Null 安全运算符PHP 8.0$obj?->method()
析构器 promotionPHP 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');

参考链接