2022-04-21

[php-src読書録]その15: interfaceとtrait

php-srcのコードリーディングした内容をコツコツ残すテスト。その15

今日はinterfaceとtrait

interface

<?php

interface Base
{
  public function hoge();
}

class Hoge implements Base
{
  public function hoge()
  {
  }
}

opcode

$_main:
; (lines=2, args=0, vars=0, tmps=0)
; (before optimizer)
; /path/to/15.php:1-14
; return  [] RANGE[0..0]
0000 DECLARE_CLASS string("hoge")
0001 RETURN int(1)

Base::hoge:
; (lines=1, args=0, vars=0, tmps=0)
; (before optimizer)
; /path/to/15.php:5-5
; return  [] RANGE[0..0]
0000 RETURN null

Hoge::hoge:
; (lines=1, args=0, vars=0, tmps=0)
; (before optimizer)
; /path/to/15.php:10-12
; return  [] RANGE[0..0]
0000 RETURN null

implementsした場合は DECLARE_CLASS opcodeが出力されているのがポイント。 前回はuseとimplementsがないパターンの継承パターンだったが、useとimplementsがある場合はフローがちょっと変わってくる。

interfaceも zend_compile_class_decl() でコンパイルされる

そのinterfaceをimplementsした場合は zend_compile_implements() が呼ばれる
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L7733:L7733

クラスが継承しているインターフェースの数とインターフェースを num_interfaces interface_names にセットする
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L7598-L7599

ZEND_DECLARE_CLASS_SPEC_CONST_HANDLERdo_bind_class() を呼び出す

static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DECLARE_CLASS_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
	USE_OPLINE

	SAVE_OPLINE();
	do_bind_class(RT_CONSTANT(opline, opline->op1), (opline->op2_type == IS_CONST) ? Z_STR_P(RT_CONSTANT(opline, opline->op2)) : NULL);
	ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}

do_bind_class()zend_bind_class_in_slot() を呼び出す
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L1164:L1164

zend_bind_class_in_slot()zend_do_link_class() を呼び出す
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L1131:L1131

zend_do_link_class()zend_do_implement_interfaces() を呼び出す
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_inheritance.c#L2846:L2846

zend_do_implement_interfaces()do_implement_interface() を呼び出す
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_inheritance.c#L1767:L1767

do_implement_interface()do_interface_implementation() を呼び出す
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_inheritance.c#L1772

do_inherit_iface_constant()do_inherit_method() で継承
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_inheritance.c#L1650:L1650

zend_do_inherit_interfaces() ではimplements先のインターフェースが継承しているインターフェースを追加する
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_inheritance.c#L1666:L1666
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_inheritance.c#L1310-L1320

例えば、C1 => I1 => I2 => I3 という感じでimplementsしている場合、ce->interfaces にはI2はI3, I1はI2+I3が入る。 C1では自身がimplementsしているI1とI1のインターフェースの ce->interfaces が入るのでI1,I2,I3がセットされることになる。

interfaceのメソッドを実装していないときのハンドリングはabstractと同じ仕組み

trait

traitも zend_compile_class_decl() によって zend_class_entry にコンパイルされる。

useした場合は zend_do_bind_traits() が呼ばれる
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_inheritance.c#L2830-L2832

zend_do_bind_traits() ではmethodやpropertyのバインディング(クラスへのコピー)を行う。 メソッドは zend_do_traits_method_binding() でuseしたtraitの数だけ zend_traits_copy_functions() が呼ばれる
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_inheritance.c#L2123-L2129

traitのmethodをコピー
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_inheritance.c#L1901
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_inheritance.c#L1922

zend_hash_update_ptrce->function_table のハッシュテーブルを更新し、メソッドを追加
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_inheritance.c#L1843-L1844

alias

aliasが入っているとここでmodifierを変更している
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_inheritance.c#L1904-L1920

<?php

trait T
{
  public function call()
  {}
}

class C
{
  use T {
    call as private;
  }
}

別名が入っている場合は↓のルートで名前やmodifierを変更してメソッドを追加している
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_inheritance.c#L1872-L1897

<?php

trait T
{
  public function call()
  {}
}

class C
{
  use T {
    call as public call2;
  }
}

優先度などのチェックはここでやっている
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_inheritance.c#L1959-L2026

insteadofを入れている場合は対象のクラスから除外する関数名を引けるように exclude_tables にセットしている

除外している場合は zend_hash_find(exclude_table, fnname) がNULLじゃなくなるので関数がコピーされない。これによってtraitの関数の優先度を制御している
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_inheritance.c#L2107-L2121
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_inheritance.c#L1899:L1899

このエントリーをはてなブックマークに追加