Variables, Scope and Constants

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against PHP 8.5, checked on 2026-10-05. It covers how PHP stores named values — variables and constants — and where each name is visible.

Variables

A variable is a $ followed by a name: a letter or underscore, then letters, digits and underscores (bytes 0x80-0xff count as letters, so UTF-8 names work). Names are case-sensitive. $this is reserved inside methods and cannot be assigned.

Variables need no declaration: assigning creates them. Assignment copies the value (arrays and strings are copied lazily, on write); the & operator creates a reference, so two names point to the same value (see References and Memory Management).

<?php

declare(strict_types=1);

$title = 'Kindred';
$copy = $title;          // by value
$alias = &$title;        // by reference

$title = 'Dawn';
var_dump($copy, $alias); // string(7) "Kindred" string(4) "Dawn"

// Arrays are values too: modifying the copy does not touch the original.
$shelf = ['Solaris'];
$other = $shelf;
$other[] = 'Kindred';
var_dump(count($shelf), count($other));   // int(1) int(2)

Objects behave differently: a variable holds a handle to the object, so $b = $a makes both names refer to the same object. Use clone for a copy.

Undefined variables

Reading a variable that was never assigned yields null and, since PHP 8.0, a warning ("Undefined variable $x"); it was a notice in PHP 7. An accepted RFC promotes this warning to an Error exception in PHP 9.0, which is not yet scheduled. Code that is clean today under error_reporting = E_ALL is ready for it.

<?php

declare(strict_types=1);

function total(array $prices): int
{
    foreach ($prices as $p) {
        $sum += $p;          // Warning: Undefined variable $sum -- initialise it first
    }
    return $sum;
}

echo total([3, 4]), "\n";    // 7, plus a warning; an Error in PHP 9

Variable variables

A variable variable uses the value of one variable as the name of another: $$name, or ${$expr} for expressions. The same works for properties ($obj->$prop, $obj->{$expr}) and, since PHP 8.3, for class constants (Format::{$case}).

<?php

declare(strict_types=1);

$field = 'title';
$$field = 'Solaris';
var_dump($title);                     // string(7) "Solaris"

$book = new stdClass();
$book->{'year' . 'Published'} = 1961;
var_dump($book->yearPublished);       // int(1961)

final class Limits { public const MAX_LOANS = 5; }
$name = 'MAX_LOANS';
var_dump(Limits::{$name});            // int(5) -- dynamic class constant fetch, 8.3

Variable variables make code hard to read and analyse. Prefer arrays for dynamic names ($values['title']); reserve $obj->$prop for code such as hydrators where the name genuinely comes from data, and never derive the name from user input.

Scope

PHP has function scope, not block scope: a variable assigned inside an if or a loop is visible after it, in the same function. Each function body is a separate scope, and the global scope (the top level of a script and of included files) is not visible inside functions.

<?php

declare(strict_types=1);

$library = 'City Library';            // global scope

function welcome(): string
{
    // $library is not visible here
    return isset($library) ? $library : 'unknown library';
}

echo welcome(), "\n";                 // unknown library

if (true) {
    $inBlock = 'visible after the block';
}
echo $inBlock, "\n";                  // no block scope

Included files share the scope of the line that includes them: an include inside a function sees that function’s local variables.

Source: Variable scope

The global keyword and $GLOBALS

global $name; inside a function binds a local name to the global variable (by reference). $GLOBALS is an array of all global variables. Since PHP 8.1, $GLOBALS is a read-only copy as a whole: you can still write individual elements ($GLOBALS['x'] = 1), but not replace or unset() the array, pass it by reference, or iterate it by reference.

<?php

declare(strict_types=1);

$loanCount = 0;

function registerLoan(): void
{
    global $loanCount;      // a reference to the global
    $loanCount++;
}

function registerReturn(): void
{
    $GLOBALS['loanCount']--;
}

registerLoan();
registerLoan();
registerReturn();
echo $loanCount, "\n";      // 1

Global state makes functions depend on hidden input and complicates testing. Pass values as parameters, or keep state in objects; dependency-injection containers (Laravel’s, PSR-11) exist largely to avoid globals.

Static variables

A static variable inside a function keeps its value between calls. It is initialised once, the first time the declaration is reached; since PHP 8.3 the initialiser can be any expression, including function calls (before, only constant expressions were allowed). In methods, static variables are shared by all instances, and, since 8.1, also between a parent class method and a child class that inherits it.

