---
title: 'Variables and Data Types'
source: 'https://academia.sh/en/courses/php-fundamentals/variables-and-data-types'
course: 'PHP Fundamentals'
language: en
updated: '2026-10-21T05:03:34+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - "Shows by measurement that a variable's type changes with the assigned value without a signal"
    - 'Verifies by counting how exceeding the integer limit and division change the type'
    - 'Tells apart that reading an undefined variable gives a warned null, not a zero value'
    - "Shows with a two-file setup that type declarations follow the strict types mode of the caller's file"
---

# Variables and Data Types

How the type of a value in PHP can change without a signal: assignment, the integer limit, floating-point numbers, casts, the undefined variable, and how type declarations behave under strict types mode.

The Syntax and Tags lesson measured that a file either never runs or is cut while running; in both cases there was a signal: an exit code, a status code, an exception. This lesson goes down to where the signal never arrives: the value runs, execution continues, but the type of the value may have changed. The question is whether the type changes without a signal.

In PHP a variable is a name that starts with `$` and needs no type declaration. The Programming Fundamentals course (M01/K02) established the concepts of variable and type; here only the way PHP treats those concepts is measured. This arrangement is called loose typing. To read the type, `get_debug_type()` is used: it gives names such as `int`, `float`, `string`, `bool`, `array` and `null`. The same method is followed in every measurement: the file sets up an error handler, the handler increases a counter at every notice, and the counter is printed at the end of the file. If the counter is zero, the language has said nothing.

## Type Depends on the Value

In PHP the type is a property not of the variable but of the value it carries at that moment. The same name `$x` can carry an integer, a string, an array and a null one after another, and the language says nothing about it. The file below makes four assignments and reads the type after each one.

```php
<?php
// assignment.php — the same name carries four different types in a row, no signal arrives
$notices = 0;
set_error_handler(function (int $level) use (&$notices): bool {
    $notices++;
    return true;
});

$x = 5;
echo get_debug_type($x), "\n";
$x = 'five';
echo get_debug_type($x), "\n";
$x = [1, 2];
echo get_debug_type($x), "\n";
$x = null;
echo get_debug_type($x), "\n";
echo 'notices: ', $notices, "\n";
```

```
int
string
array
null
notices: 0
```

**LE16.** Four assignments produced four different types and the notice counter stayed at zero. This is the most basic consequence of loose typing: to say the type of a value by looking at a variable, every assignment up to that line has to be traced. What is measured is not the freedom itself but that it is marked by no signal. The channel of this lesson is silent.

## The Integer Limit

An `int` value has an upper limit, and the constant `PHP_INT_MAX` gives it. The value of the constant depends on the build and is not printed in this lesson; what is measured is what happens when the limit is exceeded. The file below adds one to the limit and reads the type before and after the result.

```php
<?php
// overflow.php — exceeding the integer limit silently changes the type
$warnings = 0;
set_error_handler(function (int $level) use (&$warnings): bool {
    $warnings++;
    return true;
});

$limit = PHP_INT_MAX;
$next = $limit + 1;
echo get_debug_type($limit), ' -> ', get_debug_type($next), "\n";
echo 'reported warnings: ', $warnings, "\n";
```

```
int -> float
reported warnings: 0
```

**LE17.** One more than the limit did not throw an error, did not report a warning and was not truncated: the value became a `float` and the operation continued. The line `reported warnings: 0` in the output shows that no signal arrived. The result is not wrong, it is of another kind: a value stored as a `float` can no longer tell all integers apart. The `integer overflow` row in the core table had placed this result in the silent channel; the measurement confirmed it.

## Floating-Point Numbers and Division

A change of type does not happen only at the limit. When the division operator `/` divides two integers, the type of the result depends on whether the quotient comes out exact. Floating-point numbers, on the other hand, cannot store most decimal fractions exactly. The file below measures three things: the result type of division, whether the sum `0.1 + 0.2` equals `0.3`, and the type of the integer division `intdiv()`.

```php
<?php
// fraction.php — division changes the type, floating point does not store exactly
$divisions = [
    '6 / 3'        => 6 / 3,
    '7 / 2'        => 7 / 2,
    'intdiv(7, 2)' => intdiv(7, 2),
];
foreach ($divisions as $expression => $result) {
    printf("%-14s%-7s%s\n", $expression, get_debug_type($result), var_export($result, true));
}

$sum = 0.1 + 0.2;
echo '0.1 + 0.2 === 0.3: ', var_export($sum === 0.3, true), "\n";
echo 'difference within 1e-9: ', var_export(abs($sum - 0.3) < 1e-9, true), "\n";
```

```
6 / 3         int    2
7 / 2         float  3.5
intdiv(7, 2)  int    3
0.1 + 0.2 === 0.3: false
difference within 1e-9: true
```

**LE18.** `6 / 3` gave an `int` and `7 / 2` a `float`: the result type of the same operator changed with the values of the operands. The sum `0.1 + 0.2` did not come out strictly equal to `0.3`, but the difference between them is very small. Both are silent; using a threshold instead of equality in a floating-point comparison is the programmer's job. If exact division is wanted, there is `intdiv()`, whose type is always `int`.

