Free Handbook · Every example compiled & verified

Fundamentals

Variables, PHP's types, var_dump vs print_r vs echo, type juggling and strict_types, constants, operators, integer division and null handling.

0 / 148 lessons🔥 0 day streak
ShareXLinkedIn

Module 01 · what you'll be able to do

  • Declare variables, name them correctly, and read the "Undefined variable" warning
  • Name PHP's types and inspect any value with var_dump, print_r and get_debug_type
  • Predict how PHP converts types in loose mode, and switch it off with declare(strict_types=1)
  • Use == vs ===, <=>, ??, ?: and the arithmetic operators without the classic PHP surprises
  • Cast between types deliberately and handle null with isset, ?? and ??=
01

Variables

A PHP variable is a $ followed by a name: $price, $userName, $_count. The name starts with a letter or underscore, then letters, digits and underscores, and it is case-sensitive. There is no declaration keyword — a variable is created the first time you assign to it.

PHP is dynamically typed: the variable has no type, the value does. The same variable can hold an int now and a string later. That is convenient in a short script and a source of bugs in a large one, which is why modern PHP adds type declarations on functions and properties (you will use them from Module 05).

phpmain.php
<?php
$product = "Keyboard";
$price = 49;
$inStock = true;

echo "$product costs $price\n";

$price = $price + 10;   // reassign using the old value
$price += 1;            // the same idea, shorter
echo "New price: $price\n";

$price = "sold out";    // now it holds a string
echo "Price: $price\n";

[$a, $b] = [1, 2];      // assign two variables at once
[$a, $b] = [$b, $a];    // swap them
echo "a=$a b=$b\n";
Outputcompiled & run with real PHP
Keyboard costs 49
New price: 60
Price: sold out
a=2 b=1
Your turn

Add a variable $quantity = 3 and print the total cost of three keyboards at the original price.

Error you will hit

Warning: Undefined variable — a typo PHP lets run

php
<?php
$userName = "Asha";
echo "Hello, $username!\n";
Warning: Undefined variable $username in main.php on line 3
Hello, !
Why PHP said that

Variable names are case-sensitive, so $username is a different, never-assigned variable. Reading it is only a warning: PHP treats it as null, prints an empty string, and keeps going. The script "works" and quietly prints the wrong thing — the most common beginner bug in PHP.

The fix

Use the exact name. Treat every warning as a bug: in development, run with all errors visible and fix each one. Static analysers like PHPStan (Module 11) catch this before you run.

php
<?php
$userName = "Asha";
echo "Hello, $userName!\n";
Variable scope in one sentence
A variable created in the main script is not visible inside a function, and one created in a function disappears when it returns. PHP has no block scope: a variable assigned inside an if or loop still exists after it. Module 05 covers scope, global and closures.
02

Data types and how to inspect them

Every PHP value has one of these types. Four are scalar (a single value), two are compound, and null is its own thing.

TypeExampleNotes
int42, -7, 1_000_000, 0xFF64-bit on every modern system: up to PHP_INT_MAX (9223372036854775807). Underscores are ignored.
float3.14, 1.5e3A 64-bit double. Not exact: 0.1 + 0.2 is not 0.3.
string"hi", 'hi'A sequence of bytes, not characters (Module 03).
booltrue, falseCase-insensitive keywords; write them in lower case.
array[1, 2], ['id' => 7]An ordered map: list and dictionary in one (Module 04).
objectnew DateTime()An instance of a class (Module 06).
nullnullThe only value of type null: "no value". Unset and undefined variables read as null.

Three ways to look at a value, and you need all three. echo prints a value as a string — good for output, useless for debugging because false and null print nothing. print_r prints arrays and objects readably. var_dump prints the type and value of anything — the one to reach for when something is wrong.

phpmain.php
<?php
$values = [42, 3.5, "42", true, false, null];

foreach ($values as $v) {
    echo "echo: [", $v, "]  type: ", get_debug_type($v), "\n";
}

var_dump(42, "42", false, null);
var_dump(0.1 + 0.2);

print_r(["id" => 7, "tags" => ["php", "web"]]);
echo "\n";
Outputcompiled & run with real PHP
echo: [42]  type: int
echo: [3.5]  type: float
echo: [42]  type: string
echo: [1]  type: bool
echo: []  type: bool
echo: []  type: null
int(42)
string(2) "42"
bool(false)
NULL
float(0.30000000000000004)
Array
(
    [id] => 7
    [tags] => Array
        (
            [0] => php
            [1] => web
        )

)
Your turn

