php-srcのコードリーディングした内容をコツコツ残すテスト。その12
classの続き。method呼び出し, private, protected
<?php
class Hoge
{
private $fuga;
public function call($foo)
{
echo $foo;
}
}
$hoge = new Hoge();
$hoge->call(111);
$hoge->fuga;
opcode
$_main:
; (lines=9, args=0, vars=1, tmps=5)
; (before optimizer)
; /path/to/12.php:1-16
; return [] RANGE[0..0]
0000 V1 = NEW 0 string("Hoge")
0001 DO_FCALL
0002 ASSIGN CV0($hoge) V1
0003 INIT_METHOD_CALL 1 CV0($hoge) string("call")
0004 SEND_VAL_EX int(111) 1
0005 DO_FCALL
0006 T5 = FETCH_OBJ_R CV0($hoge) string("fuga")
0007 FREE T5
0008 RETURN int(1)
LIVE RANGES:
1: 0001 - 0002 (new)
Hoge::call:
; (lines=3, args=1, vars=1, tmps=0)
; (before optimizer)
; /path/to/12.php:7-10
; return [] RANGE[0..0]
0000 CV0($foo) = RECV 1
0001 ECHO CV0($foo)
0002 RETURN null
INIT_METHOD_CALL
FETCH_OBJ_R
あたり
zend_compile_method_call()
でメソッド呼び出しはコンパイルされる
ZEND_INIT_METHOD_CALL
をemit
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L4529:L4529
zend_compile_call_common()
で引数などコンパイル
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L4557:L4557
zend_compile_prop()
=> zend_compile_prop()
=> zend_delayed_compile_prop()
と呼び出していって、ZEND_FETCH_OBJ_R
をemit
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L2923:L2923
ZEND_INIT_METHOD
のopcodeは ZEND_INIT_METHOD_CALL_SPEC_CV_CONST_HANDLER
がハンドリングする
obj = Z_OBJ_P(object);
function_name = RT_CONSTANT(opline, opline->op2);
fbc = obj->handlers->get_method(&obj, Z_STR_P(function_name), ((IS_CONST == IS_CONST) ? (RT_CONSTANT(opline, opline->op2) + 1) : NULL));
// zend_hash_find(&zobj->ce->function_table, lc_method_name) を呼び出してmethodのzend_functionのポインタを取得
call = zend_vm_stack_push_call_frame(call_info,
fbc, opline->extended_value, obj);
call->prev_execute_data = EX(call);
EX(call) = call;
FETCH_OBJ_R
のopcodeは ZEND_FETCH_OBJ_R_SPEC_CV_CONST_HANDLER
がハンドリングする。
ZEND_FETCH_OBJ_R_SPEC_CV_CONST_HANDLER
は ZEND_FETCH_OBJ_R_SPEC_CV_CONST_INLINE_HANDLER
を呼び出す
container = EX_VAR(opline->op1.var);
zend_object *zobj = Z_OBJ_P(container);
name = Z_STR_P(RT_CONSTANT(opline, opline->op2));
retval = zobj->handlers->read_property(zobj, name, BP_VAR_R, cache_slot, EX_VAR(opline->result.var));
read_property
は関数ポインタで zend_std_read_property()
を呼び出す。
zend_std_read_property()
は zend_get_property_offset()
を呼び出す。
property_offset = zend_get_property_offset(zobj->ce, name, (type == BP_VAR_IS) || (zobj->ce->__get != NULL), cache_slot, &prop_info);
zend_get_property_offset()
はプロパティ名からプロパティのzvalを取得し属性に応じた処理を行う。
zv = zend_hash_find(&ce->properties_info, member)
property_info = (zend_property_info*)Z_PTR_P(zv);
flags = property_info->flags;
private変数にアクセスしようとすると最終的にここに到達してエラーになる
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_object_handlers.c#L355:L355
if (flags & (ZEND_ACC_CHANGED|ZEND_ACC_PRIVATE|ZEND_ACC_PROTECTED)) {
if (property_info->ce != scope) {
if (flags & ZEND_ACC_PRIVATE) {
if (property_info->ce != ce) {
} else {
if (!silent) {
zend_bad_property_access(property_info, ce, member);
flagsにはZEND_ACC_PRIVATEが入っているので1,3行目の分岐を通る。
2行目は今のコールスコープがプロパティのクラスと異なる場合という分岐。 今はmainスクリプトのスコープからプロパティを呼び出しているので「異なる」という判定になる。
property_info->ce != EG(current_execute_data)->func->common.scope;
ちなみに、メソッドのスコープはここで設定されている↓
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L7089:L7089
4行目はオブジェクトのクラスとプロパティのクラスが異なる場合という分岐。スーパークラスのprivateプロパティを呼び出すとこの判定を通る。
今回はオブジェクトのクラスとプロパティのクラスが同じなのでelseを通り、slientが0なので zend_bad_property_access()
が呼ばれエラーとなる。
protectedの場合、is_protected_compatible_scope()
でアクセス可否を判定する
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_object_handlers.c#L361
具体的には、呼び出し元の継承チェーンに呼び出し先のクラスが含まれるか、あるいは呼び出し先のクラスの継承チェーンに呼び出し元が含まれるか、というのを判定している。
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_object_handlers.c#L236-L240
前者はこんな感じなパターン。protectedがスーパークラス側にある場合。
<?php
class Base
{
protected $hoge = 123;
}
class Hoge extends Base
{
public function hoge()
{
return $this->hoge . PHP_EOL;
}
}
echo (new Hoge)->hoge();
後者はこんな感じ。これできるの初めて知った。
<?php
class Base
{
public function hoge()
{
return $this->fuga;
}
}
class Hoge extends Base
{
protected $fuga = 123;
}
echo (new Hoge)->hoge();