Область видимости переменной

Область видимости переменной — контекст, в котором определили переменную. В PHP предусмотрели область видимости функции и глобальную область видимости. Переменная, которую определили за пределами функции, ограничивается глобальной областью видимости. Код включаемого файла наследует ту же область видимости переменных, что и строка, на которой включается файл.

Пример #1 Пример глобальной области видимости переменных

<?php

$a
= 1;
include
'b.inc'; // Код внутри файла b.inc получит доступ к переменной $a

?>

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

Пример #2 Пример локальной области видимости переменных

<?php

$a
= 1; // Определяем переменную $a в глобальной области видимости

function test()
{
echo
$a; // Переменная $a не определена, поскольку конструкция echo указывает на локальную версию переменной $a
}

?>

До PHP 8.0.0 при вызове функции приведённый пример сгенерирует ошибку уровня E_NOTICE о неопределённой переменной, а в новых версиях ошибку уровня E_WARNING. Причина ошибки состоит в том, что языковая конструкция echo указывает на локальную версию переменной $a, а переменной не присвоили значение в локальной области видимости. Обратите внимание, поведение переменных отличается от языка C в том, что глобальные переменные в C автоматически доступны функциям, если только глобальную переменную не перезаписали локальным определением. Несовпадение поведения иногда вызывает проблемы при непреднамеренном изменении глобальной переменной. В PHP глобальные переменные потребуется объявить глобальными внутри функции, если функция будет использовать эти переменные.

Ключевое слово global

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

Пример #3 Пример поведения ключевого слова global

<?php

$a
= 1;
$b = 2;

function
Sum()
{
global
$a, $b;

$b = $a + $b;
}

Sum();
echo
$b;

?>

Результат выполнения приведённого примера:

3

При объявлении переменных $a и $b глобальными внутри функции ссылки на эти переменные будут указывать на глобальную версию. Количество глобальных переменных, которыми умеет манипулировать функция, не ограничивается.

Второй способ доступа к переменным глобальной области видимости — суперглобальный PHP-массив $GLOBALS. Перепишем предыдущий пример так:

Пример #4 Работа с суперглобальной переменной $GLOBALS вместо ключевого слова global

<?php

$a
= 1;
$b = 2;

function
Sum()
{
$GLOBALS['b'] = $GLOBALS['a'] + $GLOBALS['b'];
}

Sum();
echo
$b;

?>

Массив $GLOBALS — ассоциативный массив, в котором ключ представляет название глобальной переменной, а значение элемента массива — содержимое переменной. Обратите внимание, суперглобальная переменная $GLOBALS существует в любой области видимости, причина состоит в том, что $GLOBALSсуперглобальная переменная. Пример ниже показывает силу суперглобальных переменных:

Пример #5 Суперглобальные переменные и область видимости

<?php

function test_superglobal()
{
echo
$_POST['name'];
}

?>

Замечание: Не будет ошибкой, если указать ключевое слово global вне функции, например в файле, который включается внутри функции другого файла.

Переменные, которые определили через ключевое слово static

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

Пример #6 Пример показывает, когда требуется статическая переменная

<?php

function test()
{
$a = 0;
echo
$a;
$a++;
}

?>

Эта функция бесполезна, поскольку при каждом вызове устанавливает для переменной $a значение 0 и выводит 0. Инкремент переменной $a++ здесь не играет роли, поскольку при выходе из функции переменная $a исчезает. Чтобы написать полезную функцию подсчёта, которая не потеряет текущего значения счётчика, переменную $a объявляют статической:

Пример #7 Пример со статической переменной

<?php

function test()
{
static
$a = 0;
echo
$a;
$a++;
}

?>

Теперь функция проинициализирует переменную $a только при первом вызове, а при каждом вызове функция test() будет выводить значение переменной $a, а затем инкрементировать значение.

Со статическими переменными также работают в рекурсивных функциях. Следующая функция рекурсивно считает до 10, а статическая переменная $count помогает определить момент остановки:

Пример #8 Статические переменные и рекурсивные функции

<?php