Add var_dump(PHP_INT_MAX + 1);. What type comes back, and why? (An int that overflows silently becomes a float.)

ToolShows type?false / nullUse it for
echoNoPrints nothingReal output to the user
print_r($x)NoPrints nothingA readable look at an array; print_r($x, true) returns the text instead of printing it
var_dump($x)Yes, plus string lengthbool(false) / NULLDebugging — always tells you exactly what you have
get_debug_type($x)Returns the type name"bool" / "null"Error messages and logs (PHP 8+; older code uses gettype())
Money is not a float
Because floats are binary approximations, never store prices as 19.99. Store whole cents as an int (1999) or use the bcmath extension's string arithmetic. Payment code that adds floats eventually loses a cent.
03

Type juggling and declare(strict_types=1)

When an operator or function gets the "wrong" type, PHP converts it for you. This is type juggling. "10" + 5 is 15 because + is arithmetic, so the numeric string becomes a number. "10" . 5 is "105" because . is string joining, so the int becomes a string. The operator decides the type, not the values.

phpmain.php
<?php
var_dump("10" + 5);      // numeric string -> int
var_dump("1.5" + 1);     // numeric string -> float
var_dump("10" . 5);      // int -> string
var_dump(7 + true);      // true -> 1
var_dump("3" * "4");     // both converted

function double(int $n): int
{
    return $n * 2;
}
var_dump(double("21")); // coercive mode: "21" is converted to 21
Outputcompiled & run with real PHP
int(15)
float(2.5)
string(3) "105"
int(8)
int(12)
int(42)
Your turn

Try var_dump(double(4.0));. A float with no fractional part is accepted and converted. Now try double("abc") — even the default mode refuses a non-numeric string.

Automatic conversion hides bugs: a form field "21" silently becomes 21 today and a "21 apples" becomes trouble tomorrow. Putting declare(strict_types=1); as the first statement of a file switches that file to strict mode: scalar arguments and return values must already have the declared type. The single allowed widening is int to float.

Error you will hit

TypeError under strict_types

php
<?php
declare(strict_types=1);

function double(int $n): int
{
    return $n * 2;
}

echo double("5");
Fatal error: Uncaught TypeError: double(): Argument #1 ($n) must be of type int, string given, called in main.php on line 9 and defined in main.php:4
Stack trace:
#0 main.php(9): double('5')
#1 {main}
  thrown in main.php on line 4
Why PHP said that

