function test(): void {
$array = [1, 2, 3];
foreach($array as &$number) {
echo $number . ' ';
}
echo PHP_EOL;
foreach($array as $number) {
echo $number . ' ';
}
}
test();
a)
1 2 3
1 2 3
b)
1 2 3
1 2 2
c)
1 2 3
3 3 3
d) Нічого, відбудеться помилка
Розгорнути правильну відповідь з поясненням
Помилок в коді немає, отже варіант d відпадає. Запустивши програму ми побачимо, що правильна відповідь - b.
1 2 3
1 2 2
Давайте розберемося, чому так відбувається.
В основі такої поведінки лежать дві особливості PHP: область видимості змінних і посилання.
У PHP поділяють дві області видимості змінних: локальну і глобальну. Кожна функція створює свою область видимості. Однак локальну область видимості створюють тільки функції - цикли і оператори розгалуження не створюють свої області видимості, і тому змінні, які оголошуються всередині циклу, доступні і поза ним. Розглянемо наступний приклад:
$array = [1, 2, 3];
foreach($array as $number){
echo $number . ' ';
}
echo PHP_EOL;
echo $number;
В результаті його виконання буде виведено
1 2 3
3
Тепер ми розуміємо, що змінна $number в першому і другому циклі foreach - одна і та ж сама.
Після цього в гру вступає досить екзотична і рідко використовувана можливість PHP - посилання.
Посилання в PHP дозволяють звертатися до однієї і тієї ж змінної під різними іменами, наприклад
$a = 42;
$b = &$a;
$b = 21;
echo $a . ' ' . $b;
//prints 21 21
Тепер, коли у нас є всі шматочки пазла, зберемо все воєдино.
-
Під час першої ітерації створюється змінна $number, якої ще немає в області видимості, в яку за посиланням записуються елементи з масиву $array, тобто на першій ітерації $number вказує на перший елемент [1, 2, 3], на другій ітерації на другий елемент - [1, 2, 3], на третій елемент на третій ітерації відповідно - [1, 2, 3].
-
Після того, як цикл закінчився, змінна $number знаходиться в області видимості функції і вказує на останній елемент масиву: [1, 2,3]
-
Починається виконання другого циклу. Змінна $number вже є в області видимості, тому вона перевикористовується.
-
Коли інтерпретатор PHP виконує foreach, він присвоює значення змінної $number:
на першій ітерації $number = $array[0], отже масив набуває вигляду [1, 2, 1];
на другий ітерації $number = $array [1], отже масив набуває вигляду [1, 2, 2];
на третій ітерації $ number = $ array [2], отже масив не змінюється [1, 2, 2];
Така поведінка є дуже не очевидною? і через це може з'являтися безліч багів. Уникайте використання посилань в своєму коді і намагайтеся не використовувати змінні, які ви створюєте в циклах і операторах розгалуження на більш високих рівнях вкладеності.
Follow up
Що виведе даний код:
$arr = [1, 2, 3];
function test(array $array): void{
foreach($array as &$number){
echo $number . ' ';
}
echo PHP_EOL;
foreach($array as $number){
echo $number . ' ';
}
echo PHP_EOL;
}
test($arr);
foreach($arr as $num){
echo $num . ' ';
}
Якщо ви відповіли
1 2 3
1 2 2
1 2 2
то ви не вгадали. Правильна відповідь:
1 2 3
1 2 2
1 2 3
Це відбувається через ще одну особливість PHP, яку ми вже розглядали в першому питанні марафону: при зміні масиву всередині функції, масив копіюється (тобто відбувається copy-on-write), і оригінальний масив не змінюється.
Більш докладно прочитати про посилання і області видимості змінних можна тут: