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

Область видимости переменной - это контекст, в котором эта переменная определена. В большинстве случаев все переменные 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 объявляется как static:

Пример #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() во второй раз.