42

私はPHPにかなり慣れていませんが、何年も同じような言語でプログラミングを行っています。私は次のことに困惑しました:

class Foo {
    public $path = array(
        realpath(".")
    );
}

構文エラーが発生しました:Parse error: syntax error, unexpected '(', expecting ')' in test.php on line 5これがrealpath呼び出しです。

しかし、これは正常に機能します。

$path = array(
    realpath(".")
);

しばらくこれに頭をぶつけた後、属性のデフォルトで関数を呼び出すことはできないと言われました。あなたはでそれをしなければなりません__construct。私の質問は:なぜですか?!これは「機能」ですか、それともずさんな実装ですか?理論的根拠は何ですか?

4

5 に答える 5

54

コンパイラ コードは、これが設計によるものであることを示唆していますが、その背後にある公式の理由はわかりません。また、この機能を確実に実装するためにどれだけの労力がかかるかはわかりませんが、現在行われている方法には間違いなくいくつかの制限があります.

私の 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();

コンパイラはこれらのさまざまなメソッドで多くのことを行いますが、いくつかの点に注意することが重要です。

  1. を呼び出すとzend_do_begin_class_declaration()、 が呼び出されget_next_op()ます。これは、現在のオペコード配列に新しいオペコードを追加することを意味します。
  2. array_init()新しいオペコードを生成しzend_do_add_static_array_element()ません。代わりに、配列がすぐに作成され、現在のクラスのプロパティ テーブルに追加されます。メソッド宣言は、 の特別なケースを介して、同様の方法で機能しzend_do_begin_function_declaration()ます。
  3. 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 行の修正ではなく、言語の設計者は、この時点でそれを含める価値はないと判断したようです。

于 2010-10-22T20:04:38.683 に答える
23

私の質問は: なぜ?! これは「機能」またはずさんな実装ですか?

特徴と言っていいでしょう。クラス定義はコードの設計図であり、定義時にコードを実行することは想定されていません。オブジェクトの抽象化とカプセル化が壊れます。

ただし、これはあくまでも私の見解です。これを定義するときに開発者がどのような考えを持っていたかは、はっきりとは言えません。

于 2010-10-18T14:50:37.630 に答える
7

あなたはおそらくこのような何かを達成することができます:

class Foo
{
    public $path = __DIR__;
}

IIRCにはphp5.3以降__DIR__が必要です。__FILE__

于 2010-10-18T14:57:18.750 に答える
5

これはずさんなパーサーの実装です。私はそれを説明する正しい用語を持っていません (「ベータ削減」という用語は何とか当てはまると思います...) が、PHP 言語パーサーは必要以上に複雑で複雑なので、あらゆる種類の異なる言語構造には特別なケースが必要です。

于 2010-10-18T14:51:36.397 に答える
2

私の推測では、実行可能な行でエラーが発生しない場合、正しいスタック トレースを取得できないと思います...定数で値を初期化することでエラーが発生することはあり得ないため、問題はありません。ただし、関数例外/エラーをスローする可能性があり、宣言的な行ではなく、実行可能な行内で呼び出す必要があります。

于 2010-10-18T14:52:46.440 に答える