今月に入ってから 1 回もブログを書いていなかったのでわりとどうでもいい小ネタを。
はじめに
PHP vld を使うと opcode が簡単に表示できて便利です。
インストール方法とか使い方はリンク先参照。opcode ってなに? って人は下記の xdebug の資料とかが参考になります。
マジック定数
クラス内でマジック定数を echo するだけのコードの opcode を vld で見てみました。
<?php namespace A; class B { public function func() { echo __NAMESPACE__; echo __CLASS__; echo __TRAIT__; echo __FUNCTION__; echo __METHOD__; echo __DIR__; echo __FILE__; echo __LINE__; } }
下記のようになりました。
line # * op fetch ext return operands --------------------------------------------------------------------------------- 8 0 > ECHO 'A' 9 1 ECHO 'A%5CB' 10 2 ECHO '' 11 3 ECHO 'func' 12 4 ECHO 'A%5CB%3A%3Afunc' 13 5 ECHO '%2Fhome%2Fng%2Fsandbox%2Fphp' 14 6 ECHO '%2Fhome%2Fng%2Fsandbox%2Fphp%2Fz.php' 15 7 ECHO 15 16 8 > RETURN null
なんとなくリテラルを echo しているだけの opcode になっているっぽいです。
下記のようなリテラルを echo するだけのコードと比べると opcode が同じになるので、マジック定数は実行時にはリテラルになっているようです。
<?php namespace A; class B { public function func() { echo 'A'; echo 'A\B'; echo ''; echo 'func'; echo 'A\B::func'; echo '/home/ng/sandbox/php'; echo '/home/ng/sandbox/php/z.php'; echo 15; } }
c/c++ を知っていれば __FILE__ とかがリテラルになるのはとても自然なことに感じます。
が、トレイト内の __CLASS__ だとリテラルになりません。
<?php namespace A; class B { public function func() { echo __NAMESPACE__; echo __CLASS__; echo __TRAIT__; echo __FUNCTION__; echo __METHOD__; echo __DIR__; echo __FILE__; echo __LINE__; } }
line # * op fetch ext return operands
---------------------------------------------------------------------------------
8 0 > ECHO 'A'
9 1 FETCH_CONSTANT ~0 <const:'A\__CLASS__'>
2 ECHO ~0
10 3 ECHO 'A%5CB'
11 4 ECHO 'func'
12 5 ECHO 'A%5CB%3A%3Afunc'
13 6 ECHO '%2Fhome%2Fng%2Fsandbox%2Fphp'
14 7 ECHO '%2Fhome%2Fng%2Fsandbox%2Fphp%2Fz.php'
15 8 ECHO 15
16 9 > RETURN null
トレイト内の __CLASS__ は トレイトを use しているクラスの名前を返す ため、コンパイル時にはリテラルに解決出来ないためです(PHP: 自動的に定義される定数 - Manual)。
(個人的には実行時に解釈されるならマジック定数ではなく get_used_class のような関数の方が良かった気もします)
名前空間内の非修飾関数呼び出し
下記のコードの opcode を見てみます。
<?php namespace A { func(); \func(); B\func(); \C\func(); }
下記のようになります。
line # * op fetch ext return operands
---------------------------------------------------------------------------------
4 0 > INIT_NS_FCALL_BY_NAME
1 DO_FCALL_BY_NAME 0
5 2 INIT_FCALL_BY_NAME 'func'
3 DO_FCALL_BY_NAME 0
6 4 INIT_FCALL_BY_NAME 'A%5CB%5Cfunc'
5 DO_FCALL_BY_NAME 0
7 6 INIT_FCALL_BY_NAME 'C%5Cfunc'
7 DO_FCALL_BY_NAME 0
8 8 > RETURN 1
よーく見てみると func() だけ opcode が少し異なります。他の呼び出しだと INIT_FCALL_BY_NAME で operands に完全修飾された関数名が入っていますが func() だと INIT_NS_FCALL_BY_NAME で operands にはなにもありません。
・・・しまったこのネタは Qiita に書いてた
要するに、名前空間内の非修飾な関数呼び出しは、その名前空間で定義された関数とグローバル関数の2つの関数に解決されます。
exit とか
こういうの↓は関数ではなく言語構造なので opcode も関数呼び出しにはなりません。さらに die は exit のエイリアスなので opcode は同じです。
<?php isset($a); empty($a); echo "x"; print "x"; exit; die;
line # * op fetch ext return operands
---------------------------------------------------------------------------------
2 0 > ZEND_ISSET_ISEMPTY_VAR 12800000 ~0 !0
1 FREE ~0
3 2 ZEND_ISSET_ISEMPTY_VAR 11800000 ~1 !0
3 FREE ~1
4 4 ECHO 'x'
5 5 PRINT ~2 'x'
6 FREE ~2
6 7 > EXIT
7 8* EXIT
9* > RETURN 1
declare(ticks=1)
シグナルとかを処理しない限り使う機会がほとんどない declare ですが・・・
<?php declare(ticks=1) { echo 1; echo 2; echo 3; }
line # * op fetch ext return operands
---------------------------------------------------------------------------------
4 0 > ECHO 1
1 TICKS
5 2 ECHO 2
3 TICKS
6 4 ECHO 3
5 TICKS
7 6 TICKS
8 7 > RETURN 1
declare(ticks=1) はスクリプトのコンパイル時に TICKS という opcode をねじ込みます。制御構造とはちょっと違うんですねー
さいごに
vld を使うときは xdebug は無効にしておいた方が良いです。些細なことですが xdebug が有効だと EXT_STMT などの余分な opcode が追加されて見づらくなります。
こんなふうに↓
line # * op fetch ext return operands
---------------------------------------------------------------------------------
2 0 > NOP
4 1 EXT_STMT
2 ECHO 1
3 TICKS
5 4 EXT_STMT
5 ECHO 2
6 TICKS
6 7 EXT_STMT
8 ECHO 3
9 TICKS
7 10 TICKS
11 > RETURN 1
いちいち xdebug の有効/無効を切り替えるのも面倒なので、次のようなシェルスクリプトを /usr/local/bin/phpvld 辺りに作っておくと便利です。
#!/bin/bash php -n -d extension=vld.so -d vld.active=1 -d vld.execute=0 "$@"