Skip to content

declare 语句

declare 是 PHP 的一个特殊语言结构,用于设定当前代码块的执行指令。目前支持三个指令:ticksencodingstrict_typesdeclare 在文件级别使用时影响整个文件,也可以配合花括号限制作用范围。在 PHP 8.1+ 中,declare 的作用域规则发生了变化。

前置知识

基础概念

declare 的三种指令:

指令用途默认值版本
ticks控制低级语句执行回调的频率不可用PHP 所有版本
encoding指定脚本的编码取决于 php.iniPHP 5.3+
strict_types开启严格类型检查模式0(关闭)PHP 7.0+

PHP 8.1 作用域变化

从 PHP 8.1 开始,declare(strict_types=1) 只影响其所在的文件,不再受 declare 块的花括号作用域限制。其他 declare 指令(如 ticks)仍然支持块作用域。

语法结构

strict_types:严格类型模式

php
<?php
declare(strict_types=1);

function add(int $a, int $b): int
{
    return $a + $b;
}

// 严格模式下,传入非 int 类型会抛出 TypeError
try {
    echo add(3, 5) . "\n";    // 8
    echo add("3", "5") . "\n"; // TypeError!
} catch (\TypeError $e) {
    echo "类型错误:{$e->getMessage()}\n";
}

非严格模式(默认)

php
<?php
declare(strict_types=0);
// 或不写 declare

function add(int $a, int $b): int
{
    return $a + $b;
}

// 非严格模式下,字符串 "3" 会被隐式转换为 int 3
echo add("3", "5") . "\n"; // 8(无错误)

encoding:指定脚本编码

php
<?php
declare(encoding='UTF-8');

// 指定脚本的编码为 UTF-8
// 这会影响多字节字符串函数的行为
$text = '你好,世界';
echo strlen($text) . "\n";          // 12(字节数)
echo mb_strlen($text) . "\n";      // 6(字符数)

ticks:低级语句回调

php
<?php
declare(strict_types=1);
declare(ticks=1);

// 注册 tick 回调函数
register_tick_function(function (): void {
    echo "[tick] 每执行一条低级语句触发\n";
});

echo "语句 1\n";
echo "语句 2\n";
echo "语句 3\n";

// 取消 tick 回调
unregister_tick_function(function (): void {});

详细说明

strict_types 详解

declare(strict_types=1) 必须是文件中的第一条语句(在 <?php 标签之后,注释之前可以有),且只影响声明所在的文件。

php
<?php
declare(strict_types=1);

// 严格模式下,标量类型声明的参数和返回值
// 必须严格匹配,不会进行隐式类型转换

function calculateArea(float $width, float $height): float
{
    return $width * $height;
}

// 正确:float 传入 float
echo calculateArea(5.0, 3.0) . "\n";    // 15

// 错误:int 传入严格模式的 float
try {
    echo calculateArea(5, 3) . "\n"; // TypeError in strict mode
} catch (\TypeError $e) {
    echo "类型错误:{$e->getMessage()}\n";
}

// 严格模式下的 int 传 float:TypeError
// 严格模式下的 float 传 int:TypeError
// 严格模式下的 string "5" 传 int:TypeError

strict_types 的影响范围

strict_types=1 只影响声明所在的文件中被调用函数的参数和返回值类型检查。它不影响内置函数的行为,也不影响从其他(非 strict)文件调用本文件中的函数。

strict_types 是调用方设置

strict_types调用方决定,而不是被调用方。这意味着:

php
<?php
// file: strict_lib.php
declare(strict_types=1);

function multiply(int $a, int $b): int
{
    return $a * $b;
}
php
<?php
// file: non_strict_caller.php
// 没有 declare(strict_types=1)

require_once __DIR__ . '/strict_lib.php';

// 调用方没有开启严格模式,
// 所以 multiply 中的 int 参数会接受隐式转换
echo multiply("3", "5") . "\n"; // 15,无错误(类型被转换)

ticks 详解

ticks 指令指定了每隔多少条"低级语句"(tickable statement)触发一次注册的回调函数。

可触发的语句(tickable statements): 大多数简单的语句(赋值、函数调用、条件判断等)是可触发的。但控制结构(如 ifwhilefor 本身不是可触发语句)。

php
<?php
declare(strict_types=1);
declare(ticks=1);

$counter = 0;

register_tick_function(function () use (&$counter): void {
    $counter++;
    echo "[tick #{$counter}]\n";
});

$a = 1;      // tick
$b = 2;      // tick
$c = $a + $b; // tick

echo "a + b = {$c}\n"; // tick

unregister_tick_function(function () use (&$counter): void {});

ticks 的实际用途

ticks 在现代 PHP 开发中使用较少,但有一些特殊用途:

  1. 信号处理:在 CLI 脚本中配合 pcntl_signal 处理系统信号。
  2. 性能分析:实现简单的代码执行计时。
  3. 调试:追踪代码执行流程。
php
<?php
declare(strict_types=1);
declare(ticks=1);

// 信号处理示例(CLI 模式)
if (function_exists('pcntl_signal')) {
    pcntl_signal(SIGINT, function (int $signo): void {
        echo "\n收到 SIGINT 信号,准备退出...\n";
        exit(0);
    });

    pcntl_signal(SIGTERM, function (int $signo): void {
        echo "\n收到 SIGTERM 信号,准备退出...\n";
        exit(0);
    });
}

echo "按 Ctrl+C 终止脚本\n";
$counter = 0;

