Lesson 3 of 20

Variables & Data Types

Variables Are Names for Values

A variable is a name you attach to a value so you can use it later. In PHP every variable name starts with a dollar sign, and you create one simply by assigning to it — there is no separate declaration step and no type to announce in advance.

The naming rules are short but strict. After the $, a name must start with a letter or an underscore, and may then contain letters, digits and underscores. No spaces, no hyphens, and nothing else. Most importantly, variable names are case-sensitive: $total, $Total and $TOTAL are three unrelated variables. This is the source of a large share of "Undefined variable" warnings, especially for students moving over from a language or an editor that was more forgiving.

PHP is dynamically typed, which means a variable's type is decided by whatever you last put in it, and it can change. Assigning $x = 5 and then $x = "hello" is perfectly legal. That flexibility is convenient and is also the reason the next few sections exist: when the language never forces you to be explicit, you have to be explicit on purpose.

Choose names that describe the thing, not its type. $studentCount tells a reader something; $sc, $data2 and $temp tell them nothing. You will read your own code far more often than you write it, and a form-handling script with fifteen well-named variables is a completely different experience from one with fifteen abbreviations.

Example
<?php
$studentName = "Ananya Sharma";   // string
$rollNumber  = 118;                // int
$percentage  = 87.5;               // float
$hasPaidFees = true;               // bool
$subjects    = ["Physics", "Maths"]; // array
$guardian    = null;                // null - deliberately "no value yet"

// Assignment copies the value for everything except objects
$a = 10;
$b = $a;    // $b is its own copy
$b = 20;
echo $a;    // still 10

// Case sensitivity is real
$total = 100;
// echo $Total;   // Warning: Undefined variable $Total

// Legal and illegal names
$_internal = 1;      // fine
$user2     = 2;      // fine
// $2user  = 3;      // Parse error - cannot start with a digit
// $user-name = 4;   // Parse error - hyphen is the minus operator
Notes
  • Pick one naming style and hold to it across the project. PHP code in the wild mostly uses $camelCase for variables and ClassNames in PascalCase, which is what the PSR-12 style guide assumes.

The Types You Will Actually Use

PHP has a small set of built-in types, and four of them cover most of your day: string, int, float and bool. Two more — array and object — hold collections and structured things. Then there is null, which is not a value so much as the explicit absence of one.

The one that deserves a warning is float. Floating-point numbers are stored in binary, and many ordinary decimal fractions cannot be represented exactly in binary — in the same way that one-third cannot be written exactly in decimal. So 0.1 + 0.2 does not produce exactly 0.3, and a comparison like if ($sum == 0.3) can be false even when everything looks right on screen. This is not a PHP defect; it happens in JavaScript, Java and C too.

For a shopping cart or fee calculator, that matters. The standard fix is to keep money as whole numbers in the smallest unit — store paise as an integer, so ₹499.50 is stored as 49950 — and divide by 100 only when you display it. Every addition then happens in exact integer arithmetic, and rounding errors cannot accumulate across a hundred line items.

  • string — text of any length, from one character to an entire HTML page
  • int — whole numbers, positive or negative, with no fractional part
  • float — numbers with a decimal point; fine for averages and percentages, risky for money
  • bool — exactly two values, true and false
  • array — an ordered map holding many values, covered in its own lesson
  • object — an instance of a class, covered in the object-oriented lessons
  • null — the deliberate absence of a value; a variable that has never been assigned is also null-like, but produces a warning when read
Notes
  • PHP_INT_MAX tells you the largest integer this machine can hold — 9223372036854775807 on any 64-bit system, which is every modern computer. Go past it and PHP silently converts the value to a float, losing precision. This is why phone numbers and Aadhaar-style identifiers should be stored as strings, not integers: they are labels you never do arithmetic on, and leading zeros matter.

Type Juggling: PHP's Most Famous Trap

When you combine values of different types, PHP quietly converts one of them so the operation can proceed. This is called type juggling, and it is genuinely useful — data arriving from a form is always a string, so $_POST['qty'] * 50 working without a manual conversion saves a lot of noise.

It becomes a trap with the loose equality operator ==, which converts before comparing. PHP 8 improved this considerably: comparing a number with a string that is not numeric now converts the number to a string instead of the other way round, so 0 == "hello" is false in PHP 8 where it was true in PHP 7. Much old advice about this on the internet describes the PHP 7 behaviour and is now wrong.

