О сайтах и не о сайтах

Теперь и в tg!

tg

Переехал с твиттера на t.me/tulvit_blog, если что.

PHP: Переменные переменных

Все еще, стало быть, пытаюсь осилить главу "Language Basics" книжечки "Programming PHP" от O'Reilly.

Дошел до "Variable Variables". Всего два предложения на тему:

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

$var = "lol";
$$var = "kek";
echo $lol; // outputs "kek"

За сим все, едем дальше.

И вот такое "раскрытие" темы встречаю в каждой первой книге по PHP (штуки три, все-таки, ранее прочитал, было это, правда, лет пять-семь назад). Да что там в книгах, даже в официальной документации по PHP пишут практически то же самое.

Мне же никогда не нравилось ни название "переменные переменных", ни эта запись "$$". Сложно, непонятно, да и просто криво как-то.

Почему нельзя заместо "variable variables" использовать какое-нибудь другое определение, что-то вроде "динамическое именование переменных". И до кучи применить т. н. complex (curly) syntax. Тогда все встает на свои места:

В PHP имена переменных можно назначать динамически,

$var = "lol";
${$var} = "kek";
echo $lol; // outputs "kek"

Логично и понятно, создали переменную, подставив в качестве ее имени строку, которая хранилась в другой переменной. Никаких там $$ и переменных переменных переменных.

Вообще, что такое $. Это shorthand от ${}. Который, в свою очередь, является оператором. Который говорит следующее: "вернуть значение переменной, чье имя - следующая строка".

Строка в значении строка. Буквальном. Т. е. без всего этого "название переменной должно начинаться с a-Z или подчеркивания". Название переменной может быть хоть "123lol". Или даже "1 ^_^ !!!". Никакой суть разницы.

Объявим четыре переменные,

$var = 'foo';
${'my'} = 'foo';
${'123lol'} = 'foo';
${'1 ^_^ !!!'} = 'foo';

И задампим эти объявленные переменные с помощью get_defined_vars(), где увидим следующее:

'var' => string(3) "foo"
'my' => string(3) "foo"
'123lol' => string(3) "foo"
'1 ^_^ !!!' => string(3) "foo"

Т. е. да, никакой разницы, название переменной - любая строка. А $my - это ровно то же самое, что и ${'my'}.

Ограничение на используемые символы в названии переменных накладывает не ${}, а именно что сокращенная запись без скобок. Ну просто потому что с переменными работать приходится постоянно, каждый раз писать ${'i'} замучаешься, а $i вроде сподручней будет. Откуда и возникают уже ограничения. Почему нельзя использовать пробел - понятно, парсер споткнется. А вот все остальное (нельзя начинать с цифры, использовать спец. символы) - лишь дань общепринятым стандартам в именовании переменных. Т. е. в других языках эти ограничения взялись не с потолка (например, где нет префиксов перед переменной, запрет на цифирные имена совершенно очевиден, т. к. в противном случае не поймешь, 123 - это переменная или integer), а вот в пхп просто чтобы было "как у всех", технических ограничений никаких, благо $123 с 123 не спутаешь. Но в целом все сделали правильно, единообразие оно всегда хорошо.

Другими словами, используем полную запись ${} - имя переменной любая строка. Используем просто $ - уже надо укладываться в ограничения на символы.

Т. е. мы можем объявить переменную, как ${'i'}, и далее в коде обращаться к ней, как к $i. А можем объявить переменную, как ${':3'}, что вполне себе валидное имя, но обращаться к ней через просто символ доллара - уже нет, не можем.

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

$var = 'lol';
${$var} = 'foo'; // just the same as $lol = 'foo';
${'some'} = 'foo'; // $some = foo;
${$var . '_some'} = 'foo'; // $lol_some = 'foo';
${date('M')} = 'foo'; // $Dec = 'foo';

Т. е. "переменные переменных" - это лишь частный случай динамического создание имен переменных.

Где сие может понадобится?

Предположим, у нас есть два массива.

$cars = array();
$books = array();

И надо вывести первый или второй, в зависимости от GET параметра, который может быть либо q=cars, либо q=books.

Ничего сложного и без динамических переменных,

$cars = array();
$books = array();

if (!empty($_GET['q'])) {
switch ($_GET['q']) {
case 'cars':
// output $cars
break;
case 'books';
// output $books
break;
}
}

Ну т. е. либо свитч, либо нагромождение ифов. А если этих массивов не два, а сто? Не писать же сто же условий? И динамические имена переменных вполне себе решение в данном случае:

$cars = array();
$books = array();

if (!empty($_GET['q'])) {
// output ${$_GET['q']};
}

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

Или вот еще пример. Есть страничка с несколькими GET параметрами, index.php?user=nick&id=123&page=2. В коде это надо как-то распарсить, и под каждый аргумент создать свою переменную, чтобы дальше можно было оперировать $user, $id и $page. Можно так:

if (!empty($_GET['user'])) $user = $_GET['user'];
if (!empty($_GET['id'])) $id = $_GET['id'];
if (!empty($_GET['page'])) $page = $_GET['page'];

А можно через динамическое создание переменных:

foreach(array('user', 'id', 'page') as $value) {
if (!empty($_GET[$value])) ${$value} = $_GET[$value];
}

И там и там три строчки, но если переменных надо создать уже несколько десятков, то разница становится очевидна.

Хотя, конечно же, в этих примерах прекрасно можно было бы обойтись и без динамических переменных, а использовать те же ассоциативные массивы. Тут уж кому как удобней, так-то. Лично я при прочих равных выберу именно ассоциативные массивы.

Комментарии

мне они пригождались разве что для обращения к свойсвам объектов