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

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

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

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

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

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

<?php

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

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

test();

До 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 . PHP_EOL;
    $a++;
}

test();
test();
test();

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

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

<?php

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

test();
test();
test();

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

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

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

<?php

function test()
{
    static $count = 0;

    $count++;
    echo $count . PHP_EOL;

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

    $count--;
}

test();

До 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().