Type Juggling & Casting
Reviewed & published by Brayan K
PHP is loosely typed, which means it will quietly convert types behind your back. This lesson shows exactly when juggling happens, how to take control with explicit casts, how PHP 8 made comparisons safer, and how strict_types protects your functions.
Part of the free PHP course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
- See where implicit juggling kicks in
- Force conversions with (int), (float), (string), (bool)
- Understand == vs === after the PHP 8 changes
- Enable strict_types to catch type bugs early
- Validate user input with filter_var and ctype_*
- Avoid the silent (int) "abc" => 0 trap
1️⃣ Implicit Juggling
Juggling happens automatically whenever an operator meets the "wrong" type. Arithmetic operators juggle towards numbers; the . operator juggles towards strings; boolean contexts juggle towards true/false. PHP 8 tightened the rules: adding a string that doesn't start as a number now throws a TypeError rather than guessing.
<?php
// "Type juggling" = PHP silently converting a value's type for an operation.
// It happens because PHP is loosely typed.
echo "5" + 3 . "\n"; // 8 — the string "5" is juggled to int 5
// A LEADING-numeric string still works, with a warning on stderr:
// "Warning: A non-numeric value encountered"
echo "5 apples" + 3; // 8 — PHP reads the 5 and warns about the rest
echo "\n";
// A string with no leading number is a TypeError in PHP 8:
try {
echo "apples" + 3;
} catch (TypeError $e) {
echo "TypeError: " . $e->getMessage() . "\n"; // Unsupported operand types: string + int
}
echo 5 . 3 . "\n"; // 53 — the . operator juggles both to strings
echo true + true . "\n"; // 2 — booleans juggle to 1 + 1
echo "10" * "2" . "\n"; // 20 — numeric strings juggle to ints
?>2️⃣ Explicit Casting
When you want a conversion you control, put the target type in parentheses in front of the value: (int), (float), (string), (bool), (array). Note that (int) truncates (it never rounds) and stops at the first non-digit.
<?php
// EXPLICIT casting: you decide the conversion with (type) in front.
$s = "42.9";
var_dump((int) $s); // int(42) — truncates, does NOT round
var_dump((float) $s); // float(42.9)
var_dump((bool) $s); // bool(true) — non-empty, non-"0" string
var_dump((string) 99); // string(2) "99"
var_dump((array) "hi"); // array(1) { [0]=> string(2) "hi" }
// Casting a string that starts with digits keeps the leading number.
var_dump((int) "123abc"); // int(123)
var_dump((int) "abc123"); // int(0) — no leading digits
?>3️⃣ Loose vs Strict Comparison
The loose == operator juggles both sides to a common type before comparing — the source of PHP's most infamous surprises. PHP 8 fixed the worst of them (0 == "abc" is now false), but numeric strings still compare numerically, so "10" == "1e1" is true. The strict === sidesteps all of it.
<?php
// == juggles types before comparing. === compares value AND type.
var_dump(0 == "0"); // true — both seen as 0
var_dump(0 == ""); // false — PHP 8 fixed this old trap
var_dump(0 == "abc"); // false — PHP 8: non-numeric string is NOT 0
var_dump("1" == "01"); // true — both numeric strings -> 1
var_dump("10" == "1e1"); // true — 1e1 is 10 in scientific notation
var_dump(100 == "1e2"); // true — 1e2 is 100
// Strict === sidesteps ALL of it.
var_dump(0 === "0"); // false — int vs string
var_dump("1" === "01"); // false — different strings
?>4️⃣ Turning Off Juggling: strict_types
Add declare(strict_types=1); as the first line of a file and PHP stops auto-juggling the arguments and return values of typed functions. Pass the wrong type and you get an immediate TypeError — a bug caught now rather than three layers deep later. (An int is still accepted where a float is wanted, because that widening loses nothing.)
<?php
declare(strict_types=1);
// With strict_types on, PHP refuses to silently juggle arguments and
// return values for typed functions — bugs surface immediately.
function addTax(float $price): float {
return $price * 1.2;
}
echo addTax(10.0) . "\n"; // 12 — a real float, fine
echo addTax(10) . "\n"; // 12 — int 10 is widened to 10.0 (allowed)
// echo addTax("10"); // TypeError! a string is NOT auto-juggled here
?>5️⃣ Converting User Input Safely
Everything from a form or URL arrives as a string, so converting it is a daily task — and the place where a careless (int) bites. The robust pattern is validate, then cast: filter_var() returns the clean value or false, making "invalid" unmistakable.
<?php
// For USER INPUT (always strings), convert deliberately and validate.
$ageInput = " 29 ";
// 1) trim, 2) check it's numeric, 3) cast.
$ageInput = trim($ageInput);
if (ctype_digit($ageInput)) {
$age = (int) $ageInput;
echo "Age is $age\n"; // Age is 29
}
// filter_var is the robust, intention-revealing way to validate+convert.
$n = filter_var("42", FILTER_VALIDATE_INT);
var_dump($n); // int(42)
$bad = filter_var("nope", FILTER_VALIDATE_INT);
var_dump($bad); // bool(false) — clearly "invalid"
?>Now you try. Fill the ___ blanks with the right cast keyword.
<?php
// 🎯 YOUR TURN — fill each ___ to convert a form value safely.
$raw = "73.8";
$asInt = (___) $raw; // 👉 cast to int -> 73 (truncated)
$asFloat = (___) $raw; // 👉 cast to float -> 73.8
$asBool = (___) $raw; // 👉 cast to bool -> true
var_dump($asInt); // int(73)
var_dump($asFloat); // float(73.8)
var_dump($asBool); // bool(true)
// ✅ Expected output:
// int(73)
// float(73.8)
// bool(true)
?>🧩 Reorder Challenge
These scrambled lines should validate a string and print a result. Put them in the order that prints Valid: 50.
You must define $raw, then trim it, then validate it into $clean before you can test and print it. The === false check is essential because filter_var returns false for invalid input but could legitimately return 0 for the string "0". Output: Valid: 50.
🧠 Quick Recall
Predict the output before revealing each answer.
The first prints 8 (arithmetic juggles "5" to the int 5); the second prints 53 (the . operator juggles both to strings and concatenates).
Prints int(42). Casting to int truncates the fractional part — it does not round to 43.
Prints bool(true). Both are numeric strings, so PHP compares them numerically: 10 equals 10 (1e1 is scientific notation for 10). With === it would be false.
Common Errors (and the fix)
- (int) "abc" silently became 0 — a blind cast hides bad input. Validate with filter_var(..., FILTER_VALIDATE_INT) first.
- Two different strings compare equal — they're numeric strings under == (e.g. "1" == "01"). Use ===.
- TypeError when calling a typed function — with strict_types=1 you must pass the exact type. Cast deliberately before the call.
- "A non-numeric value encountered" / TypeError on + — PHP 8 won't add a non-numeric string. Clean and validate the value before arithmetic.
Pro Tips
- 💡 Put declare(strict_types=1); at the top of every new file. It turns silent type bugs into loud, early errors.
- 💡 Cast on purpose, compare with ===. Explicit beats implicit every time.
- 💡 Treat all input as untrusted strings. Validate with filter_var before you cast or calculate.
📋 Quick Reference — Juggling & Casting
| Expression | Result | Why |
|---|---|---|
| "5" + 3 | 8 | Juggled to numbers |
| 5 . 3 | "53" | Juggled to strings |
| (int) "42.9" | 42 | Truncates, no rounding |
| (bool) "0" | false | "0" is the only falsy string |
| 0 == "abc" | false | PHP 8 fixed this trap |
| "10" == "1e1" | true | Numeric strings compared as numbers |
| strict_types=1 | TypeError | No auto-juggling of args |
Mini-Challenge: Validate Form Fields
Write it yourself, run it on onecompiler.com/php or your own machine, then check against the expected output in the comments.
<?php
// 🎯 MINI-CHALLENGE: validate and total two form fields.
// Form data always arrives as strings. No code filled in — follow the steps.
//
// 1. $qtyRaw = "3", $priceRaw = "19.99" (both strings).
// 2. Validate $qtyRaw with filter_var(..., FILTER_VALIDATE_INT).
// 3. Validate $priceRaw with filter_var(..., FILTER_VALIDATE_FLOAT).
// 4. If BOTH are not false, compute $total = $qty * $price.
// 5. echo "Total: $" . number_format($total, 2);
// otherwise echo "Invalid input".
//
// ✅ Expected output:
// Total: $59.97
// your code here
?>🎉 Lesson Complete!
- ✅ Juggling is implicit; casting with (type) is explicit and controlled
- ✅ (int) truncates and stops at the first non-digit
- ✅ == juggles; === avoids every surprise
- ✅ declare(strict_types=1) turns type bugs into early errors
- ✅ Validate input with filter_var before casting
Practice quiz
What does echo "5" + 3; print?
- 53
- "5"3
- 8
- A TypeError
Answer: 8. The + operator juggles the numeric string "5" to the int 5, giving 8.
What does echo 5 . 3; print?
- 53
- 8
- 15
- 5.3
Answer: 53. The . concatenation operator juggles both numbers to strings and joins them: "53".
What does var_dump((int) "42.9"); output?
- int(43)
- float(42.9)
- int(0)
- int(42)
Answer: int(42). Casting to int truncates the fractional part — it does not round — so it is 42.
What does var_dump("10" == "1e1"); output?
- bool(false)
- bool(true)
- int(10)
- An error
Answer: bool(true). Both are numeric strings, so PHP compares them numerically; 1e1 is 10, so they are equal.
In PHP 8, what does var_dump(0 == "abc"); output?
- bool(false)
- bool(true)
- int(0)
- An error
Answer: bool(false). PHP 8 fixed this trap: a non-numeric string is no longer treated as 0, so the result is false.
What does var_dump(0 === "0"); output?
- bool(true)
- int(0)
- bool(false)
- string(1)
Answer: bool(false). === compares value AND type; an int and a string differ in type, so the result is false.
What does var_dump((int) "abc123"); output?
- int(123)
- int(0)
- int(456)
- string(6)
Answer: int(0). (int) reads leading digits only; "abc123" has no leading digits, so it casts to 0.
What does var_dump((bool) "0"); output?
- bool(true)
- int(0)
- string(1)
- bool(false)
Answer: bool(false). "0" is the one string that casts to false; any other non-empty string is true.
What is the difference between juggling and casting?
- They are the same thing
- Juggling is implicit; casting is explicit with (int), (float), etc.
- Casting is automatic; juggling is manual
- Juggling only works on integers
Answer: Juggling is implicit; casting is explicit with (int), (float), etc.. PHP juggles types automatically during operations; casting is a deliberate (type) conversion you control.
What does filter_var("nope", FILTER_VALIDATE_INT) return?
- 0
- null
- false
- "nope"
Answer: false. filter_var returns false for invalid input, which is far clearer than a blind (int) cast turning it into 0.
Continue this course
- Previous: Numbers & Math
- Next: Constants — Define unchanging values with const and define(), plus magic constants
- Quick reference: PHP cheat sheet › Basics
Frequently asked questions
What is type juggling in PHP?
Type juggling is PHP automatically converting a value from one type to another to complete an operation. For example "5" + 3 converts the numeric string to the integer 5 and gives 8, and the concatenation operator turns 5 . 3 into the string "53". It's convenient but implicit, which is how subtle bugs sneak in. In PHP 8, adding a non-numeric string like "5 apples" + 3 now throws a TypeError instead of silently juggling, which is a welcome safety improvement.
What is the difference between juggling and casting?
Juggling is implicit — PHP does it for you, automatically, during an operation. Casting is explicit — you write (int), (float), (string), (bool), or (array) in front of a value to force a specific conversion that you control. Prefer casting when intent matters: (int) "42.9" deliberately truncates to 42, whereas relying on juggling leaves the conversion (and its surprises) up to PHP.
What does declare(strict_types=1) do?
Placed as the very first statement in a file, declare(strict_types=1) turns off implicit argument and return-value juggling for functions that have type declarations. Passing a string to a parameter typed as int then throws a TypeError instead of being silently converted. One sanctioned exception: an int is still allowed where a float is expected, because that widening is lossless. Strict types catch whole classes of bugs early and are strongly recommended for new code.
How should I convert user input to numbers safely?
Form and query data always arrives as strings, so convert deliberately. Trim the value, then validate before casting. filter_var($v, FILTER_VALIDATE_INT) returns the integer on success or false on failure, which is far clearer than a blind (int) cast that silently turns "abc" into 0. For digit-only checks use ctype_digit(), and for floats use FILTER_VALIDATE_FLOAT. Validating up front means bad input is rejected loudly instead of corrupting your data quietly.