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

Область видимости переменной — это контекст, в котором определили переменную. По большей части у всех переменных PHP только одна область видимости. Эта единая область видимости охватывает также файлы, которые включили в скрипт выражениями include и require. Например:

<?php

$a
= 1;
include
'b.inc';

?>

Здесь переменная $a будет доступна внутри включённого скрипта b.inc. Однако определение (тело) пользовательской функции задаёт локальную область видимости данной функции. Переменная внутри функции по умолчанию ограничена локальной областью видимости функции. Например:

<?php

$a
= 1; /* Глобальная область видимости */

function test()
{
echo
$a; /* Ссылка на переменную в локальной области видимости */
}

test();

?>

Этот скрипт сгенерирует неопределенную переменную E_WARNING (или E_NOTICE до PHP 8.0.0) для диагностики. Однако если в настройках INI-директивы display_errors установили скрытие такой диагностики, то ничего выводиться не будет. Это связано с тем, что языковая конструкция echo указывает на локальную версию переменной $a, а в пределах этой области видимости переменной не присвоили значение. Можно заметить, что поведение немного отличается от языка C в том, что глобальные переменные в C автоматически доступны функциям, если только глобальную переменную не перезаписали локальным определением. Это может вызвать некоторые проблемы, поскольку люди могут нечаянно изменить глобальную переменную. В PHP глобальные переменные требуется объявлять глобальными внутри функции, если функция будет использовать эти переменные.

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

Сначала пример ключевого слова global:

Пример #1 Использование global

<?php

$a
= 1;
$b = 2;

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

$b = $a + $b;
}

Sum();
echo
$b;

?>

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

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

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

<?php

$a
= 1;
$b = 2;

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

Sum();
echo
$b;

?>

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

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

<?php

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

?>

Замечание:

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

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

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

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

<?php

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

?>

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

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

<?php

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

?>

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

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

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

<?php

function test()
{
static
$count = 0;

$count++;
echo
$count;

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

$count--;
}

?>

Статическим переменным разрешается присваивать значения в виде константных выражений, но динамические выражения наподобие вызова функции вызовут ошибку разбора.

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

<?php

function foo() {
static
$int = 0; // Правильно
static $int = 1 + 2; // Правильно
static $int = sqrt(121); // Неправильно, поскольку это функция

$int++;
echo
$int;
}

?>

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

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

<?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() во второй раз.