## Boolean Conversion

Which values count as false when a value is converted to a boolean in a condition or with `(bool)` is another face of the subject of types. The file below converts eleven values.

```php
<?php
// truthiness.php — which values count as false
$values = [
    '0'       => 0,
    '0.0'     => 0.0,
    "''"      => '',
    "'0'"     => '0',
    "'0.0'"   => '0.0',
    "' '"     => ' ',
    "'false'" => 'false',
    '[]'      => [],
    '[0]'     => [0],
    'null'    => null,
    '-1'      => -1,
];
foreach ($values as $name => $value) {
    printf("%-9s%s\n", $name, var_export((bool) $value, true));
}
```

```
0        false
0.0      false
''       false
'0'      false
'0.0'    true
' '      true
'false'  true
[]       false
[0]      true
null     false
-1       true
```

**LE19.** Zero, the empty string, `'0'`, the empty array and `null` counted as false; `'0.0'`, a single space, the string `'false'`, an array holding one element and `-1` counted as true. The difference between `'0'`, which counts as false, and `'0.0'`, which counts as true, shows that the conversion looks at the form of the value, not its meaning. None of them produces a notice.

## Number Notations

The same integer can be written in several forms in source code: hexadecimal, binary, octal, and with underscores for readability. All of them produce the same type, `int`. The file below puts six notations side by side and prints their types and values; what is measured is whether the notation affects the type.

```php
<?php
// notation.php — number notations produce the same type
$notations = [
    '0x1F'      => 0x1F,
    '0b101'     => 0b101,
    '0o17'      => 0o17,
    '017'       => 017,
    '1_000_000' => 1_000_000,
    '1e3'       => 1e3,
];
foreach ($notations as $notation => $value) {
    printf("%-10s%-6s%s\n", $notation, get_debug_type($value), var_export($value, true));
}
```

```
0x1F      int   31
0b101     int   5
0o17      int   15
017       int   15
1_000_000 int   1000000
1e3       float 1000.0
```

**LE20.** Five notations gave `int`; only `1e3` became a `float`, because exponent notation always produces a floating-point number and its type does not change even when its value equals an integer. `017`, which starts with `0`, was read as octal, that is, it became `15`; someone who takes the leading zero for decoration silently produces a wrong value. This class of notation is also silent.

## Null Checks

There are three ways to tell that a variable has no value, and the three give different results: `isset()`, `empty()` and `??`. To measure the differences, the same five values are given to all three. The fifth variable has never been defined; none of the checks produces a warning because of it, which is a result worth measuring in itself.

```php
<?php
// null_checks.php — isset, empty and ?? answer different questions
$notices = 0;
set_error_handler(function (int $level) use (&$notices): bool {
    $notices++;
    return true;
});

$zero = 0;
$blank = '';
$none = null;
$filled = 'a';

printf("%-7s%-7s%-7s%s\n", 'name', 'isset', 'empty', '??');
foreach (['zero', 'blank', 'none', 'filled', 'never'] as $name) {
    $value = $$name ?? null;
    printf(
        "%-7s%-7s%-7s%s\n",
        $name,
        var_export(isset($$name), true),
        var_export(empty($$name), true),
        var_export($value, true)
    );
}
echo 'notices: ', $notices, "\n";
```

```
name   isset  empty  ??
zero   true   true   0
blank  true   true   ''
none   false  true   NULL
filled true   false  'a'
never  false  true   NULL
notices: 0
```

**LE21.** `isset()` answers only the question "defined and not `null`": `true` for `0` and the empty string, `false` for `null` and for undefined. `empty()` asks the opposite question and also counts `0` and the empty string as empty. `??` carries the value of the left side or `null`. The undefined `$never` produced no notice in any of the three checks; only a direct read produces the warning. Choosing the wrong check leads to logic errors such as counting `0` as "missing", and these errors are reported to no one.

## Casts

The type can be changed at the programmer's wish just as it changes as the result of an operation. Writing a type name in parentheses converts the value to that type; this is called a cast. The file below runs six conversions and prints their results together with their types. The inputs include a partly numeric string, a string with no number at all, and a fractional number.

```php
<?php
// cast.php — casts change the result and give no signal
$casts = [
    '(int) "12abc"'   => (int) '12abc',
    '(int) "abc"'     => (int) 'abc',
    '(int) 3.99'      => (int) 3.99,
    '(bool) "0"'      => (bool) '0',
    '(string) false'  => (string) false,
    '(float) "1e3"'   => (float) '1e3',
];

foreach ($casts as $expression => $result) {
    printf("%-18s%-8s%s\n", $expression, get_debug_type($result), var_export($result, true));
}
```

```
(int) "12abc"     int     12
(int) "abc"       int     0
(int) 3.99        int     3
(bool) "0"        bool    false
(string) false    string  ''
(float) "1e3"     float   1000.0
```