while ($counter < 10) {
    sleep(1);
    $counter++;
    echo "运行中... {$counter}/10\n";
}

echo "正常完成\n";

encoding 详解

declare(encoding='...') 用于指定脚本的编码,主要影响多字节字符串扩展(mbstring)的行为。

php
<?php
declare(encoding='UTF-8');

// 指定编码后,mbstring 函数的默认编码变为 UTF-8
echo mb_internal_encoding() . "\n"; // UTF-8

$text = '中文测试';
echo "字节数:" . strlen($text) . "\n";     // 12
echo "字符数:" . mb_strlen($text) . "\n";   // 4

encoding 的局限性

declare(encoding=...) 主要用于 zend.multibyte 启用的情况。在现代 PHP 应用中,推荐使用 mb_internal_encoding()mb_regex_encoding() 来设置编码,因为 declare(encoding) 的行为依赖于编译配置。

declare 的作用域

php
<?php
declare(strict_types=1);

// strict_types 影响整个文件(PHP 8.1+)

function foo(): void {
    echo "foo()\n";
}

// ticks 可以限制在块作用域内
declare(ticks=1) {
    register_tick_function(function (): void {
        echo "[tick]\n";
    });
    echo "块内语句 1\n"; // 触发 tick
    echo "块内语句 2\n"; // 触发 tick
    unregister_tick_function(function (): void {});
}

echo "块外语句\n"; // 不触发 tick

实战示例

严格类型模式下的数学库

php
<?php
declare(strict_types=1);

class MathLibrary
{
    public static function circleArea(float $radius): float
    {
        if ($radius <= 0) {
            throw new \InvalidArgumentException("半径必须为正数");
        }
        return M_PI * $radius * $radius;
    }

    public static function factorial(int $n): int
    {
        if ($n < 0) {
            throw new \InvalidArgumentException("阶乘仅支持非负整数");
        }
        if ($n <= 1) {
            return 1;
        }
        return $n * self::factorial($n - 1);
    }

    public static function roundTo(float $value, int $precision): float
    {
        $factor = 10 ** $precision;
        return round($value * $factor) / $factor;
    }
}

echo "圆面积(r=5):" . MathLibrary::circleArea(5.0) . "\n";
echo "10 的阶乘:" . MathLibrary::factorial(10) . "\n";
echo "四舍五入:" . MathLibrary::roundTo(3.14159, 2) . "\n";

// 以下会抛出 TypeError(严格模式下 float 参数不接受 int)
try {
    MathLibrary::circleArea(5);
} catch (\TypeError $e) {
    echo "类型错误:{$e->getMessage()}\n";
}

带信号处理的 CLI 守护进程

php
<?php
declare(strict_types=1);
declare(ticks=1);

class DaemonProcess
{
    private bool $running = true;
    private int $processedCount = 0;

    public function start(): void
    {
        if (function_exists('pcntl_signal')) {
            pcntl_signal(SIGTERM, [$this, 'handleSignal']);
            pcntl_signal(SIGINT, [$this, 'handleSignal']);
            pcntl_async_signals(true);
        }

        echo "守护进程启动,PID: " . getmypid() . "\n";

        while ($this->running) {
            $this->processTask();
            sleep(1);
        }

        echo "守护进程正常退出,共处理 {$this->processedCount} 个任务\n";
    }

    private function processTask(): void
    {
        $this->processedCount++;
        $time = date('H:i:s');
        echo "[{$time}] 任务 #{$this->processedCount} 完成\n";
    }

    public function handleSignal(int $signo): void
    {
        $signalName = match ($signo) {
            SIGTERM => 'SIGTERM',
            SIGINT  => 'SIGINT',
            default => "信号 {$signo}",
        };
        echo "\n收到 {$signalName},准备优雅退出...\n";
        $this->running = false;
    }
}

// 模拟运行(注释掉实际信号处理部分以演示)
$daemon = new DaemonProcess();

// 简化演示:处理 3 个任务后停止
for ($i = 0; $i < 3; $i++) {
    $daemon->processTask();
    sleep(1);
}
echo "演示结束\n";

注意事项

  1. strict_types 必须在文件开头declare(strict_types=1) 必须是文件的第一条语句(除了 <?php 标签)。

  2. strict_types 是调用方设置:严格模式由调用方决定,不是由函数定义方决定。

  3. ticks 影响性能declare(ticks=1) 会在每条低级语句后检查回调,可能显著降低性能。

  4. encoding 的实际效果有限:在现代应用中,mb_internal_encoding() 更常用。

  5. declare(ticks) 需要配合回调:单独的 declare(ticks=N) 没有任何效果,必须配合 register_tick_function

最佳实践

  1. 始终在新项目中启用 strict_types:严格类型检查能减少类型相关的 bug,提高代码质量。

  2. 避免在生产代码中使用 ticksticks 的性能开销大,仅在需要信号处理或调试时使用。

  3. 使用 mb_internal_encoding() 代替 declare(encoding):后者依赖于编译配置,行为不够可靠。

  4. 在库文件中也启用 strict_types:库文件使用 declare(strict_types=1) 是最佳实践,调用方会根据自身的设置决定是否强制严格类型。

  5. 了解调用方控制原则:理解 strict_types 由调用方控制这一设计决策,是正确使用严格类型模式的关键。

下一节

流程控制的跳转与终止语句到此结束。接下来学习 include/require 文件包含机制。

参考链接