Почему атрибуты PHP не позволяют функции? - PullRequest
41 голосов
/ 18 октября 2010

Я довольно новичок в PHP, но я программирую на похожих языках уже много лет.Я был сбит с толку следующим:

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

Произошла синтаксическая ошибка: Parse error: syntax error, unexpected '(', expecting ')' in test.php on line 5, то есть вызов realpath.

Но это прекрасно работает:

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

После того, как я некоторое время ударился об это, мне сказали, что нельзя вызывать функции в атрибуте по умолчанию;Вы должны сделать это в __construct.Мой вопрос: почему ?!Это "особенность" или небрежная реализация?Каково обоснование?

Ответы [ 5 ]

51 голосов
/ 23 октября 2010

Код компилятора предполагает, что это сделано специально, хотя я не знаю, какова официальная причина этого. Я также не уверен, сколько усилий потребуется, чтобы надежно реализовать эту функцию, но есть определенные ограничения в том, как все в настоящее время делается.

Хотя мои знания компилятора PHP невелики, я попытаюсь проиллюстрировать то, что, по моему мнению, происходит, чтобы вы могли увидеть, где есть проблема. Ваш пример кода является хорошим кандидатом для этого процесса, поэтому мы будем использовать его:

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

Как вы хорошо знаете, это вызывает синтаксическую ошибку. Это результат PHP грамматики , которая дает следующее соответствующее определение:

class_variable_declaration: 
      //...
      | T_VARIABLE '=' static_scalar //...
;

Таким образом, при определении значений переменных, таких как $path, ожидаемое значение должно соответствовать определению статического скаляра. Неудивительно, что это несколько ошибочно, учитывая, что определение статического скаляра также включает типы массивов, значения которых также являются статическими скалярами:

static_scalar: /* compile-time evaluated scalars */
      //...
      | T_ARRAY '(' static_array_pair_list ')' // ...
      //...
;

Давайте на секунду предположим, что грамматика была другой, и отмеченная строка в правиле delcaration переменной класса выглядела примерно так, как показано в следующем примере кода (несмотря на нарушение допустимых в противном случае назначений):

class_variable_declaration: 
      //...
      | T_VARIABLE '=' T_ARRAY '(' array_pair_list ')' // ...
;

После перекомпиляции PHP образец сценария больше не будет работать с этой синтаксической ошибкой. Вместо этого произойдет сбой с ошибкой времени компиляции «Недопустимый тип привязки» . Поскольку код теперь действителен на основе грамматики, это указывает на то, что в конструкции компилятора есть что-то конкретное, что вызывает проблемы. Чтобы выяснить, что это такое, давайте на мгновение вернемся к исходной грамматике и представим, что в примере кода было допустимое присвоение $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

Обратите внимание, что в последнем случае, если тип кода операции не является одним из ожидаемых типов, выдается ошибка & ndash; Ошибка «Неверный тип привязки» . Исходя из этого, мы можем сказать, что при назначении нестатических значений каким-то образом последний код операции будет отличаться от ожидаемого. Итак, что происходит, когда мы используем нестатический массив с измененной грамматикой?

Вместо вызова 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 немного запутанно). Аналогично, нецелесообразно пытаться оценить эти дополнительные коды операций в строке (вы не можете быть уверены, что данная функция или класс уже разрешены), поэтому нет способа избежать генерации кодов операций.

Потенциальным решением было бы создать новый массив кодов операций, который был ограничен объявлением переменной класса, подобно тому, как обрабатываются определения методов.Проблема в том, чтобы решить, когда оценивать такую ​​однократную последовательность.Будет ли это сделано при загрузке файла, содержащего класс, при первом обращении к свойству или при создании объекта этого типа?

Как вы указали, другие динамические языки нашли способчтобы справиться с этим сценарием, так что это не невозможно принять это решение и заставить его работать.Однако, насколько я могу судить, в случае с PHP это не было бы однострочным исправлением, и разработчики языка, похоже, решили, что на данном этапе это не стоит того.

20 голосов
/ 18 октября 2010

Мой вопрос: почему ?!Это "особенность" или небрежная реализация?

Я бы сказал, что это определенно особенность.Определение класса является планом кода и не должно выполнять код во время определения.Это нарушило бы абстракцию и инкапсуляцию объекта.

Однако это только мой взгляд.Я не могу точно сказать, какая идея была у разработчиков при определении этого.

6 голосов
/ 18 октября 2010

Вы, вероятно, можете добиться чего-то похожего на это:

class Foo
{
    public $path = __DIR__;
}

IIRC __DIR__ нуждается в php 5.3+, __FILE__ был дольше

5 голосов
/ 18 октября 2010

Это небрежная реализация парсера. У меня нет правильной терминологии для ее описания (я думаю, что термин «бета-сокращение» как-то подходит ...), но синтаксический анализатор языка PHP является более сложным и сложным, чем нужно, и поэтому специальный корпус необходим для различных языковых конструкций.

2 голосов
/ 18 октября 2010

Полагаю, вы не сможете получить правильную трассировку стека, если ошибка не возникает в исполняемой строке ... Поскольку при инициализации значений с константами не может быть никаких ошибок, проблем нет с этим, но функция может генерировать исключения / ошибки и должна вызываться в пределах исполняемой строки, а не декларативной.

...