0

Scope — where a variable is visible (local, global, closure, class)

Beginner5 min read·eng-12-025

Concept

Scope — the region of code where a variable is visible and accessible. PHP has several distinct scoping rules that differ from most other languages.

PHP's scoping rules (different from JavaScript!):

  • Local scope: Variables defined inside a function are only accessible inside that function. They don't "see" variables defined outside.
  • Global scope: Variables defined outside functions. NOT accessible inside functions unless explicitly declared.
  • global keyword: Brings a global variable into the local function scope: global $config;.
  • $GLOBALS superglobal: Array containing all global variables: $GLOBALS['config'].
  • Static scope: static $counter = 0; inside a function — persists between calls, but still local to that function.
  • Closure scope: Closures do NOT automatically inherit outer scope. Use use ($var) to capture specific variables.

PHP vs JavaScript scoping: In JavaScript, an inner function can access variables from outer functions (closure). In PHP, a regular function CANNOT see its outer scope — you need explicit use ($var) in a closure.

Class scope:

  • private: Only within the class.
  • protected: Within the class and subclasses.
  • public: From anywhere.
  • Static properties: Shared across all instances, accessed via ClassName::$prop or static::$prop.

Block scope (PHP 8): PHP does NOT have block scope for variables. A variable defined inside a for loop is accessible after the loop. This differs from JavaScript/Java.

Variable variables: $$name — variable whose name is stored in $name. Rare, but worth knowing.

Code Example

php
<?php
$message = 'Hello from global scope';
$count   = 0;

// ❌ PHP function CANNOT see global variables
function greet(): void
{
    echo $message; // Notice: Undefined variable $message
}

// ✅ Use global keyword to import
function greetWithGlobal(): void
{
    global $message; // explicitly import
    echo $message;   // 'Hello from global scope'
    $message = 'Modified!'; // modifies the actual global!
}

// ✅ Better: pass as parameter (pure function style)
function greetPure(string $message): void
{
    echo $message;
}

// Closure scope — must explicit capture with 'use'
$prefix = 'Hello';
$greet  = function(string $name) use ($prefix): string
{
    return "{$prefix}, {$name}!";
};
echo $greet('Alice'); // 'Hello, Alice!'
// Without 'use ($prefix)': Notice: Undefined variable $prefix

// Arrow function — automatic capture (but by value at definition time)
$greeting = fn(string $name): string => "{$prefix}, {$name}!";
echo $greeting('Bob'); // 'Hello, Bob!'

// Static scope — persists between calls
function counter(): int
{
    static $count = 0; // initialized ONCE, survives between calls
    return ++$count;
}
echo counter(); // 1
echo counter(); // 2
echo counter(); // 3

// PHP has NO block scope — loop variable leaks
for ($i = 0; $i < 3; $i++) {
    $loopVar = $i * 2;
}
echo $i;       // 3 — still accessible!
echo $loopVar; // 4 — still accessible!

// Class scope levels
class BankAccount
{
    public float    $interestRate = 0.05; // accessible everywhere
    protected float $balance      = 0.0;  // accessible in class + subclasses
    private string  $accountNumber;       // only within BankAccount

    public function getBalance(): float { return $this->balance; } // can access private
}

class SavingsAccount extends BankAccount
{
    public function earn(): void { $this->balance += $this->balance * $this->interestRate; } // protected OK
    // $this->accountNumber — Fatal error: private property of parent
}

// Static class property — shared across all instances
class RequestCounter
{
    private static int $requests = 0;
    public static function increment(): void { self::$requests++; }
    public static function getCount(): int   { return self::$requests; }
}
RequestCounter::increment();
RequestCounter::increment();
echo RequestCounter::getCount(); // 2 — shared state