In strict mode PHP no longer converts "5" to 5. The message names the function, the parameter (#1 ($n)), the type it wanted and the type it got, and the stack trace shows the call on line 9. It is thrown as a TypeError, so it can be caught (Module 08).

The fix

Convert at the edge of your program, on purpose, where the data comes in — then pass real ints around.

php
<?php
declare(strict_types=1);

function double(int $n): int
{
    return $n * 2;
}

$input = "5";              // e.g. from a form or the command line
echo double((int) $input); // 10
In real jobs
Laravel, Symfony and most modern packages put declare(strict_types=1); at the top of every file, and coding standards enforce it. Strict mode is per file and applies to calls made from that file. Arithmetic like "10" + 5 still juggles; strict mode only governs function arguments and return values.
Error you will hit

TypeError: Unsupported operand types — a non-numeric string in arithmetic

php
<?php
$quantity = "three";
echo $quantity * 2;
Fatal error: Uncaught TypeError: Unsupported operand types: string * int in main.php:3
Stack trace:
#0 {main}
  thrown in main.php on line 3
Why PHP said that

Juggling only works when the string looks like a number. PHP 8 refuses to guess what "three" means and throws a TypeError — older PHP silently used 0. A string with a numeric start, like "3 items", still gives a result plus a Warning: A non-numeric value encountered.

The fix

Validate input before doing maths with it. is_numeric() tells you whether a string is a clean number.

php
<?php
$quantity = "3";
if (is_numeric($quantity)) {
    echo $quantity * 2; // 6
}
04

Constants: const and define

A constant is a name for a value that never changes. It has no $, is written in UPPER_SNAKE_CASE by convention, and is global. There are two ways to make one: const is a language keyword resolved when the file compiles, so it must be at the top level (or inside a class); define() is a function call that runs at runtime, so it can sit inside an if and use a computed name.

phpmain.php
<?php
const TAX_RATE = 0.18;
const APP_NAME = "Shop";
const SIZES = ["S", "M", "L"];     // constants can hold arrays

define("MAX_ITEMS", 10);
if (!defined("DEBUG")) {
    define("DEBUG", false);        // define() can be conditional
}

echo APP_NAME, " allows ", MAX_ITEMS, " items\n";
echo "Tax on 200: ", 200 * TAX_RATE, "\n";
echo "Largest size: ", SIZES[2], "\n";
var_dump(DEBUG);

// built-in and "magic" constants
echo PHP_INT_MAX, "\n";
echo "This is line ", __LINE__, "\n";
Outputcompiled & run with real PHP
Shop allows 10 items
Tax on 200: 36
Largest size: L
bool(false)
9223372036854775807
This is line 18
Your turn

Move the echo of __LINE__ down one line and predict the new output. Magic constants like __LINE__, __FILE__ and __DIR__ change depending on where they are written.

Error you will hit

Undefined constant — usually a missing $

php
<?php
$maxUsers = 50;
echo MAX_USERS;
Fatal error: Uncaught Error: Undefined constant "MAX_USERS" in main.php:3
Stack trace:
#0 {main}
  thrown in main.php on line 3
Why PHP said that

A bare name without $ is read as a constant. No constant MAX_USERS was defined, so PHP 8 throws an Error. (PHP 7 used to guess you meant the string "MAX_USERS" and carry on — a notorious source of silent bugs.)

The fix

Either you forgot the $ on a variable, or you forgot to define the constant.

php
<?php
const MAX_USERS = 50;
echo MAX_USERS;
Constants cannot be reassigned
Writing TAX_RATE = 0.2; is a parse error — a constant is not a variable. Calling define() twice with the same name gives Warning: Constant MAX_ITEMS already defined and keeps the first value. Inside classes you will use class constants (self::VERSION) and enums instead (Modules 06–07).
05

Operators: ., ==, ===, <=>, ?? and ?:

The arithmetic operators are the usual + - * /, plus % (remainder) and ** (power). What makes PHP different is that strings join with a dot ., never with +, and that there are two equality operators.

phpmain.php
<?php
$first = "Ada";
$greeting = "Hello, " . $first;   // . joins strings
$greeting .= "!";                  // .= appends
echo $greeting, "\n";

echo 2 ** 10, "\n";                // power
echo 7 / 2, "\n";                  // / gives a float when needed

$n = 5;
$n *= 3;                           // compound assignment
$n++;                              // increment
echo "n = $n\n";

// the spaceship: -1, 0 or 1 — used for sorting
echo 3 <=> 7, " ", 7 <=> 7, " ", 9 <=> 7, "\n";

// logical operators short-circuit
$loggedIn = false;
echo ($loggedIn && print("never runs")) ? "yes" : "no", "\n";
Outputcompiled & run with real PHP
Hello, Ada!
1024
3.5
n = 16
-1 0 1
no

== compares after juggling, === compares type and value

== (loose) converts both sides to a common type first, then compares. === (strict) is true only if both values have the same type and the same value. PHP 8 made == much saner — a number and a non-numeric string are now compared as strings, so 0 == "abc" is finally false — but it still has surprises. Default to === and !==.

phpmain.php
<?php
$pairs = [
    ['1', 1],
    ['1', '01'],
    ['10', '1e1'],
    [0, 'abc'],
    [null, false],
    [[], false],
    [0, ''],
];

foreach ($pairs as [$a, $b]) {
    $loose = var_export($a == $b, true);
    $strict = var_export($a === $b, true);
    echo str_pad(json_encode($a) . ' vs ' . json_encode($b), 16),
         str_pad("==  $loose", 13), "===  $strict\n";
}
Outputcompiled & run with real PHP
"1" vs 1        ==  true     ===  false
"1" vs "01"     ==  true     ===  false
"10" vs "1e1"   ==  true     ===  false
0 vs "abc"      ==  false    ===  false
null vs false   ==  true     ===  false
[] vs false     ==  true     ===  false
0 vs ""         ==  false    ===  false

Two numeric strings are compared as numbers by ==, which is why "10" == "1e1". That is a real security bug when comparing password hashes or tokens — always use === (or hash_equals() for secrets).

?? and ?: — the two "or else" operators

$a ?? $b (null coalescing) gives $a unless it is null or undefined — and it never warns about an undefined variable or array key. $a ?: $b (the short ternary, "Elvis") gives $a unless it is falsy — so 0, "" and "0" also fall through to $b. Pick by asking: is 0 a valid value here?

phpmain.php
<?php
$settings = ['perPage' => 0, 'title' => ''];

echo $settings['perPage'] ?? 20, "\n";   // 0: the key exists and is not null
echo $settings['perPage'] ?: 20, "\n";   // 20: 0 is falsy
echo $settings['theme'] ?? 'light', "\n"; // missing key, no warning
echo $settings['title'] ?: 'Untitled', "\n";

$age = 20;
echo $age >= 18 ? "adult" : "minor", "\n"; // the full ternary
Outputcompiled & run with real PHP
0
20
light
Untitled
adult
Visualize?? versus ?: on the same valueStep 1 / 5
<?php
$qty = 0;
$a = $qty ?? 5;
$b = $qty ?: 5;
$c = $missing ?? 5;
echo "$a $b $c";
Line 2

$qty holds the int 0 — a real value, but a falsy one.

Variables now
$qty0
All 5 steps as a table
StepLineWhat happenedVariables now
12$qty holds the int 0 — a real value, but a falsy one.$qty = 0
23?? only asks "is it null?" 0 is not null, so $a gets 0.$qty = 0 $a = 0
34?: asks "is it truthy?" 0 is falsy, so $b gets the fallback 5.$qty = 0 $a = 0 $b = 5
45$missing was never assigned. ?? treats that as null without a warning, so $c is 5.$qty = 0 $a = 0 $b = 5 $c = 5
56Interpolate the three values.
and / or are not && / ||
PHP also has and, or and xor, but they bind more loosely than =. $ok = true and false; assigns true to $ok and then throws the and result away. Use && and || in conditions; you will only see or in old idioms like ... or die().
06

Division, intdiv and %

PHP has no separate integer-division operator. / returns an int only when both sides are ints and the result is exact; otherwise it returns a float. For whole-number division use intdiv(), which truncates toward zero. % is the integer remainder, and its sign follows the left operand. For float remainders use fmod().

phpmain.php
<?php
var_dump(6 / 2);          // exact: int
var_dump(7 / 2);          // not exact: float
var_dump(intdiv(7, 2));   // whole part only
var_dump(intdiv(-7, 2));  // truncates toward zero, not down
var_dump(7 % 3, -7 % 3);  // remainder takes the sign of the left side
var_dump(fmod(7.5, 2));   // remainder for floats

// the classic use of intdiv and %: split minutes into h:mm
$minutes = 135;
printf("%d:%02d\n", intdiv($minutes, 60), $minutes % 60);

// % 2 tells even from odd
foreach ([4, 7] as $n) {
    echo $n, $n % 2 === 0 ? " is even\n" : " is odd\n";
}
Outputcompiled & run with real PHP
int(3)
float(3.5)
int(3)
int(-3)
int(1)
int(-1)
float(1.5)
2:15
4 is even
7 is odd
Your turn

Convert 4000 seconds into hours, minutes and seconds with intdiv and %. Expected: 1:06:40.

Error you will hit

DivisionByZeroError: Modulo by zero

php
<?php
$items = 10;
$perPage = 0;   // e.g. a bad config value
echo $items % $perPage;
Fatal error: Uncaught DivisionByZeroError: Modulo by zero in main.php:4
Stack trace:
#0 {main}
  thrown in main.php on line 4
Why PHP said that

Since PHP 8, dividing by zero with /, % or intdiv() throws DivisionByZeroError (the message says "Division by zero" for / and intdiv). PHP 7 returned false or INF with a warning and carried on.

The fix

Guard the divisor before dividing — or catch DivisionByZeroError if zero is truly exceptional (Module 08).

php
<?php
$items = 10;
$perPage = 0;
echo $perPage > 0 ? $items % $perPage : 0;
07

Casting and truthiness

A cast converts a value on purpose: (int), (float), (string), (bool), (array). Casts never warn and never throw — which is both their strength and their danger: (int) "abc" quietly gives 0. The function forms intval(), floatval(), strval() and boolval() do the same thing.

phpmain.php
<?php
var_dump((int) "42");       // 42
var_dump((int) "12abc");    // 12: reads the leading digits
var_dump((int) "abc");      // 0: nothing numeric
var_dump((int) 3.99);       // 3: truncates, does not round
var_dump((int) round(3.99)); // 4: round first if you mean it
var_dump((float) "1.5e3");  // 1500
var_dump((string) 7.0);     // "7"
var_dump(intval("ff", 16)); // 255: intval can take a base
var_dump(is_numeric("12abc"), is_numeric(" 12"), is_numeric("1e3"));
Outputcompiled & run with real PHP
int(42)
int(12)
int(0)
int(3)
int(4)
float(1500)
string(1) "7"
int(255)
bool(false)
bool(true)
bool(true)

Every value also has a truthiness: what it becomes in an if or under (bool). There are exactly eight falsy values; everything else is truthy — including "false", "0.0" and " ".

phpmain.php
<?php
$falsy = [
    'false' => false, '0' => 0, '0.0' => 0.0, '-0.0' => -0.0,
    '""' => "", '"0"' => "0", '[]' => [], 'null' => null,
];
$truthy = [
    '"false"' => "false", '"0.0"' => "0.0", '" "' => " ",
    '-1' => -1, '[0]' => [0], '"00"' => "00",
];

foreach ($falsy + $truthy as $label => $value) {
    echo str_pad($label, 10), var_export((bool) $value, true), "\n";
}
Outputcompiled & run with real PHP
false     false
0         false
0.0       false
-0.0      false
""        false
"0"       false
[]        false
null      false
"false"   true
"0.0"     true
" "       true
-1        true
[0]       true
"00"      true

The string "0" is falsy but "0.0" and "00" are truthy. When a form sends "0", if ($qty) treats it like "no quantity". Compare explicitly instead.

Quick check

What does var_dump((int) "7 days" + (int) true . ""); print?

08

Null handling: isset, empty, ?? and ??=

null means "no value". A variable is null when you assign null, when you unset() it, or when it was never assigned. The tools for checking it differ in one important way: whether they warn on something undefined.

CheckTrue whenWarns if undefined?
isset($x)$x exists and is not nullNo
$x === null / is_null($x)$x is nullYes
empty($x)$x is undefined or falsy (0, "", "0", []…)No
$x ?? $defaultReturns $default if $x is null or undefinedNo
phpmain.php
<?php
$user = ['name' => 'Asha', 'nickname' => null, 'visits' => 0];

var_dump(isset($user['name']));      // true
var_dump(isset($user['nickname']));  // false: exists but is null
var_dump(array_key_exists('nickname', $user)); // true: the key is there
var_dump(empty($user['visits']));    // true: 0 is "empty"

// ??= assigns only if the left side is null or undefined
$user['nickname'] ??= $user['name'];
$user['visits'] ??= 100;             // 0 is not null: unchanged
echo "{$user['nickname']} has {$user['visits']} visits\n";

$city = null;
echo "City: " . ($city ?? "unknown") . "\n";

unset($user['visits']);
var_dump(isset($user['visits']));
Outputcompiled & run with real PHP
bool(true)
bool(false)
bool(true)
bool(true)
Asha has 0 visits
City: unknown
bool(false)
Your turn

Change $user['visits'] ??= 100; to use ?: instead ($user['visits'] = $user['visits'] ?: 100;). What is printed now, and which one is right for a visit counter?

The nullsafe operator ?->
For objects, $order?->customer?->email returns null as soon as any link in the chain is null, instead of crashing. You will use it from Module 06 onward.
Dynamic typing
Variables have no fixed type; each value does. A variable can hold an int, then a string.
Type juggling
PHP converting a value to the type an operator or function needs, e.g. "10" + 5 becoming 15.
strict_types
declare(strict_types=1); as a file's first statement: scalar arguments and return values must match their declared types, or a TypeError is thrown.
Loose vs strict comparison
== compares after type juggling; === requires the same type and value.
Spaceship operator
$a <=> $b returns -1, 0 or 1; the building block of custom sorting.
Null coalescing
$a ?? $b: $a unless it is null or undefined, with no warning. ??= assigns only in that case.
Falsy
A value that becomes false in a condition: false, 0, 0.0, -0.0, "", "0", [], null.
Constant
A global name without $ whose value cannot change, made with const (compile time) or define() (runtime).
Quick check

Which expression is true in PHP 8?

Frequently asked questions

What is the difference between == and === in PHP?
== compares two values after converting them to a common type, so "1" == 1 is true. === compares type and value, so "1" === 1 is false. Use === by default; it avoids the type-juggling surprises that == still has, such as "10" == "1e1" being true.
What does declare(strict_types=1) do?
It makes PHP stop converting scalar arguments and return values for function calls made from that file. Passing the string "5" to an int parameter then throws a TypeError instead of silently becoming 5. It must be the first statement in the file and applies only to that file.
What is the difference between var_dump, print_r and echo?
echo prints a value as text for real output. print_r prints arrays and objects in a readable form but hides types. var_dump prints the type and value of anything, including false and null, which makes it the right tool for debugging.

Finish the PHP handbook, then get hired

Sit the exam for your certificate, run your resume through the ATS checker, and see the jobs that ask for exactly this.

Check my resume
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.