コンパイラ コードは、これが設計によるものであることを示唆していますが、その背後にある公式の理由はわかりません。また、この機能を確実に実装するためにどれだけの労力がかかるかはわかりませんが、現在行われている方法には間違いなくいくつかの制限があります.
私の PHP コンパイラーに関する知識はそれほど多くありませんが、どこに問題があるのかを理解できるように、何が起こっているのかを説明してみます。あなたのコード サンプルはこのプロセスに適しているので、それを使用します。
class Foo {
public $path = array(
realpath(".")
);
}
ご存じのとおり、これにより構文エラーが発生します。これはPHP grammarの結果であり、次の関連定義を作成します。
class_variable_declaration:
//...
| T_VARIABLE '=' static_scalar //...
;
したがって、 などの変数の値を定義する場合$path
、期待される値は静的スカラーの定義と一致する必要があります。当然のことながら、静的スカラーの定義には、値も静的スカラーである配列型も含まれていることを考えると、これは多少の誤称です。
static_scalar: /* compile-time evaluated scalars */
//...
| T_ARRAY '(' static_array_pair_list ')' // ...
//...
;
少しの間、文法が異なっていて、クラス変数の宣言規則の注目された行が、コード サンプルと一致する次のようなものであると仮定しましょう (そうでなければ有効な代入を破っていますが):
class_variable_declaration:
//...
| T_VARIABLE '=' T_ARRAY '(' array_pair_list ')' // ...
;
PHP を再コンパイルした後、サンプル スクリプトはその構文エラーで失敗しなくなりました。代わりに、コンパイル時エラー"Invalid binding type"で失敗します。文法に基づいてコードが有効になったので、これは実際にはコンパイラの設計に問題を引き起こしている特定の何かがあることを示しています。それが何であるかを理解するために、少しの間元の文法に戻って、コード サンプルに の有効な割り当てがあったと想像してみましょう$path = array( 2 );
。
ガイドとして文法を使用すると、このコード サンプルを解析するときにコンパイラ コードで呼び出されるアクションを確認することができます。あまり重要でない部分は省略していますが、プロセスは次のようになります。
// ...
// Begins the class declaration
zend_do_begin_class_declaration(znode, "Foo", znode);
// Set some modifiers on the current znode...
// ...
// Create the array
array_init(znode);
// Add the value we specified
zend_do_add_static_array_element(znode, NULL, 2);
// Declare the property as a member of the class
zend_do_declare_property('$path', znode);
// End the class declaration
zend_do_end_class_declaration(znode, "Foo");
// ...
zend_do_early_binding();
// ...
zend_do_end_compilation();
コンパイラはこれらのさまざまなメソッドで多くのことを行いますが、いくつかの点に注意することが重要です。
- を呼び出すと
zend_do_begin_class_declaration()
、 が呼び出されget_next_op()
ます。これは、現在のオペコード配列に新しいオペコードを追加することを意味します。
array_init()
新しいオペコードを生成しzend_do_add_static_array_element()
ません。代わりに、配列がすぐに作成され、現在のクラスのプロパティ テーブルに追加されます。メソッド宣言は、 の特別なケースを介して、同様の方法で機能しzend_do_begin_function_declaration()
ます。
zend_do_early_binding()
現在のオペコード配列の最後のオペコードを
消費し、NOP に設定する前に次のタイプのいずれかをチェックします。
- ZEND_DECLARE_FUNCTION
- ZEND_DECLARE_CLASS
- ZEND_DECLARE_INHERITED_CLASS
- ZEND_VERIFY_ABSTRACT_CLASS
- ZEND_ADD_INTERFACE
最後のケースで、オペコードの型が予期される型のいずれでもない場合、エラーがスローされることに注意してください – 「無効なバインディング型」エラー。このことから、静的でない値を割り当てると、何らかの形で最後のオペコードが予想外のものになることがわかります。では、修正された文法で非静的配列を使用するとどうなるでしょうか?
を呼び出す代わりにarray_init()
、コンパイラは引数を準備して を呼び出しますzend_do_init_array()
。これget_next_op()
により、新しいINIT_ARRAY オペコードが呼び出されて追加され、次のようになります。
DECLARE_CLASS 'Foo'
SEND_VAL '.'
DO_FCALL 'realpath'
INIT_ARRAY
ここに問題の根源があります。これらのオペコードを追加することでzend_do_early_binding()
、予期しない入力を取得し、例外をスローします。クラスと関数の定義を早期にバインドするプロセスは、PHP のコンパイル プロセスにかなり不可欠であるように思われるため、無視することはできません (ただし、DECLARE_CLASS の生成/消費はややこしいものです)。同様に、これらの追加のオペコードをインラインで試して評価することは実際的ではないため (特定の関数またはクラスがまだ解決されているかどうかを確認することはできません)、オペコードの生成を回避する方法はありません。
考えられる解決策は、メソッド定義が処理される方法と同様に、クラス変数宣言にスコープが設定された新しいオペコード配列を構築することです。これを行う際の問題は、そのような 1 回限りのシーケンスをいつ評価するかを決定することです。クラスを含むファイルがロードされたとき、プロパティが最初にアクセスされたとき、またはそのタイプのオブジェクトが構築されたときに実行されますか?
あなたが指摘したように、他の動的言語はこのシナリオを処理する方法を見つけたので、その決定を下して機能させることは不可能ではありません。私が知る限りでは、PHP の場合は 1 行の修正ではなく、言語の設計者は、この時点でそれを含める価値はないと判断したようです。