<?php

declare(strict_types=1);

function nextLoanId(): int
{
    static $id = 1000;                     // initialised once, on the first call
    return $id++;
}

echo nextLoanId(), ' ', nextLoanId(), ' ', nextLoanId(), "\n";   // 1000 1001 1002

function uptime(): float
{
    static $startedAt = hrtime(true);      // a function call as initialiser: PHP 8.3+
    return (hrtime(true) - $startedAt) / 1e9;
}

uptime();
var_dump(uptime() >= 0.0);                 // bool(true)

function cachedLookup(string $isbn): string
{
    static $cache = [];
    return $cache[$isbn] ??= strtoupper($isbn);    // computed once per ISBN
}

echo cachedLookup('isbn-a'), "\n";

Closures and captured variables

Anonymous functions do not see the enclosing scope either, unless variables are imported with use (by value, or by reference with &). Arrow functions (fn) capture every variable they use by value, automatically. Both are detailed in Closures and Callables.

<?php

declare(strict_types=1);

$minYear = 1970;
$books = [['title' => 'Solaris', 'year' => 1961], ['title' => 'Kindred', 'year' => 1979]];

$recent1 = array_filter($books, function (array $b) use ($minYear): bool { return $b['year'] >= $minYear; });
$recent2 = array_filter($books, fn (array $b): bool => $b['year'] >= $minYear);

var_dump(count($recent1), count($recent2));   // int(1) int(1)

Predefined variables and superglobals

PHP fills a number of variables before your script runs. The superglobals are available in every scope without global:

Superglobal Contents

$GLOBALS

all global variables

$_SERVER

server and execution environment: REQUEST_METHOD, REQUEST_URI, HTTP_* headers, REMOTE_ADDR, SCRIPT_FILENAME, argv in the CLI, …

$_GET

query-string parameters

$_POST

form fields of a POST with application/x-www-form-urlencoded or multipart/form-data body

$_FILES

uploaded files

$_COOKIE

cookies sent by the client

$_SESSION

session data, after session_start()

$_REQUEST

$_GET, $_POST (and optionally $_COOKIE) merged in the order given by request_order; avoid it

$_ENV

environment variables, only if variables_order contains E (often not); getenv() is more reliable

Other predefined variables are local: $argv and $argc (CLI arguments), $http_response_header (set by HTTP stream wrappers; since 8.4 prefer http_get_last_response_headers()) and, inside methods, $this.

Every superglobal holding request data is untrusted user input. Their use in web applications — validation, escaping, php://input, request_parse_body() — is the subject of HTTP Requests and Responses.

<?php

declare(strict_types=1);

// In the CLI SAPI:
printf("%s called with %d argument(s)\n", $argv[0], $argc - 1);
printf("SAPI script: %s\n", basename($_SERVER['SCRIPT_FILENAME']));
var_dump(isset($_SERVER['REQUEST_METHOD']));       // bool(false) -- no HTTP request on the CLI
var_dump(getenv('HOME') !== false);

isset, empty, unset and ??

These language constructs work on variables, array elements and properties without triggering "undefined" warnings:

Construct Result

isset($x)

true if $x exists and is not null; with several arguments, true only if all are set. Works on nested array keys and properties without warnings.

empty($x)

true if $x does not exist or is falsy (0, "0", "", [], null, false). Equivalent to !isset($x) || !$x.

unset($x)

destroys the variable (or array element, or property); unsetting a reference only removes that name.

$x ?? $default

$x if it is set and not null, otherwise $default; never warns.

$x ??= $default

assigns $default only if $x is unset or null (7.4).

array_key_exists($k, $a)

true even when the value is null, unlike isset().

<?php

declare(strict_types=1);

$book = ['title' => 'Kindred', 'subtitle' => null, 'copies' => 0];

var_dump(isset($book['title']), isset($book['subtitle']), array_key_exists('subtitle', $book));
// bool(true) bool(false) bool(true)

var_dump(empty($book['copies']), empty($book['missing']));   // bool(true) bool(true)

echo $book['subtitle'] ?? '(no subtitle)', "\n";             // (no subtitle)
echo $book['author']['name'] ?? 'unknown author', "\n";      // nested: no warning