**LE22.** The numeric prefix of the string was taken, the rest was thrown away, and nothing was said about it. `"12abc"` became `12`, `"abc"` became `0`, and `3.99` became `3` by truncation. There is no warning on any line of the output; a cast is a way of saying "I know what I am asking", and the language adds no signal to it. The programmer wrote the conversion, but the language does not say whether the result is lossless.

## Undefined Variable

Reading a variable before any value has been assigned to it is a separate case. M09/K01 measured the question of what stands in for what was not written: there, a zero value took the place of an unassigned value. In PHP an undefined variable does not give a zero value: it gives the null value `null` and reports an `E_WARNING`. The file below prints only the level name in the error handler; the message text is not printed because it depends on the version.

```php
<?php
// undefined.php — an undefined variable gives null and reports E_WARNING
set_error_handler(function (int $level): bool {
    echo 'reported: ', $level === E_WARNING ? 'E_WARNING' : 'other', "\n";
    return true;
});

$result = $missing;
echo 'value: ', var_export($result, true), "\n";
echo 'type: ', get_debug_type($result), "\n";
```

```
reported: E_WARNING
value: NULL
type: null
```

**LE23.** The value is `null`, the type is `null` and the operation continued; but this time a warning arrived. The channel of the undefined variable is warning: it is heard, it does not halt. A variable that carries `null` instead of `0` or the empty string leaves a path open to another surprise in later operations; this leads to the conversion rules that are the subject of the Operators lesson.

## Type Declarations

Loose typing can be limited by writing a type on a parameter. Writing `int $n` asks for the parameter to be an integer. The language meets this request in two ways: if the value fits, it accepts it; for one that does not fit, it either coerces or rejects it. The file below tests three inputs: a string that looks like an integer, a fractional number, and a string that carries a non-numeric suffix.

```php
<?php
// type.php — int parameter: '5' is coerced, 1.5 gives E_DEPRECATED, '5x' gives TypeError
set_error_handler(function (int $level): bool {
    echo 'reported: ', $level === E_DEPRECATED ? 'E_DEPRECATED' : 'other', "\n";
    return true;
});

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

foreach (['5', 1.5, '5x'] as $input) {
    try {
        $result = double_it($input);
        echo var_export($input, true), ' -> ', $result, "\n";
    } catch (TypeError $error) {
        echo var_export($input, true), ' -> ', get_class($error), "\n";
    }
}
```

```
'5' -> 10
reported: E_DEPRECATED
1.5 -> 2
'5x' -> TypeError
```

**LE24.** Three inputs were heard through three different channels. `'5'` silently became `5` and the result was `10`. Because `1.5` is fractional, `E_DEPRECATED` was reported and the value ran truncated. `'5x'` carries a non-numeric suffix, so it threw `TypeError`. A type declaration is therefore a rule, not a guarantee: the rule coerces the values inside its scope, rejects those outside it, and warns for those in between.

## The Bounding Measurement

The mechanism that turns coercion off is the `declare(strict_types=1)` declaration, and the real limit of the measurement is whom it binds. **LE25.** Strict types mode applies to the caller's file, not to the file of the called function. A two-file setup shows this: two function files, one with the strict declaration and one without, carry the same function with an `int` parameter; two caller files send `'5'` to it.

```php
<?php
// function_loose.php — a function with an int parameter defined in a file without the mode
function double_loose(int $n): int
{
    return $n * 2;
}
```

```php
<?php
// function_strict.php — a function defined in a file that declares strict types mode
declare(strict_types=1);

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

```php
<?php
// caller_strict.php — calls the function in the file without the mode from a strict file
declare(strict_types=1);
require 'function_loose.php';

try {
    echo double_loose('5'), "\n";
} catch (TypeError $error) {
    echo get_class($error), "\n";
}
```

```
TypeError
```

```php
<?php
// caller_loose.php — calls the function in the strict file from a file without the mode
require 'function_strict.php';

echo double_strict('5'), "\n";
```

```
10
```

When `'5'` was sent from the strict file to a function in a file without the mode, `TypeError` arrived; when the same value went from a file without the mode to a function declared strict, the output was `10`. The rule works in the file where the call is made, not in the file where the function is located. For this reason, looking at the parameter of a function does not tell how the call will behave: the same function coerces or rejects depending on which file it is called from.

## Summary

- In PHP the type depends on the value: the same variable can carry four types in a row and no notice arrives; the channel of this lesson is silent.
- When the integer limit is exceeded the value becomes a `float`, the `/` operator gives an `int` or a `float` depending on whether the quotient is exact, and none of them produces a warning.
- Casts take the numeric prefix of a string such as `"12abc"`, throw the rest away and do not announce it.
- An undefined variable gives `null`, not a zero value, and reports an `E_WARNING`.
- A type declaration gives coercion for `'5'`, `E_DEPRECATED` for `1.5` and `TypeError` for `'5x'` on an `int` parameter.
- `declare(strict_types=1)` binds the caller's file: `'5'` is rejected from a strict file, and the same value is coerced to a strict function from a file without the mode.

## Next Step

It was seen that the type changes silently in assignment, in division and in casts. The Operators lesson asks the same question of operators: what do arithmetic and comparison operators say when they bring two different types together?