Surprises still remain, though, because two numeric strings are still compared as numbers. "10" == "1e1" is true, since both mean ten. "1" == "01" is true for the same reason. If you are comparing verification codes, coupon codes or OTP-style values, that is a real bug waiting to happen — a code of "01" would be accepted for "1".

The fix is a single extra character. === compares value and type, with no conversion at all, so "1" === "01" is false and 0 === "hello" is false. Make === your default and use == only when you have consciously decided that you want the conversion. The same applies to !== versus !=.

Example
<?php
// Juggling that helps you
$qty = "3";              // came from a form, so it is a string
echo $qty * 50;          // 150 - converted to a number automatically
echo "Total: " . 150;    // "Total: 150" - number converted to string

// Loose comparison, PHP 8 behaviour
var_dump(0 == "hello");   // false in PHP 8 (was true in PHP 7!)
var_dump("1" == "01");    // true  - both are numeric strings
var_dump("10" == "1e1");  // true  - both mean the number ten
var_dump(null == false);  // true
var_dump("" == null);     // true

// Strict comparison - no conversion, no surprises
var_dump("1" === "01");   // false
var_dump(0 === "hello");  // false
var_dump(1 === 1.0);      // false - int is not float

// A real bug this prevents
$submittedCode = "01";
$correctCode   = "1";
if ($submittedCode == $correctCode)  { /* WRONG - this runs */ }
if ($submittedCode === $correctCode) { /* correct - this does not */ }
Notes
  • The same rule applies to in_array() and array_search(), which use loose comparison unless you pass true as the third argument. in_array("1", [1, 2, 3], true) is false, while without the flag it is true. Pass the strict flag by default.

Converting on Purpose, and Checking What You Have

When you want a conversion, do it explicitly so the intent is visible in the code. Casting is written as the target type in brackets: (int), (float), (string), (bool), (array). Casting a string that starts with digits keeps the digits and drops the rest, so (int) "25 years" is 25 — convenient, and also a silent way to accept nonsense, so cast only when you have already decided the value should be a number.

For input that came from a user, validation is better than casting. filter_var() with FILTER_VALIDATE_INT returns the integer if the whole string really is an integer, and false if it is not, letting you reject bad input instead of quietly turning it into zero. The form-handling lesson uses this pattern throughout.

PHP also gives you a family of is_* functions for checking a type before you act on it. Be careful with one distinction: is_numeric("12abc") is false, but (int) "12abc" is 12. They answer different questions — "is this entirely a number?" versus "what number can I squeeze out of this?" — and mixing them up produces bugs that only show up on messy real-world input.

Finally, know PHP's rules for truthiness, because if ($value) uses them. 0, 0.0, "", "0", the empty array, null and false are all falsy; everything else is truthy. The one that surprises people is the string "0", which is falsy even though it contains a character. A form field where a user legitimately typed 0 will be treated as "not filled in" by naive code.

Example
<?php
// Explicit casts
var_dump((int) "42");        // int(42)
var_dump((int) "25 years");  // int(25)  - trailing text dropped
var_dump((int) "years 25");  // int(0)   - nothing numeric at the start
var_dump((float) "3.14");    // float(3.14)
var_dump((string) 99);       // string(2) "99"
var_dump((bool) "0");        // bool(false)  <- the surprising one

// Validating instead of casting - the right choice for user input
$age = filter_var($_POST['age'] ?? '', FILTER_VALIDATE_INT);
if ($age === false) {
    echo "Please enter a valid age.";
}

// Checking types
var_dump(is_int(5));            // true
var_dump(is_string("5"));       // true
var_dump(is_numeric("3.14"));   // true  - numeric string
var_dump(is_numeric("12abc"));  // false
var_dump(gettype(3.0));         // "double" - PHP's old name for float

// Truthiness
foreach ([0, "0", "", "false", [], null, "0.0"] as $v) {
    var_dump((bool) $v);
}
// false, false, false, TRUE, false, false, TRUE
Notes
  • gettype() returns "double" for a float and "boolean" for a bool, for historical reasons. Use get_debug_type() instead — it returns the modern names float, bool, int, string, and the actual class name for objects, which is far more useful in an error message.

Constants: Values That Must Not Change

