Variable Scope in PHP: Global, Static and Closures

Quick Summary

  • A variable created inside a PHP function is local to it, and a function can only use a global variable after importing it with global or reading it from $GLOBALS.
  • A static variable stays local to its function but keeps its value between calls, and before PHP 8.3 its initial value must be a constant expression.
  • Closures get outer variables through use, by value unless you write &$var, while arrow functions capture them automatically and can't change them.

Variable scope is the part of your code where a variable can be seen and used. In PHP, a variable created inside a function is local to that function, and a variable created outside any function is global. A PHP function doesn’t see global variables on its own: import them with the global keyword or read them from the $GLOBALS array. Closures get outer variables through use, and arrow functions copy them automatically.

The variables you work with fall into four groups, each tied to one of those two scopes:

  1. Local variables (local scope)
  2. Global variables (special global scope)
  3. Static variables (local scope)
  4. Function parameters (local scope)

How do you use a global variable inside a function?

Both functions below print the global variable $hw, which is defined outside them.

PHP
$hw = "Hello World";

function printHW(){
    global $hw; //global scope
    $name = "nahid"; //local scope
    echo $hw;
}

function printHW2(){
    echo $GLOBALS['hw']; //$GLOBALS -> Super Global Array
}

printHW();  // prints Hello World
printHW2(); // prints Hello World
  • printHW() imports $hw with global $hw;, so echo $hw prints the global value. $name is local: it exists only while the function runs.
  • printHW2() reads the same value from $GLOBALS['hw']. $GLOBALS is a superglobal array of all global variables, available everywhere without importing.

Without either of them, echo $hw; inside a function looks for a local $hw, finds none and prints “Warning: Undefined variable $hw”. PHP 8 raises a warning here; PHP 7 raised a notice.

Both ways also let a function change the global. global makes the local name a reference to the global variable, and assigning to an element of $GLOBALS writes the global directly:

PHP
$visits = 0;

function addVisit() {
    global $visits;
    $visits++;
}

function addVisit2() {
    $GLOBALS['visits']++;
}

addVisit();
addVisit2();
echo $visits; // 2

WordPress code uses the same keyword, for example global $post; or global $wpdb; inside a function.

What changed with $GLOBALS in PHP 8.1?

Since PHP 8.1, you can still read and write single entries such as $GLOBALS['hw'], but not the array as a whole. A line like $GLOBALS = []; no longer runs: PHP stops at compile time with “$GLOBALS can only be modified using the $GLOBALS[$name] = $value syntax”. A copy of the array is also an ordinary copy now:

PHP
$GLOBALS['site'] = 'nmh47'; // still allowed: sets the global $site
echo $site;                 // nmh47

$copy = $GLOBALS;
$copy['site'] = 'changed';
echo $site;                 // still nmh47 on PHP 8.1 and later

Before PHP 8.1, changing $copy['site'] changed $site too. The $GLOBALS page on php.net lists every operation that is no longer allowed.

What does a static variable do?

A static variable is local to its function, but it keeps its value between calls instead of starting over. Its initial value is set once, on the first call, so the three calls below print 1, 2 and 3.

PHP
function doSomething() {
    static $i = 0; //static scope
    $i++;
    echo $i;
    echo "\n";
}
doSomething();
doSomething();
doSomething();
  • Writing static $i; and then $i = $i ?? 0; gives the same result; static $i = 0; is just shorter.
  • Before PHP 8.3, the initial value must be a constant expression: static $start = time(); stops PHP 8.2 with “Constant expression contains invalid operations”. PHP 8.3 and later accept any expression there.
  • Since PHP 8.1, a static variable in a method is shared with child classes that inherit the method without overriding it, like a static property. The variable scope page on php.net shows the example.

How do closures and arrow functions see outer variables?

An anonymous function has its own local scope too. List the outer variables it needs in use:

PHP
$greeting = 'Hello';

$sayHello = function ( $name ) use ( $greeting ) {
    return "$greeting, $name";
};

$greeting = 'Hi';
echo $sayHello( 'Nahid' ); // Hello, Nahid

$count = 0;

$increment = function () use ( &$count ) {
    $count++;
};

$increment();
$increment();
echo $count; // 2

use ( $greeting ) copies the value when the closure is created, so changing $greeting later doesn’t affect it. use ( &$count ) passes a reference instead, so the closure can change the outer variable.

Arrow functions, available since PHP 7.4, capture the outer variables they use automatically, but always by value:

PHP
$tax = 0.2;

$withTax = fn( $price ) => $price * ( 1 + $tax );
echo $withTax( 100 ); // 120

$count = 0;
$add = fn() => ++$count;
echo $add();  // 1
echo $count;  // 0

The arrow function increments its own copy of $count, so the outer $count stays 0. When a callback must change an outer variable, use a closure with use ( &$var ).

In WordPress, use is how a hook callback gets values from the code around it, for example the IDs to hide from the blog homepage:

PHP
$hide_ids = array( 99, 105 );

add_action( 'pre_get_posts', function ( $query ) use ( $hide_ids ) {
    if ( $query->is_home() && $query->is_main_query() ) {
        $query->set( 'post__not_in', $hide_ids );
    }
} );

Common problems

  • Undefined variable inside a function. PHP looks for a local variable with that name. Import the global with global, read it from $GLOBALS, or better, pass the value in as a parameter.
  • A parameter change doesn’t reach the caller. Assigning a new value to a parameter inside a function doesn’t change the variable you passed in, unless the parameter is declared by reference (&$value).
  • A static variable isn’t reset. It keeps its value for the rest of the request, which is why this Fibonacci function with a static counter prints nothing when it’s called a second time.
  • A template variable overwrites a WordPress global. Top-level templates such as single.php run in global scope, so a variable named like a WordPress global, such as $id, replaces it. Specific names in theme templates avoid the clash.
  • A variable is missing in a template part. Files loaded with get_template_part() run inside a function. They see the WordPress globals it imports, such as $post and $wpdb, but not the variables of the template that called them. Pass values in the third parameter, get_template_part( 'template-parts/card', null, array( 'title' => $title ) ), and read them as $args['title'] in the part (WordPress 5.5 and later).
Share on X (opens in a new tab) Share on LinkedIn (opens in a new tab) Share on Facebook (opens in a new tab)

ChatGPT (opens in a new tab) Claude (opens in a new tab) Gemini (opens in a new tab) Perplexity (opens in a new tab) Grok (opens in a new tab)

More Insights

See More