PHP - допустимые имена переменных

В руководстве PHP по переменным мы можем прочитать:

Переменные имена соответствуют тем же правилам, что и другие метки в PHP. Правильное имя переменной начинается с буквы или подчеркивания, за которой следует любое количество букв, цифр или символов подчеркивания. В качестве регулярного выражения оно выражается следующим образом: "[a-zA-Z_\x7f-\xff] [a-zA-Z0-9_\x7f-\xff] * '

Так очевидно, когда мы пытаемся запустить:

$0-a = 5;
echo $0-a;

мы получим ошибку Parse. Это совершенно очевидно.

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

${'0-a'} = 5;
echo ${'0-a'};

он работает без проблем.

Также используя переменные переменные, такие как:

$variable = '0-a';
$$variable = 5;
echo $$variable;

работает без проблем.

Итак, вопрос в том, что это предложение, которое я цитирую в руководстве, неверно, или, может быть, это то, что я показал, не является реальной переменной или, возможно, документировано где-то еще в руководстве PHP?

Я проверил его - и, похоже, он работает как в PHP 5.6, так и в 7.1

Также возникает вопрос: безопасно ли использовать такие конструкции? Основываясь на руководстве, кажется, что это не должно быть возможным.

Ответ 1

Вы можете буквально выбрать любое имя для переменной. "i" и "foo" - очевидные варианты, но "", "\n" и "foo.bar" также допустимы. Причина? Таблица символов PHP - это просто словарь: строковый ключ, содержащий ноль или более байтов, отображается в структурированном значении (называемом zval). Интересно, что есть два способа доступа к этой таблице символов: лексические переменные и динамические переменные.

Лексические переменные - это то, о чем вы читаете в документации по "переменным". Лексические переменные определяют ключ таблицы символов во время компиляции (то есть, когда движок выполняет лексинг и анализ кода). Чтобы этот лексер был простым, лексические переменные начинаются с [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]* $ и должны соответствовать регулярному выражению [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*. Сохраняя его простым, этот способ означает, что парсер не должен выяснять, например, является ли $foo.bar переменной с ключом "foo.bar" или переменной строкой "foo" объединенной с константой bar.

Теперь динамические переменные - то, где это становится интересным. Динамические переменные позволяют получить доступ к тем более необычным именам переменных. PHP вызывает эти переменные переменные. (Мне не нравится это имя, поскольку их противоположность - логически "постоянная переменная", что сбивает с толку. Но я буду называть их переменными здесь.) Основное использование выглядит так:

$a = 'b';
$b = 'SURPRISE!';
var_dump($$a, ${$a}); // both emit a surprise

Переменные переменные анализируются иначе, чем лексические переменные. Вместо определения ключа таблицы символов во время лексирования ключ таблицы символов оценивается во время выполнения. Логика выглядит следующим образом: лексер PHP видит синтаксис переменной переменной ($$a или, в более общем случае, ${expression}), синтаксический анализатор PHP откладывает вычисление выражения до момента выполнения, а затем во время выполнения движок использует результат выражения для ввода в таблицу символов. Это немного больше работы, чем лексические переменные, но гораздо более мощный.

Внутри ${} вы можете иметь выражение, которое оценивает любую последовательность байтов. Пустая строка, нулевой байт, все это. Все идет. Это удобно, например, в heredocs. Это также удобно для доступа к удаленным переменным как переменным PHP. Например, JSON допускает любой символ в имени ключа, и вам может потребоваться доступ к ним как к прямым переменным (а не к элементам массива):

$decoded = json_decode('{ "foo.bar" : 1 }');
foreach ($decoded as $key => $value) {
    ${$key} = $value;
}
var_dump(${'foo.bar'});

Таким образом, использование переменных переменных аналогично использованию массива в качестве "таблицы символов", например, $array['foo.bar'], но подход с использованием переменных переменных вполне приемлем и немного быстрее.


добавление

Под "немного быстрее" мы говорим так далеко справа от десятичной запятой, что они практически неразличимы. Только в 10 ^ 8 символических доступах разница превышает 1 секунду в моих тестах.

Set array key: 0.000000119529
Set var-var:   0.000000101196
Increment array key: 0.000000159856
Increment var-var:   0.000000136778

Потеря ясности и условности, вероятно, не стоит этого.

$N = 100000000;

$elapsed = -microtime(true);
$syms = [];
for ($i = 0; $i < $N; $i++) { $syms['foo.bar'] = 1; }
printf("Set array key: %.12f\n", ($elapsed + microtime(true)) / $N);

$elapsed = -microtime(true);
for ($i = 0; $i < $N; $i++) { ${'foo.bar'} = 1; }
printf("Set var-var:   %.12f\n", ($elapsed + microtime(true)) / $N);

$elapsed = -microtime(true);
$syms['foo.bar'] = 1;
for ($i = 0; $i < $N; $i++) { $syms['foo.bar']++; }
printf("Increment array key: %.12f\n", ($elapsed + microtime(true)) / $N);

$elapsed = -microtime(true);
${'foo.bar'} = 1;
for ($i = 0; $i < $N; $i++) { ${'foo.bar'}++; }
printf("Increment var-var:   %.12f\n", ($elapsed + microtime(true)) / $N);

Ответ 2

Я видел на php.net (испанская версия) такие вещи, как $ función, $ año * и так далее, всегда хотел попробовать, но никогда не делал. Однако по какой-то причине я написал некоторые переменные, такие как $ 1a ($ 1st), $ 2a, $ 3a... И это сработало, не нужно $ {1a} или что-то в этом роде, но Phpstorm мучает меня такими предупреждениями, как " Ожидается: точка с запятой "и" Выражение не присваивается: постоянная ссылка ". Итак, после прочтения вашего опыта в этой статье, чтобы очистить мой редактор от предупреждений и избежать возможных проблем в будущем, я просто изменил все на $ a, $ b, $ c и т.д. Примечание: $ ano (year) на испанском языке - это anus так что никто не любит его использовать