今月に入ってから 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 "$@"