Ответ 1
Код компилятора предполагает, что это по дизайну, хотя я не знаю, что такое официальная аргументация. Я также не уверен, сколько усилий потребуется для надежного внедрения этой функции, но есть определенные ограничения в том, как это делается в настоящее время.
Хотя мои знания о компиляторе 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 образец script больше не будет терпеть неудачу с этой синтаксической ошибкой. Вместо этого с ошибкой компиляции "Недействительный тип привязки" будет сбой. Поскольку код теперь действителен на основе грамматики, это указывает на то, что в дизайне компилятора действительно есть что-то конкретное, что вызывает проблемы. Чтобы понять, что это такое, пусть на мгновение вернется к исходной грамматике и представьте, что образец кода имел правильное назначение $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()
. Это означает, что он добавляет новый код операции в текущий массив операций opcode. -
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
Обратите внимание, что в последнем случае, если тип кода операции не является одним из ожидаемых типов, возникает ошибка: 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 является довольно грязным). Точно так же нецелесообразно пытаться оценить эти дополнительные коды операций inline (вы не можете быть уверены, что данная функция или класс еще разрешены), поэтому нет способа избежать генерации кодов операций.
Потенциальным решением могло бы стать создание нового массива opcode, который был бы привязан к объявлению переменной класса, подобно тому, как обрабатываются определения методов. Проблема с этим заключается в том, чтобы решить, когда следует оценивать такую последовательность очередей. Будет ли это сделано, когда файл, содержащий класс, будет загружен, когда свойство будет впервые доступно, или когда объект такого типа будет сконструирован?
Как вы уже указали, другие динамические языки нашли способ справиться с этим сценарием, поэтому не просто принять это решение и заставить его работать. Из того, что я могу сказать, сделав это в случае PHP, это не будет однострочное исправление, и разработчики языка, похоже, решили, что в этот момент это не было чем-то стоящим.