Почему атрибуты PHP не позволяют функции?
Я довольно новичок в 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 ответов
Код компилятора предполагает, что это сделано специально, хотя я не знаю, какова официальная причина этого. Я также не уверен, сколько усилий потребуется, чтобы надежно реализовать эту функциональность, но есть определенные ограничения в том, как все это делается в настоящее время.
Хотя мои знания компилятора 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();
Хотя компилятор многое делает в этих различных методах, важно отметить несколько вещей.
- Вызов
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 немного запутанно). Аналогично, нецелесообразно пытаться оценить эти дополнительные коды операций в строке (вы не можете быть уверены, что данная функция или класс уже решены), поэтому нет способа избежать генерации кодов операций.
Потенциальным решением было бы создать новый массив кодов операций, который был ограничен объявлением переменной класса, подобно тому, как обрабатываются определения методов. Проблема в том, чтобы решить, когда оценивать такую однократную последовательность. Будет ли это сделано при загрузке файла, содержащего класс, при первом обращении к свойству или при создании объекта этого типа?
Как вы указали, другие динамические языки нашли способ справиться с этим сценарием, поэтому немаловажно принять это решение и заставить его работать. Однако, насколько я могу судить, в случае с PHP это не было бы однострочным исправлением, и разработчики языка, похоже, решили, что на данном этапе это не стоит того.
Мой вопрос: почему?! Это "особенность" или небрежная реализация?
Я бы сказал, что это определенно особенность. Определение класса является планом кода и не должно выполнять код во время определения. Это нарушило бы абстракцию объекта и инкапсуляцию.
Однако это только мое мнение. Я не могу точно сказать, какая идея была у разработчиков при определении этого.
Вы, вероятно, можете достичь чего-то похожего на это:
class Foo
{
public $path = __DIR__;
}
IIRC __DIR__
нужен php 5.3+, __FILE__
был вокруг дольше
Это небрежная реализация парсера. У меня нет правильной терминологии для ее описания (я думаю, что термин "бета-сокращение" как-то подходит...), но синтаксический анализатор языка PHP является более сложным и сложным, чем нужно, и поэтому все виды специальный корпус необходим для различных языковых конструкций.
Я думаю, что вы не сможете получить правильную трассировку стека, если ошибка не возникает в исполняемой строке... Так как не может быть никакой ошибки при инициализации значений с константами, с этим нет проблем, но функция может выдавать исключения / ошибки и должна вызываться внутри исполняемой строки, а не декларативной.