Обхват на променливи

Обхватът (scope) на променлива е контекстът, в който тя е дефинирана. На повечето места променливите в PHP имат само един обхват. Този единствен обхват се разпростира също и във включените и в изискваните файлове. Например:

<?php
$a = 1;
include 'b.inc';
?>

Тук променливата $a ще бъде налична във включения скрипт b.inc. В рамките на потребителски-дефинирани функции, обаче, се въвежда локален обхват. Всяка променлива, използвана вътре във функция, по подразбиране е ограничена в локалния обхват на функцията. Например:

<?php
$a = 1; /* глобален обхват */ 

function Test()
{ 
    echo $a; /* референция към променлива от локалния обхват */ 
} 

Test();
?>

Този скрипт няма да изведе нищо, защото изразът echo се отнася за локална версия на променливата $a, а тя няма присвоена стойност в този обхват. Вероятно забелязвате, че това е малко по-различно от езика C с това, че в C променливите са налични автоматично във функциите, освен ако не са отменени от локална дефиниция. Това може да създаде проблеми, тъй като човек може по невнимание да промени глобална променлива. В PHP глобалните променливи трябва да бъдат декларирани като глобални вътре в дадена функция ако ще бъдат използвани в тази функция.

Ключовата дума global

Първо, примерна употреба на global:

Пример 12-1. Употреба на global

<?php
$a = 1;
$b = 2;

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

    $b = $a + $b;
} 

Sum();
echo $b;
?>

Горният скрипт ще изведе "3". Декларирайки $a и $b като глобални за функцията, всички референции към променливите ще сочат към глобалната версия. Няма ограничение в броя на глобалните променливи, които могат да бъдат манипулирани от функция.

Вторият начин за достъп до променливи от глобалния обхват е да се използва специалния PHP-дефиниран $GLOBALS масив. Предният пример може да бъде пренаписан така:

Пример 12-2. Използване на $GLOBALS вместо global

<?php
$a = 1;
$b = 2;

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

Sum();
echo $b;
?>

Масивът $GLOBALS е асоциативен масив, в който името на глобалната променлива е ключът, а съдържанието на тази променлива е стойността на елемента от масива. Забележете че $GLOBALS е наличен във всеки обхват, това е така, защото $GLOBALS е суперглобален. Ето пример, демонстриращ възможностите на суперглобалните:

Пример 12-3. Пример, показващ суперглобални и обхват

<?php
function test_global()
{
    // Повечето предефинирани променливи не са "super" и изискват
    // 'global', за да бъдат налични в локалния обхват на функция.
    global $HTTP_POST_VARS;
    
    echo $HTTP_POST_VARS['name'];
    
    // Суперглобалните са налични във всеки обхват и
    // не изискват 'global'. Суперглобалните са налични
    // от PHP 4.1.0, и сега HTTP_POST_VARS се
    // смята за непрепоръчителен.
    echo $_POST['name'];
}
?>

Използване на статични променливи

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

Пример 12-4. Пример, показващ нуждата от статични променливи

<?php
function Test()
{
    $a = 0;
    echo $a;
    $a++;
}
?>

Тази функция е напълно безполезна, тъй като всеки път, когато бива извикана, тя установява $a в 0 и отпечатва "0". Изразът $a++ , който инкрементира променливата, е безсмислен, понеже в момента, в който функцията излезе, променливата $a изчезва. За да направим полезна брояща функция, която да не губи представа за текущата сума, променливата $a е дефинирана като статична:

Пример 12-5. Примерна употреба на статични променливи

<?php
function Test()
{
    static $a = 0;
    echo $a;
    $a++;
}
?>

Сега, всеки път, когато се извика функцията Test(), тя ще отпечата стойността на $a и ще я инкрементира.

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

Пример 12-6. Статични променливи с рекурсивни функции

<?php
function Test()
{
    static $count = 0;

    $count++;
    echo $count;
    if ($count < 10) {
        Test();
    }
    $count--;
}
?>

Забележка: Статичните променливи могат да бъдат декларирани, както в примерите по-горе. Опитът да се присвоят стойности на тези променливи, които са резултат от изрази, ще причини грешка в разбора (parse error).

Пример 12-7. Деклариране на статични променливи

<?php
function foo(){
    static $int = 0;          // правилно 
    static $int = 1+2;        // грешно  (тъй като е израз)
    static $int = sqrt(121);  // грешно  (тъй като също е израз)

    $int++;
    echo $int;
}
?>

Референции с глобални и статични променливи

Zend Engine 1, управляваща PHP 4, осъществява static и global модификаторите за променливи от гледна точка на референции. Например, истинска глобална променлива, внесена в обхвата на функция посредством израза global, в действителност създава референция към глобалната променлива. Това може да доведе до неочаквано поведение, за което се отнася и следващия пример:

<?php
function test_global_ref() {
    global $obj;
    $obj = &new stdclass;
}

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

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

Изпълнението на този пример ще изведе следното:

NULL
object(stdClass)(0) {
}

Подобно поведение е в сила и за израза static. Референциите не се пазят статично:

<?php
function &get_instance_ref() {
    static $obj;

    echo 'Static object: ';
    var_dump($obj);
    if (!isset($obj)) {
        // Присвояване на референция на статичната променлива
        $obj = &new stdclass;
    }
    $obj->property++;
    return $obj;
}

function &get_instance_noref() {
    static $obj;

    echo 'Static object: ';
    var_dump($obj);
    if (!isset($obj)) {
        // Присвояване на обект на статичната променлива
        $obj = new stdclass;
    }
    $obj->property++;
    return $obj;
}

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

Изпълнението на този пример ще изведе следното:

Static object: NULL
Static object: NULL

Static object: NULL
Static object: object(stdClass)(1) {
  ["property"]=>
  int(1)
}

Този пример показва, че когато присвоявате референция на статична променлива, тя не се помни, когато извикате функцията &get_instance_ref() втори път.