declare 语句
declare 是 PHP 的一个特殊语言结构,用于设定当前代码块的执行指令。目前支持三个指令:ticks、encoding 和 strict_types。declare 在文件级别使用时影响整个文件,也可以配合花括号限制作用范围。在 PHP 8.1+ 中,declare 的作用域规则发生了变化。
前置知识
- 了解 PHP 的编译和执行模型
- 熟悉 ticks 回调函数
- 了解 strict_types 的含义
- 了解 PHP 的编码设置
基础概念
declare 的三种指令:
| 指令 | 用途 | 默认值 | 版本 |
|---|---|---|---|
ticks | 控制低级语句执行回调的频率 | 不可用 | PHP 所有版本 |
encoding | 指定脚本的编码 | 取决于 php.ini | PHP 5.3+ |
strict_types | 开启严格类型检查模式 | 0(关闭) | PHP 7.0+ |
PHP 8.1 作用域变化
从 PHP 8.1 开始,declare(strict_types=1) 只影响其所在的文件,不再受 declare 块的花括号作用域限制。其他 declare 指令(如 ticks)仍然支持块作用域。
语法结构
strict_types:严格类型模式
<?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
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
declare(encoding='UTF-8');
// 指定脚本的编码为 UTF-8
// 这会影响多字节字符串函数的行为
$text = '你好,世界';
echo strlen($text) . "\n"; // 12(字节数)
echo mb_strlen($text) . "\n"; // 6(字符数)ticks:低级语句回调
<?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
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:TypeErrorstrict_types 的影响范围
strict_types=1 只影响声明所在的文件中被调用函数的参数和返回值类型检查。它不影响内置函数的行为,也不影响从其他(非 strict)文件调用本文件中的函数。
strict_types 是调用方设置
strict_types 由调用方决定,而不是被调用方。这意味着:
<?php
// file: strict_lib.php
declare(strict_types=1);
function multiply(int $a, int $b): int
{
return $a * $b;
}<?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): 大多数简单的语句(赋值、函数调用、条件判断等)是可触发的。但控制结构(如 if、while、for 本身不是可触发语句)。
<?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 开发中使用较少,但有一些特殊用途:
- 信号处理:在 CLI 脚本中配合
pcntl_signal处理系统信号。 - 性能分析:实现简单的代码执行计时。
- 调试:追踪代码执行流程。
<?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
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"; // 4encoding 的局限性
declare(encoding=...) 主要用于 zend.multibyte 启用的情况。在现代 PHP 应用中,推荐使用 mb_internal_encoding() 或 mb_regex_encoding() 来设置编码,因为 declare(encoding) 的行为依赖于编译配置。
declare 的作用域
<?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
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
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";注意事项
strict_types 必须在文件开头:
declare(strict_types=1)必须是文件的第一条语句(除了<?php标签)。strict_types 是调用方设置:严格模式由调用方决定,不是由函数定义方决定。
ticks 影响性能:
declare(ticks=1)会在每条低级语句后检查回调,可能显著降低性能。encoding 的实际效果有限:在现代应用中,
mb_internal_encoding()更常用。declare(ticks)需要配合回调:单独的declare(ticks=N)没有任何效果,必须配合register_tick_function。
最佳实践
始终在新项目中启用 strict_types:严格类型检查能减少类型相关的 bug,提高代码质量。
避免在生产代码中使用 ticks:
ticks的性能开销大,仅在需要信号处理或调试时使用。使用
mb_internal_encoding()代替declare(encoding):后者依赖于编译配置,行为不够可靠。在库文件中也启用 strict_types:库文件使用
declare(strict_types=1)是最佳实践,调用方会根据自身的设置决定是否强制严格类型。了解调用方控制原则:理解
strict_types由调用方控制这一设计决策,是正确使用严格类型模式的关键。
下一节
流程控制的跳转与终止语句到此结束。接下来学习 include/require 文件包含机制。