02 · Variables & Types¶
PHP variables always start with a $ sigil, need no declared type up front,
and can hold any type — the type lives with the value, not the variable.
This is called dynamic typing.
Declaring and assigning variables¶
<?php
$name = "Alice"; // string
$age = 30; // int
$height = 1.68; // float (double-precision)
$isStudent = false; // bool
echo $name . " is " . $age . " years old.\n";
// Alice is 30 years old.
// Variables are case-sensitive: $Name and $name are different variables.
// Variable names must start with a letter or underscore, followed by
// letters, digits, or underscores -- no spaces or hyphens.
There's no int/String/etc. keyword before the variable, and the same
variable can be reassigned to a different type at any point:
<?php
$value = 42;
var_dump($value); // int(42)
$value = "now a string";
var_dump($value); // string(13) "now a string"
Scalar types¶
PHP has four scalar (single-value) types:
<?php
$count = 10; // int
$price = 19.99; // float
$label = "T-shirt"; // string
$inStock = true; // bool
echo gettype($count) . "\n"; // integer
echo gettype($price) . "\n"; // double (PHP's internal name for float)
echo gettype($label) . "\n"; // string
echo gettype($inStock) . "\n"; // boolean
Inspecting values: var_dump and gettype¶
var_dump() is the workhorse debugging tool — it prints a value's type
and its content, and can take multiple arguments:
<?php
$items = ["apple", "banana"];
var_dump($items);
// array(2) {
// [0]=>
// string(5) "apple"
// [1]=>
// string(6) "banana"
// }
var_dump(3.14, "hi", null, true);
// float(3.14)
// string(2) "hi"
// NULL
// bool(true)
gettype() returns just the type name as a string, useful in conditionals or
quick checks — var_dump() is better for actually debugging a value.
Strings: single vs. double quotes¶
<?php
$name = "World";
// Single quotes: literal text, no interpolation, no escape sequences
// (except \\ and \')
echo 'Hello, $name!\n'; // Hello, $name!\n (printed literally)
// Double quotes: variables are interpolated, and escape sequences work
echo "Hello, $name!\n"; // Hello, World!
// Concatenation with the "." operator works with either quote style
echo 'Hello, ' . $name . "!\n"; // Hello, World!
// Curly braces disambiguate a variable inside a larger string
$price = 5;
echo "Total: \${$price}.00\n"; // Total: $5.00
Type juggling¶
PHP automatically converts between types when an operation expects a different one — this is called type juggling:
<?php
$result = "5" + 3; // int(8) -- numeric string treated as a number
$result2 = "5" . 3; // string("53") -- "." forces string concatenation
$result3 = "5 apples" + 3; // int(8) with a warning -- leading numeric part is used
// $result4 = "apples" + 3; // Fatal error (TypeError) in PHP 8+ -- no numeric part at all
echo (1 == "1") ? "equal\n" : "not equal\n"; // equal -- loose comparison juggles types
echo (0 == "abc") ? "equal\n" : "not equal\n"; // not equal in PHP 8+ (changed from PHP 7!)
PHP 8 tightened these rules considerably compared to PHP 7 (e.g., 0 == "abc"
used to be true), but juggling still happens — which is exactly why ===
(covered next) is almost always the safer choice for comparisons.
Type casting¶
You can force a conversion explicitly instead of relying on juggling:
<?php
$str = "42";
$num = (int) $str; // 42 (int)
$float = (float) "3.14"; // 3.14 (float)
$text = (string) 100; // "100" (string)
$flag = (bool) 0; // false -- 0, 0.0, "", "0", [], and null are all "falsy"
$flag2 = (bool) "hello"; // true -- any non-empty string besides "0" is truthy
// Casting to int truncates, it doesn't round
$truncated = (int) 9.9; // 9
// intval()/floatval()/strval()/boolval() are function equivalents of casts
$n = intval("100 apples"); // 100 -- reads leading numeric portion
null and the null coalescing operator¶
null represents "no value." Accessing an undefined variable or array key
returns null (with a warning) rather than crashing:
<?php
$user = ["name" => "Bob"];
// isset() checks a variable/key exists AND isn't null
if (isset($user['email'])) {
echo $user['email'];
} else {
echo "no email on file\n";
}
// The null coalescing operator "??" is shorthand for the isset() check above:
$email = $user['email'] ?? "no email on file";
echo $email . "\n"; // no email on file
// Null coalescing assignment "??=" -- assign only if currently null/unset
$config = [];
$config['timeout'] ??= 30;
echo $config['timeout'] . "\n"; // 30
Constants¶
Constants can't be changed once defined and don't use the $ sigil:
<?php
define('MAX_USERS', 100); // classic function-based constant
const APP_NAME = 'MyApp'; // "const" keyword -- must be a compile-time value
echo MAX_USERS . "\n"; // 100
echo APP_NAME . "\n"; // MyApp
// MAX_USERS = 200; // Fatal error -- constants cannot be reassigned
const is evaluated at compile time and is the more common modern style at
the top level and inside classes; define() can compute its value at
runtime (e.g., define('BUILD_TIME', time())) and works in any scope.
Operators¶
<?php
// Arithmetic
echo 7 + 3, "\n"; // 10
echo 7 - 3, "\n"; // 4
echo 7 * 3, "\n"; // 21
echo 7 / 2, "\n"; // 3.5 -- "/" always does float division if not evenly divisible
echo 7 % 3, "\n"; // 1 -- modulo (remainder)
echo 2 ** 8, "\n"; // 256 -- exponentiation
// String concatenation uses "." (NOT "+")
echo "foo" . "bar", "\n"; // foobar
// Comparison: "==" checks value equality (with juggling), "===" checks
// value AND type equality (no juggling) -- prefer "===" by default
var_dump(1 == "1"); // true -- loose: string is juggled to int
var_dump(1 === "1"); // false -- strict: int vs string, different types
var_dump(1 === 1); // true
var_dump(1 != "2"); // true
var_dump(1 !== "1"); // true -- strict "not equal"
echo 5 > 3 ? "yes" : "no", "\n"; // yes
echo 5 <=> 3, "\n"; // 1 -- spaceship operator: -1, 0, or 1
Cheat sheet¶
| Concept | Syntax | Example |
|---|---|---|
| Variable | $name = value; |
$age = 30; |
| Integer | int |
$n = 42; |
| Float | float / double |
$pi = 3.14; |
| String | string |
$s = "hi"; |
| Boolean | bool |
$ok = true; |
| Null | null |
$x = null; |
| Check type | gettype($v) |
"integer" |
| Debug value + type | var_dump($v) |
int(42) |
| Cast | (int), (float), (string), (bool) |
(int) "42" |
| Loose equality | == |
juggles types |
| Strict equality | === |
no juggling |
| Null coalescing | ?? |
$v ?? "default" |
| Null coalescing assign | ??= |
$v ??= "default" |
| Constant (compile-time) | const NAME = val; |
const PI = 3.14; |
| Constant (runtime) | define('NAME', val); |
define('PI', 3.14); |
| Concatenation | . |
"a" . "b" → "ab" |
| Spaceship | <=> |
5 <=> 3 → 1 |
How It Actually Works¶
Every PHP value is stored in a zval (Zend value) container, not as a raw machine value: a zval bundles a type tag with either the value itself (for integers, booleans, doubles) or a pointer to a heap-allocated structure (for strings, arrays, objects). When you assign $b = $a, PHP doesn't necessarily copy the underlying data — it uses copy-on-write (COW): the new variable's zval points at the same refcounted value and only a real copy is made, lazily, the instant either variable is mutated. var_dump() and gettype() read the type tag straight off the zval rather than inspecting the raw bytes, which is why they're always accurate even though PHP is dynamically typed. Type juggling ("5" + 3) works because arithmetic opcodes like ZEND_ADD check the operand zval types at runtime and coerce one side before performing the operation — there's no compile-time type resolution the way there is in Java or C, so the coercion rule is evaluated fresh on every execution of that opcode.
🔀 See this in another language¶
Exercise¶
Write profile.php that:
- Declares variables for a person's
$name(string),$age(int),$height(float in meters), and$isMember(bool). - Prints a sentence about them using double-quoted string interpolation.
- Uses
var_dump()to show the type of each variable. - Reads a value that might not exist (e.g.,
$profile['nickname']from an empty array$profile = [];) using??to supply a default, and prints the result.