PHP Conference Japan 2024

declare

(PHP 4, PHP 5, PHP 7, PHP 8)

declare 结构用来设定一段代码的执行指令。 declare 的语法和其它流程控制结构相似

declare (directive)
    statement

directive 部分允许设定 declare 代码段的行为。目前支持如下指令

由于指令是当文件被编译时处理的,因此指令的值只允许使用字面量。不可使用变量和常量。例如

<?php
// 这是合法的:
declare(ticks=1);

// 这是非法的:
const TICK_VALUE = 1;
declare(
ticks=TICK_VALUE);
?>

declare 代码段中的 statement 部分将被执行——怎样执行以及执行中有什么副作用将取决于 directive 代码段中的指令设置。

declare 结构也可以用于全局作用域,这将影响到其后的所有代码(然而如果包含有 declare 结构的代码文件是被其它文件包含的,则对包含它的父文件不起作用)。

<?php
// 下面两种用法是一样的:

// 你可以使用:
declare(ticks=1) {
// 整个脚本写在这里
}

// 或者你也可以这样:
declare(ticks=1);
// 整个脚本写在这里
?>

Ticks

一个 tick 是一个在 declare 代码段中解释器每执行 N 条可计时的低级语句就会发生的事件。N 的值是在 declare 代码段的 directive 部分用 ticks=N 来指定的。

不是所有语句都可计时。通常条件表达式和参数表达式都不可计时。

在每个 tick 中出现的事件是由 register_tick_function() 函数来指定的。更多细节见下面的例子。注意每个 tick 中可以出现多个事件。

示例 #1 Tick 的用法示例

<?php

declare(ticks=1);

// 每执行到一个 tick 事件都会调用此函数
function tick_handler()
{
echo
"tick_handler() 被调用了\n";
}

register_tick_function('tick_handler'); // 产生一个 tick 事件

$a = 1; // 产生一个 tick 事件

if ($a > 0) {
$a += 2; // 产生一个 tick 事件
print $a; // 产生一个 tick 事件
}

?>

参见 register_tick_function()unregister_tick_function()

Encoding

可以用 encoding 指令来对每个脚本指定其编码方式。

示例 #2 为脚本指定编码

<?php
declare(encoding='ISO-8859-1');
// 代码开始
?>

Caution

当和命名空间一起使用时,合法的 declare 语法只有 declare(encoding='...'); 这一种,这里的 ... 是编码值。当和命名空间一起使用时 declare(encoding='...') {} 会导致一个解析错误。

参见 zend.script_encoding

添加一个注释

用户贡献的注释 10 条

up
65
匿名
14 年前
很奇怪这么多人不理解这里的概念。注意文档中的措辞。它指出每 n 个本机执行周期调用一次 tick 处理程序。这意味着本机指令,不包括系统调用(我猜)。如果您需要优化脚本的特定部分,这可以让您有一个很好的了解,因为您可以非常有效地衡量实际代码中有多少本机指令。

一个好的分析器会考虑到这一点,并强制您(开发人员)在进入和离开每个函数时都包含对分析器的调用。这样您就可以关注每个函数完成所需的周期数。与时间无关。

这是非常强大的,不容小觑。一个好的解决方案应该允许聚合统计信息,因此函数中的总时间将被计算,包括在被调用函数内部。
up
22
Kubo2
9 年前
请注意,在 PHP 7 中,如果 Zend Multibyte 被关闭,<?php declare(encoding='...'); ?> 会抛出一个 E_WARNING。
up
22
sawyerrken at gmail dot com
11 年前
在下面的示例中

<?php
function handler(){
print
"hello <br />";
}

register_tick_function("handler");

declare(
ticks = 1){
$b = 2;
}
// 闭合花括号也是可计时的
?>

"Hello" 将被显示两次,因为闭合花括号也是可计时的。

有人可能会想,如果闭合花括号是可计时的,为什么起始花括号不可计时。这是因为 PHP 开始计时的指令是由起始花括号给出的,因此计时在它之后立即开始。
up
5
digitalaudiorock at gmail dot com
5 年前
关于我之前关于 declare(ticks=1) 在 5.6 和 7.x 之间范围变化的评论,我打算提一下这可能对信号处理程序产生的另一个影响的例子

如果您的脚本使用 declare(ticks=1) 并分配处理程序,在 5.6 中,即使正在运行的代码位于包含的文件中(其中包含的文件没有声明),信号也会被捕获并调用处理程序。然而在 7.x 中,信号将不会被捕获,直到代码返回到主脚本。

最好的解决办法是使用 pcntl_async_signals(true)(如果可用),这将允许无论代码碰巧在哪个文件中都能捕获信号。
up
9
digitalaudiorock at gmail dot com
5 年前
对于任何将此与信号处理程序结合使用的人来说,需要注意的几个重要事项

如果有人试图在可用时选择性地使用 pcntl_async_signals() (PHP >= 7.1) 或对旧版本使用 ticks,这是不可能的……至少在不会对较新 PHP 版本启用 ticks 的情况下不可能。这是因为根本没有办法有条件地声明 ticks。例如,以下代码可以“运行”,但结果可能与您预期不同。

<?php
if (function_exists('pcntl_async_signals')) {
pcntl_async_signals(true);
} else {
declare(
ticks=1);
}
?>