A constant is a name bound to a value that cannot be reassigned. Use one whenever a value is a fact about your application rather than something that varies during a request: a GST rate, a maximum upload size, a site name, the number of results per page.

There are two ways to declare one. const is the modern form; it is handled while the file is being compiled, so it must appear at the top level of a file or inside a class and its value has to be a fixed expression. define() is a function call, so it runs when execution reaches it, which lets you define a constant conditionally or build its name at runtime. For ordinary configuration, const reads better.

Two practical differences from variables. Constants have no dollar sign — you write MAX_UPLOAD_MB, not $MAX_UPLOAD_MB. And they do not interpolate inside double-quoted strings; "Rate: TAX_RATE" prints the literal words. You have to concatenate: "Rate: " . TAX_RATE. Forgetting this produces output that looks like a typo rather than an error.

Class constants are the version you will use most once you start writing classes, because they keep the name attached to the thing it belongs to: Order::STATUS_PAID is self-documenting in a way that a loose global STATUS_PAID never is.

Example
<?php
// Two ways to declare
const SITE_NAME   = "Campus Portal";
const RESULTS_PER_PAGE = 20;
define('MAX_UPLOAD_MB', 5);

echo SITE_NAME;                    // no dollar sign
echo "Welcome to " . SITE_NAME;    // must concatenate
// echo "Welcome to SITE_NAME";    // prints the literal text!

// Constants cannot be reassigned
// SITE_NAME = "Something else";   // Parse error

// Built-in constants worth knowing
echo PHP_VERSION;      // "8.3.6" or similar
echo PHP_EOL;          // the correct line ending for this operating system
echo PHP_INT_MAX;      // 9223372036854775807
echo __DIR__;          // folder this file lives in - vital for require paths
echo __FILE__;         // full path of this file

// Class constants keep the name where it belongs
class Order {
    const STATUS_PENDING = 'pending';
    const STATUS_PAID    = 'paid';
}
echo Order::STATUS_PAID;

// Checking whether one exists
if (defined('MAX_UPLOAD_MB')) {
    echo MAX_UPLOAD_MB;
}
Notes
  • __DIR__ is worth remembering right now. Using require __DIR__ . '/config.php' instead of require 'config.php' makes the path relative to the file doing the including, so your code keeps working no matter which script was requested first. Mysterious "failed to open stream: No such file or directory" errors are usually this.

Scope: Why Your Function Cannot See That Variable

PHP functions do not automatically see variables from outside themselves. This surprises people coming from JavaScript, where an inner function can read outer variables freely. In PHP, a function starts with a clean slate; the only things it knows about are its parameters and whatever it creates internally.

This is a feature, not a limitation. It means you can read a function on its own and know exactly what it depends on, and you cannot accidentally break a function by renaming an unrelated variable elsewhere in the file. The correct way to give a function outside data is to pass it as a parameter and return a result.

PHP does offer the global keyword and the $GLOBALS array as escape hatches, and you will see them in older code. Avoid both in new code. A function that reaches out and grabs globals is impossible to test on its own, and it turns any change to that global into a change that could break code anywhere in the project. If you find yourself needing global, the honest answer is nearly always that the value should have been a parameter.

The one genuinely useful special case is static. A static variable inside a function is created once and keeps its value between calls, while staying invisible to the rest of the program. It is handy for counting invocations or caching an expensive result that never changes within a single request — a database connection object, for example.

Example
<?php
$siteName = "Campus Portal";

function showHeader() {
    // echo $siteName;   // Warning: Undefined variable $siteName
}

// The right way: pass it in
function showHeaderProperly(string $siteName): void {
    echo "<h1>" . htmlspecialchars($siteName) . "</h1>";
}
showHeaderProperly($siteName);

// The escape hatch you should mostly avoid
function showHeaderBadly(): void {
    global $siteName;      // works, but hides the dependency
    echo $siteName;
}

// static: remembered between calls, invisible outside
function nextTicketNumber(): int {
    static $counter = 0;   // runs only on the first call
    $counter++;
    return $counter;
}
echo nextTicketNumber();   // 1
echo nextTicketNumber();   // 2
echo nextTicketNumber();   // 3
Notes
  • "Between calls" means within one request only. The next page load starts a brand new PHP process with $counter back at 0. If you need a number that keeps increasing across requests — an invoice number, for instance — it has to live in a database.
Ask AI