$book['format'] ??= 'paperback';
unset($book['copies']);
print_r($book);

empty() treats "0" and 0 as empty, which is rarely what validation wants: a quantity of 0 or a field containing "0" is valid input. Compare explicitly instead ($value === '', $value === null).

Constants

A constant is a name for a value that cannot change. Constants have no $, are global regardless of scope, and are case-sensitive (case-insensitive constants were removed in PHP 8.0). By convention they are UPPER_SNAKE_CASE.

const versus define()

const NAME = value; define('NAME', value);

When

at compile time

at runtime, when the call executes

Where

top level of a file or namespace, and in classes

anywhere, including inside if and functions

Name

a literal identifier

any string expression

Namespace

belongs to the current namespace

global unless the name includes the namespace

Values

constant expressions: scalars, arrays, enum cases, new objects (8.1), closures and first-class callables (8.5)

any value, including objects (8.1)

<?php

declare(strict_types=1);

namespace Library;

const MAX_LOANS = 5;                                  // Library\MAX_LOANS
const FORMATS = ['hardcover', 'paperback', 'ebook'];  // arrays are allowed
const LOAN_DAYS = 7 * 3;                              // constant expressions

if (!defined('Library\DEBUG')) {
    define('Library\DEBUG', getenv('APP_DEBUG') === '1');   // runtime value
}

echo MAX_LOANS, ' ', LOAN_DAYS, ' ', count(FORMATS), "\n";  // 5 21 3
var_dump(\constant('Library\MAX_LOANS'));                   // int(5) -- name as a string
var_dump(DEBUG);                                             // bool(false)

Class constants (public const int MAX = 5;, typed since 8.3, final since 8.1) are covered in Classes and Objects. Since PHP 8.5, const declarations can carry attributes, and constant expressions can contain closures and first-class callables.

Source: Constants, Syntax and define

Predefined constants

Core and extensions define many constants: PHP_VERSION, PHP_MAJOR_VERSION, PHP_OS_FAMILY, PHP_EOL, PHP_INT_MAX, PHP_INT_SIZE, PHP_FLOAT_EPSILON, DIRECTORY_SEPARATOR, E_ALL and the other error levels, M_PI, SORT_STRING, JSON_THROW_ON_ERROR, and so on. get_defined_constants(true) lists them grouped by extension.

Magic constants

The magic constants are resolved at compile time and change depending on where they are used:

Constant Value

__LINE__

current line number

__FILE__

full path of the current file (symlinks resolved)

__DIR__

directory of the current file, equivalent to dirname(__FILE__)

__FUNCTION__

current function name ({closure} in a closure)

__CLASS__

current class name, including namespace (in a trait: the class using it)

__TRAIT__

current trait name

__METHOD__

Class::method

__PROPERTY__

inside a property hook, the property name (8.4)

__NAMESPACE__

current namespace

ClassName::class

the fully qualified class name (resolved at compile time, also works with use aliases)

<?php

declare(strict_types=1);

namespace Library\Catalogue;

final class Book
{
    public string $title {
        set => trim($value);
        get => sprintf('%s (%s)', $this->title, __PROPERTY__);    // 8.4
    }

    public function where(): string
    {
        return sprintf('%s in %s, line %d', __METHOD__, basename(__FILE__), __LINE__);
    }
}

$b = new Book();
$b->title = '  Kindred ';
echo $b->title, "\n";        // Kindred (title)
echo $b->where(), "\n";      // Library\Catalogue\Book::where in ...php, line 16
echo __NAMESPACE__, ' ', Book::class, "\n";

__DIR__ is the reliable way to build paths relative to the current file: require __DIR__ . '/../vendor/autoload.php'; works no matter what the current working directory is.

Source: Magic constants

Where older books and tutorials are outdated

  • Undefined variables were a notice before PHP 8.0 (often hidden by error_reporting); they are a warning now and will be an Error in PHP 9.

  • Case-insensitive constants (define('X', 1, true)) were deprecated in 7.3 and removed in 8.0.

  • $GLOBALS can no longer be overwritten or iterated by reference (8.1).

  • Static variable initialisers were limited to constant expressions before 8.3.

  • register_globals, which turned request parameters into global variables and enabled countless injection attacks, was removed in PHP 5.4; some very old code still assumes it.