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.
Source: Variables — Basics
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.
Source: Variable variables
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.
Source: The global keyword and $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";
Source: Using static variables
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)
Source: Anonymous functions and Arrow Functions
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 |
|---|---|
|
all global variables |
|
server and execution environment: |
|
query-string parameters |
|
form fields of a |
|
uploaded files |
|
cookies sent by the client |
|
session data, after |
|
|
|
environment variables, only if |
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);
Source: Predefined Variables and Superglobals
isset, empty, unset and ??
These language constructs work on variables, array elements and properties without triggering "undefined" warnings:
| Construct | Result |
|---|---|
|
|
|
|
|
destroys the variable (or array element, or property); unsetting a reference only removes that name. |
|
|
|
assigns |
|
|
<?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).
Source: isset, empty, unset and Null coalescing operator
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 |
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, |
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.
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.
Source: Predefined Constants
Magic constants
The magic constants are resolved at compile time and change depending on where they are used:
| Constant | Value |
|---|---|
|
current line number |
|
full path of the current file (symlinks resolved) |
|
directory of the current file, equivalent to |
|
current function name ( |
|
current class name, including namespace (in a trait: the class using it) |
|
current trait name |
|
|
|
inside a property hook, the property name (8.4) |
|
current namespace |
|
the fully qualified class name (resolved at compile time, also works with |
<?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 anErrorin PHP 9. -
Case-insensitive constants (
define('X', 1, true)) were deprecated in 7.3 and removed in 8.0. -
$GLOBALScan 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.