function test()
{
static
$count = 0;

$count++;
echo
$count;

if (
$count < 10) {
test();
}

$count--;
}

?>

До PHP 8.3.0 статические переменные разрешалось инициализировать только константными выражениями. С PHP 8.3.0 также разрешили динамические выражения наподобие вызовов функций:

Пример #9 Объявление статических переменных

<?php

function foo()
{
static
$int = 0; // Правильно
static $int = 1 + 2; // Правильно
static $int = sqrt(121); // Правильно с PHP 8.3.0

$int++;
echo
$int;
}

?>

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

Пример #10 Статические переменные в анонимных функциях

<?php

function exampleFunction($input)
{
$result = (static function () use ($input) {
static
$counter = 0;
$counter++;
return
"Функция получила значение: $input, счётчик: $counter\n";
});

return
$result();
}

// Вызов функции exampleFunction пересоздаст анонимную функцию, поэтому статическая
// переменная не сохранит значение
echo exampleFunction('A'); // Выводит: Функция получила значение: A, счётчик: 1
echo exampleFunction('B'); // Выводит: Функция получила значение: B, счётчик: 1

?>

Начиная с PHP 8.1.0 при наследовании, но не переопределении, метода со статическими переменными унаследованный метод будет использовать статические переменные совместно с родительским методом. Поэтому статические переменные в методах теперь ведут себя как статические свойства класса.

Начиная PHP 8.3.0 статические переменные разрешили инициализировать произвольными выражениями. Поэтому статическую переменную получится инициализировать, например, вызовом метода.

Пример #11 Статические переменные в унаследованных методах

<?php

class Foo
{
public static function
counter()
{
static
$counter = 0;
$counter++;
return
$counter;
}
}

class
Bar extends Foo {}

var_dump(Foo::counter()); // int(1)
var_dump(Foo::counter()); // int(2)
var_dump(Bar::counter()); // int(3), до PHP 8.1.0 int(1)
var_dump(Bar::counter()); // int(4), до PHP 8.1.0 int(2)

?>

Ссылки с глобальными (global) и статическими (static) переменными

PHP использует модификаторы переменных static и global как ссылки. Например, реальная глобальная переменная, которую внедрили в область видимости функции через ключевое слово global, в действительности создаёт ссылку на глобальную переменную. Это приводит к неожиданному поведению, как показывает следующий пример:

<?php

function test_global_ref()
{
global
$obj;
$new = new stdClass();
$obj = &$new;
}

function
test_global_noref()
{
global
$obj;
$new = new stdClass();
$obj = $new;
}

test_global_ref();
var_dump($obj);
test_global_noref();
var_dump($obj);

?>

Результат выполнения приведённого примера:

NULL
object(stdClass)#1 (0) {
}

Аналогично ведёт себя и инструкция static. Ссылки не хранятся статично:

<?php

function &get_instance_ref()
{
static
$obj;

echo
'Статический объект: ';
var_dump($obj);

if (!isset(
$obj)) {
$new = new stdClass();

// Присвоить ссылку статической переменной
$obj = &$new;
}

if (!isset(
$obj->property)) {
$obj->property = 1;
} else {
$obj->property++;
}

return
$obj;
}

function &
get_instance_noref()
{
static
$obj;

echo
'Статический объект: ';

var_dump($obj);

if (!isset(
$obj)) {
$new = new stdClass();

// Присвоить объект статической переменной
$obj = $new;
}

if (!isset(
$obj->property)) {
$obj->property = 1;
} else {
$obj->property++;
}

return
$obj;
}

$obj1 = get_instance_ref();
$still_obj1 = get_instance_ref();
echo
"\n";
$obj2 = get_instance_noref();
$still_obj2 = get_instance_noref();

?>

Результат выполнения приведённого примера:

Статический объект: NULL
Статический объект: NULL

Статический объект: NULL
Статический объект: object(stdClass)#3 (1) {
  ["property"]=>
  int(1)
}

Пример показывает, что при назначении ссылки статической переменной эта переменная не запоминается, при повторном вызове функции &get_instance_ref().