Почему атрибуты PHP не позволяют выполнять функции?

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

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

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

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

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

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

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

Хотя мои знания о компиляторе 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

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

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

Мой вопрос: почему ?! Является ли это «особенностью» или небрежной реализацией?

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

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

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

 class Foo { public $path = __DIR__; } 

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

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

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