虽然信号处理程序可以在旧版本和新版本中正常工作,但即使存在 pcntl_async_signals,ticks 也会被启用,这仅仅是因为存在 declare 语句。因此,上述代码在功能上等同于:

<?php
if (function_exists('pcntl_async_signals')) pcntl_async_signals(true);
declare(
ticks=1);
?>

另一件需要注意的事情是,此声明的作用域在 PHP 5.6 到 7.x 之间发生了一些变化……实际上,它似乎得到了修正,如此处所述:

https://php.net/manual/en/function.register-tick-function.php#121204

这可能会导致一些非常令人困惑的行为。一个例子是 pear/System_Daemon 模块。在 PHP 5.6 中,即使使用它的脚本本身不使用 declare(ticks=1),它也可以使用 SIGTERM 处理程序,但在 PHP 7 中,除非脚本本身具有该声明,否则它不起作用。不仅处理程序没有被调用,而且信号根本没有任何作用,并且脚本不会退出。

关于 ticks 的一个附注,这个问题困扰了我一段时间:似乎围绕这一切的困惑还不够多,互联网上充斥着 ticks 已被弃用并正在被删除的虚假谣言,我相信它们都始于这里:

http://www.hackingwithphp.com/4/21/0/the-declare-function-and-ticks

尽管页面末尾有一个非常晦涩的作者注释说他弄错了(我刚刚才注意到),但文章开头非常突出的句子仍然这么说,而且该页面在任何 Google 搜索结果中都名列前茅。
up
7
php dot net at e-z dot name
11 年前
您可以注册多个 tick 函数

<?PHP
function a() { echo "a\n"; }
function
b() { echo "b\n"; }

register_tick_function('a');
register_tick_function('b');
register_tick_function('b');
register_tick_function('b');

?>

将在每个 tick 上输出
a
b
b
b
up
5
ja2016 at wir dot pl
7 年前
不要使用带有 BOM 的 uft-8 编码。否则总是会发生致命错误。请将其替换为不带 BOM 的 utf-8。

---

*BOM*
<?php
declare(strict_types=1);
//致命错误:strict_types 声明必须是脚本中的第一个语句
up
5
fok at nho dot com dot br
21 年前
这是一个非常简单的示例,它使用 ticks 执行外部脚本来显示服务器的 rx/tx 数据

<?php

function traf(){
passthru( './traf.sh' );
echo
"<br />\n";
flush(); // 使其持续流向浏览器...
sleep( 1 );
}

register_tick_function( "traf" );

declare(
ticks=1 ){
while(
true ){} // 使其持续运行...
}

?>

traf.sh 的内容
# 显示 eth0 在 1 秒内的 TX/RX
#!/bin/bash

TX1=`cat /proc/net/dev | grep "eth0" | cut -d: -f2 | awk '{print $9}'`
RX1=`cat /proc/net/dev | grep "eth0" | cut -d: -f2 | awk '{print $1}'`
sleep 1
TX2=`cat /proc/net/dev | grep "eth0" | cut -d: -f2 | awk '{print $9}'`
RX2=`cat /proc/net/dev | grep "eth0" | cut -d: -f2 | awk '{print $1}'`

echo -e "TX: $[ $TX2 - $TX1 ] bytes/s \t RX: $[ $RX2 - $RX1 ] bytes/s"
#--= 结束. =--
up
4
markandrewslade at dontspamemeat dot gmail
15 年前
请注意,调用 declare 的两种方法并不相同。

方法 1

<?php
// 打印带有时间戳和可选后缀的 "tick"。
function do_tick($str = '') {
list(
$sec, $usec) = explode(' ', microtime());
printf("[%.4f] Tick.%s\n", $sec + $usec, $str);
}
register_tick_function('do_tick');

// 在声明之前执行一次 tick,以便我们有一个参考点。
do_tick('--start--');

// 方法 1
declare(ticks=1);
while(
1) sleep(1);

/* 输出:
[1234544435.7160] Tick.--start--
[1234544435.7161] Tick.
[1234544435.7162] Tick.
[1234544436.7163] Tick.
[1234544437.7166] Tick.
*/

?>

方法 2
<?php
// 打印带有时间戳和可选后缀的 "tick"。
function do_tick($str = '') {
list(
$sec, $usec) = explode(' ', microtime());
printf("[%.4f] Tick.%s\n", $sec + $usec, $str);
}
register_tick_function('do_tick');

// 在声明之前执行一次 tick,以便我们有一个参考点。
do_tick('--start--');

// 方法 2
declare(ticks=1) {
while(
1) sleep(1);
}

/* 输出:
[1234544471.6486] Tick.--start--
[1234544472.6489] Tick.
[1234544473.6490] Tick.
[1234544474.6492] Tick.
[1234544475.6493] Tick.
*/
?>

请注意,在 declare 之后使用 {} 时,do_tick 直到我们进入 declare {} 块后约 1 秒才被自动调用。但是,当不使用 {} 时,do_tick 在调用 declare() 后立即被自动调用了两次,而不是一次。

我假设这是由于 PHP 在内部处理 ticking 的方式所致。也就是说,不带 {} 的 declare() 似乎会触发更多低级指令,而这些指令又会在声明过程中触发几次 tick(如果 ticks=1)。
up
1
ohcc at 163 dot com
4 年前
如果支持每个指令,则可以一次设置多个指令。
<?php
declare(strict_types=1, encoding='UTF-8');
?>
To Top