PHP Variables
A variable in PHP is a named container that holds a value your script can read, change, and reuse while it runs. Unlike many other languages, PHP variables don’t need a declared type — the engine figures out whether you’re holding a string, a number, an array, or an object automatically, and that type can change while your code runs. Understanding how variables are created, scoped, and stored under the hood is one of the most important foundations for writing correct PHP, because a huge share of real-world bugs — wrong values, undefined variables, arrays that change unexpectedly — trace back to a misunderstanding of how variables actually behave.
Overview: How PHP Variables Work
Every PHP variable starts with a dollar sign ($) followed by a name, such as $username or $totalPrice. You do not declare a type or reserve memory ahead of time; a variable is created the moment you first assign a value to it with the = operator. PHP is a dynamically typed language, which means the type lives with the value, not with the variable name. The same variable can hold an integer at one point in a script and a string later on, because $x is really just a label pointing at whatever value was last assigned to it.
Internally, the Zend Engine (PHP’s execution engine) stores every variable’s value in a structure called a zval (Zend value). A zval bundles the actual data together with a type tag (string, int, float, bool, array, object, null, and a few internal types) and a reference count. Each function call and the global scope each get their own symbol table — essentially a hash map from variable names to zvals. When you write $age = 30;, PHP creates an entry named age in the current symbol table pointing at a zval holding the integer 30.
PHP also uses copy-on-write for efficiency. When you assign one variable to another ($b = $a;), PHP does not immediately duplicate the underlying data — both variables point at the same zval, and the reference count increases. Only when one of the two variables is modified does PHP actually copy the data, so the other variable is unaffected. This is why passing arrays and strings around your program is cheap unless you mutate them.
Variables also have scope — the region of code where they are visible. Variables created inside a function are local to that function and disappear when it returns; variables created outside any function live in the global scope. A function cannot see a global variable simply because it is defined in the same file — PHP scoping is per-function, not per-file, and you must explicitly use the global keyword or pass the value in as a parameter to share it.
Syntax
The general form of declaring and using a PHP variable is:
<?php
$variableName = value;
| Part | Meaning |
|---|---|
$ |
The sigil that marks a name as a variable. It is always required when referring to the variable. |
variableName |
Must start with a letter or underscore, followed by any number of letters, numbers, or underscores. Variable names are case-sensitive, so $total and $Total are different variables. |
= |
The assignment operator. It copies (or, for objects, links) the value on the right into the variable on the left. |
value |
Any PHP expression: a literal, another variable, a function call, an array, and so on. |
; |
Every statement, including a variable assignment, ends with a semicolon. |
A few extra forms you will use constantly:
$a = &$b;— assignment by reference;$aand$bbecome two names for the same underlying value.$$name— a variable variable, where the value of$nameis used as the name of another variable.$obj->propertyand$array['key']— accessing a value stored inside an object or array variable.list($a, $b) = $array;or[$a, $b] = $array;— destructuring an array into several variables at once.
Examples
Example 1: Declaring and Using Basic Variables
<?php
$name = "Ada Lovelace";
$age = 36;
$isProgrammer = true;
$balance = 1234.567;
echo "Name: $name\n";
echo "Age: $age\n";
echo "Programmer: " . ($isProgrammer ? "Yes" : "No") . "\n";
echo "Balance: " . number_format($balance, 2) . "\n";
Output:
Name: Ada Lovelace
Age: 36
Programmer: Yes
Balance: 1,234.57
Each variable is created the moment it is assigned. PHP infers the type from the literal: $name becomes a string, $age an integer, $isProgrammer a boolean, and $balance a float. Double-quoted strings support variable interpolation, so a variable written directly inside the string is replaced with its value, while number_format() formats the float with a thousands separator and two decimal places.
Example 2: Scope, References, and Variable Variables
<?php
function addTax(float $price, float $rate = 0.08): float {
$total = $price + ($price * $rate);
return $total;
}
$price = 50.0;
$finalPrice = addTax($price);
echo "Final price: $" . number_format($finalPrice, 2) . "\n";
$original = 10;
$alias = &$original;
$alias = 20;
echo "Original after alias change: $original\n";
$fieldName = "email";
$$fieldName = "user@example.com";
echo "Email via variable variable: $email\n";
Output:
Final price: $54.00
Original after alias change: 20
Email via variable variable: user@example.com
Inside addTax(), $total is a local variable that only exists for the duration of that function call — it cannot be accessed from outside. The line $alias = &$original; makes $alias a reference to $original, so changing $alias also changes $original, since they now point at the exact same zval. Finally, $$fieldName reads the string stored in $fieldName (email) and uses it as a variable name, effectively creating $email.
Example 3: Handling Input-Like Data Safely
<?php
$_POST['username'] = 'jdoe';
$_POST['age'] = '29';
$username = $_POST['username'] ?? 'guest';
$age = (int) ($_POST['age'] ?? 0);
$email = $_POST['email'] ?? null;
echo "Username: $username\n";
echo "Age (int): $age\n";
echo "Age type: " . gettype($age) . "\n";
echo "Email set? " . (isset($email) ? "Yes" : "No") . "\n";
echo "Email is null? " . (is_null($email) ? "Yes" : "No") . "\n";
unset($username);
echo "Username still set? " . (isset($username) ? "Yes" : "No") . "\n";
Output:
Username: jdoe
Age (int): 29
Age type: integer
Email set? No
Email is null? Yes
Username still set? No
This mirrors a very common real-world pattern: reading values out of a superglobal such as $_POST. The null coalescing operator (??) returns the left-hand value if it exists and is not null, otherwise it falls back to the right-hand default, which avoids undefined-array-key warnings. Casting with (int) converts the string into a true integer. Note that isset($email) reports false even though $email was assigned, because isset() specifically treats a variable holding null as not set. Calling unset() removes a variable from the symbol table entirely.
How PHP Variables Work Under the Hood
When the Zend Engine executes a script, it walks through a series of steps for every variable operation:
- Lookup: When you reference
$x, PHP looks up the name in the current symbol table (the global table, or the table for the currently executing function or method). - Zval creation: On first assignment, PHP allocates a zval containing the value and a type tag, and stores a pointer to it in the symbol table entry for that name.
- Reference counting: Every zval tracks how many symbol table entries (or array slots, or object properties) currently point to it. Assigning
$b = $a;increments the count instead of copying data. - Copy-on-write: If code later modifies one of the aliases, PHP notices the reference count is greater than one, makes a private copy for that variable, and decrements the shared zval’s count — the other variable is left untouched.
- Garbage collection: When a zval’s reference count drops to zero, such as when a local variable goes out of scope as a function returns, PHP frees the memory. A separate cycle-collecting garbage collector also runs periodically to clean up circular references, such as two objects referencing each other, that simple reference counting cannot detect on its own.
- Type juggling: Operators like
+,., and comparisons such as==can silently convert values between types following PHP’s coercion rules. This is powerful but is also the source of many subtle bugs, which is why===(strict equality, which also compares type) is usually safer than==.
Understanding this pipeline explains behavior that would otherwise look mysterious: why passing a large array into a function is cheap until the function actually changes it, why two variables can become linked through a reference, and why PHP manages to be both flexible with types and reasonably fast.
Common Mistakes
Mistake 1: Assuming Any Non-Empty String Is Truthy
Beginners often assume that because a string has characters in it, it will evaluate as true in a boolean context. PHP treats the specific string "0" as falsy, which surprises almost everyone at least once.
Wrong:
<?php
$userInput = "0";
if ($userInput) {
echo "Truthy: proceeding with input.\n";
} else {
echo "Falsy: input rejected.\n";
}
This prints Falsy: input rejected. even though $userInput clearly contains data, because PHP’s boolean conversion rules specifically define the strings "" and "0", along with 0, 0.0, null, and an empty array, as falsy.
Corrected:
<?php
$userInput = "0";
if ($userInput !== "") {
echo "Truthy: proceeding with input.\n";
} else {
echo "Falsy: input rejected.\n";
}
Here the real intent, checking whether the user actually submitted something, is tested explicitly with a strict string comparison instead of relying on PHP’s boolean coercion, so "0" is correctly treated as valid input.
Mistake 2: Leaving a Reference Variable Behind After a foreach Loop
Looping by reference to modify an array in place is common, but forgetting to break the reference afterward causes the loop variable to keep quietly aliasing the array’s last element.
Wrong:
<?php
$array = [1, 2, 3, 4];
foreach ($array as &$value) {
$value = $value * 2;
}
// Missing unset($value) here!
foreach ($array as $value) {
echo $value . " ";
}
echo "\n";
print_r($array);
Output:
2 4 6 6
Array
(
[0] => 2
[1] => 4
[2] => 6
[3] => 6
)
After the first loop, $value is still a live reference to $array[3]. The second foreach reuses the name $value as an ordinary loop variable, but every assignment to it actually overwrites $array[3], so the final element gets clobbered with whatever the second-to-last iteration assigned.
Corrected:
<?php
$array = [1, 2, 3, 4];
foreach ($array as &$value) {
$value = $value * 2;
}
unset($value);
foreach ($array as $value) {
echo $value . " ";
}
echo "\n";
print_r($array);
Output:
2 4 6 8
Array
(
[0] => 2
[1] => 4
[2] => 6
[3] => 8
)
Calling unset($value) immediately after any reference-based foreach breaks the alias, so the variable name is free to be reused safely afterward.
Best Practices
- Use descriptive, camelCase variable names like
$orderTotalinstead of single letters or abbreviations, except for very short-lived loop counters like$i. - Always call
unset($value)right after aforeach (... as &$value)loop to avoid the aliasing bug shown above. - Prefer
===and!==over==and!=when you care about both value and type, especially when comparing user input, database results, or API responses. - Use the null coalescing operator (
??) or??=when reading values that might not exist, such as array keys or superglobal entries, instead of suppressing warnings with@. - Declare parameter and return types on functions, as in
function addTax(float $price): float, so PHP enforces and documents the expected types for you. - Avoid variable variables (
$$name) in application code — they make static analysis and refactoring tools unreliable; use arrays or objects instead when you need dynamic keys. - Keep variable scope as narrow as possible; avoid relying on the
globalkeyword, and pass values into functions as parameters instead.
Practice Exercises
- Write a script that declares variables for a product’s
$name,$price, and$quantity, then calculates and echoes the total cost formatted to two decimal places. - Write a function
doubleInPlace(array &$numbers): voidthat takes an array by reference and doubles every element in place, then write code that calls it and prints the modified array withprint_r(). - Given
$data = ['name' => 'Sam', 'age' => null];, write code that checks the'age'key withisset(),array_key_exists(), and??, and predict, then explain, whyisset()andarray_key_exists()disagree in this case.
Summary
- PHP variables start with
$, are created on first assignment, and are dynamically typed, since the type lives with the value, not the variable name. - Variable names are case-sensitive and must start with a letter or underscore.
- Values are stored internally as zvals in a symbol table, with reference counting and copy-on-write making assignment cheap until data is actually modified.
- Scope determines visibility: local variables inside a function disappear when it returns, while global variables require the
globalkeyword or a parameter to be shared into a function. - References, written with
&, make two variable names point at the same value; forgetting tounset()a reference loop variable is a classic bug. isset(),empty(), andunset()let you check for and remove variables, with subtle but important differences aroundnullvalues.- Prefer strict comparisons and explicit type handling to avoid surprises from PHP’s automatic type juggling.
