Data Types #

PHP is a dynamically typed language — meaning a value’s type is determined at runtime, not when the code is written. This gives flexibility, but it also opens the door to subtle bugs: a function expecting an integer suddenly receives a string, or a comparison of two values produces an unexpected result because PHP silently converts types behind the scenes. Since PHP 7, the language has increasingly moved toward stricter type safety — with type declarations on parameters and return values, declare(strict_types=1), union types, intersection types, all the way to never and mixed in PHP 8. This article covers all PHP data types in depth: their characteristics, the implicit conversion traps that frequently cause bugs, and how PHP’s modern type system helps you write more reliable code.

The PHP Data Type Map #

PHP has eight basic types, plus several special types introduced in recent versions:

flowchart TD
    A[PHP Data Types] --> B[Scalar]
    A --> C[Composite]
    A --> D[Special]
    A --> E[Modern Types\nPHP 7–8]

    B --> B1[int\nWhole numbers]
    B --> B2[float\nDecimal numbers]
    B --> B3[string\nText]
    B --> B4[bool\nTrue / False]

    C --> C1[array\nCollection of values]
    C --> C2[object\nClass instance]

    D --> D1[null\nNo value]
    D --> D2[resource\nExternal handle]
    D --> D3[callable\nFunction/closure]

    E --> E1[mixed\nAny type]
    E --> E2["union: int|string"]
    E --> E3["nullable: ?string"]
    E --> E4[never\nNever returns]

Integer #

Integer is the type for whole numbers — positive, negative, and zero — without a decimal part. PHP stores integers in 64-bit on modern systems, giving a very large range.

<?php
// Decimal notation (base 10) — the most common
$positive = 42;
$negative = -273;
$zero     = 0;

// Hexadecimal notation (base 16) — prefixed with 0x
$hex      = 0x1A;       // 26 in decimal
$bigHex   = 0xFF;       // 255

// Octal notation (base 8) — prefixed with 0
$octal    = 0755;       // 493 — familiar from chmod on Linux

// Binary notation (base 2) — prefixed with 0b
$binary   = 0b11111111; // 255

// Underscores as thousands separators (PHP 7.4+) — cosmetic only
$millions = 1_000_000;
$bytes    = 0xFF_FF_FF_FF;

var_dump($positive); // int(42)
var_dump($hex);      // int(26)
var_dump($millions); // int(1000000)

Integer Limits and Overflow #

PHP doesn’t throw an error when an integer exceeds its limit — the value is automatically converted to float:

<?php
echo PHP_INT_MAX;  // 9223372036854775807 (64-bit)
echo PHP_INT_MIN;  // -9223372036854775808
echo PHP_INT_SIZE; // 8 (bytes)

// Integer overflow — automatically becomes float, not an error
$limit = PHP_INT_MAX;
var_dump($limit);       // int(9223372036854775807)
var_dump($limit + 1);   // float(9.2233720368548E+18) — not an integer anymore!

// For high-precision arithmetic (money, cryptography), use BCMath
$result = bcadd('9223372036854775807', '1');
echo $result; // 9223372036854775808 — correct as a string

Useful Integer Functions #

<?php
// Base conversion
echo decbin(255);    // "11111111"   — decimal to binary
echo decoct(255);    // "377"        — decimal to octal
echo dechex(255);    // "ff"         — decimal to hexadecimal
echo bindec('1010'); // 10           — binary to decimal

// Integer math operations
echo abs(-42);          // 42  — absolute value
echo intdiv(17, 5);     // 3   — integer division (PHP 7+)
echo fmod(17, 5);       // 2   — float modulo
echo pow(2, 10);        // 1024

// Check whether a value is an integer
var_dump(is_int(42));     // true
var_dump(is_int(42.0));   // false
var_dump(is_int('42'));   // false
var_dump(is_numeric('42')); // true — includes numeric strings

Float #

Float (also called double) stores numbers with a decimal part. Its implementation follows the IEEE 754 double-precision standard, which has important implications for comparisons.

<?php
$pi       = 3.14159265358979;
$negative = -2.718;
$scientific = 1.5e3;    // 1500.0 — scientific notation
$tiny     = 2.5e-4;     // 0.00025

echo PHP_FLOAT_MAX;      // 1.7976931348623E+308
echo PHP_FLOAT_MIN;      // 2.2250738585072E-308 (smallest positive value)
echo PHP_FLOAT_EPSILON;  // 2.2204460492503E-16 (float accuracy)

var_dump($pi);       // float(3.14159265358979)
var_dump($scientific); // float(1500)

The Float Precision Trap #

This is one of the most frequent and most confusing sources of bugs:

<?php
// ANTI-PATTERN: comparing floats with ==
$a = 0.1 + 0.2;
var_dump($a == 0.3);          // bool(false) — SURPRISING!
var_dump($a);                 // float(0.30000000000000004)

// Why? Because 0.1 and 0.2 can't be represented exactly
// in binary floating point — just like 1/3 can't be exact in decimal.

// CORRECT: use an epsilon for float comparisons
function floatEquals(float $a, float $b, float $epsilon = PHP_FLOAT_EPSILON): bool
{
    return abs($a - $b) < $epsilon;
}

var_dump(floatEquals(0.1 + 0.2, 0.3)); // bool(true)

// Or use round() before comparing
var_dump(round(0.1 + 0.2, 10) === round(0.3, 10)); // bool(true)
Never store money values as floats. A value like 14999.99 can be stored as 14999.989999999999 due to floating point precision. For financial calculations, store values in their smallest unit as an integer (e.g. cents or whole rupiah without decimals), or use the BCMath / GMP extensions for arbitrary precision.
<?php
// ANTI-PATTERN: money calculations with floats
$price  = 19.99;
$qty    = 3;
$total  = $price * $qty;
echo $total; // 59.97 — happens to be right, but not always

$discount = 0.1; // 10%
echo $total - ($total * $discount); // 53.973 — precision may be off

// CORRECT: store in the smallest unit (e.g. cents)
$priceCents = 1999;   // Rp 19.99 stored as 1999 cents
$qtyCents   = 3;
$totalCents = $priceCents * $qtyCents; // 5997 cents = Rp 59.97
echo $totalCents / 100;               // display: 59.97

// Or use BCMath for full precision
$priceBc = '19.99';
$totalBc = bcmul($priceBc, '3', 2); // '59.97' — 2-decimal precision

Special Float Values #

<?php
// Infinity and NaN
$inf     = INF;
$negInf  = -INF;
$nan     = NAN;

var_dump(is_infinite(INF));      // true
var_dump(is_infinite(1.0));      // false
var_dump(is_nan(NAN));           // true
var_dump(is_nan(sqrt(-1)));      // true — negative square root produces NaN
var_dump(is_finite(42.0));       // true
var_dump(is_finite(INF));        // false

// Operations that produce INF
var_dump(log(0));    // float(-INF)
var_dump(1 / 0.0);   // float(INF) — not an error!

String #

A PHP string is a sequence of bytes — not a sequence of Unicode characters. This important difference matters when working with multibyte text like Indonesian, Arabic, or Mandarin.

<?php
// Single quotes — no interpolation, no escape sequences (except \\ and \')
$s1 = 'Hello, $name!';        // literal text, $ is not interpolated
$s2 = 'First line\nSecond';   // \n doesn't become a newline

// Double quotes — variable interpolation and escape sequences active
$name = "Budi";
$s3 = "Hello, $name!";        // Hello, Budi!
$s4 = "First line\nSecond";   // \n becomes a newline

// Escape sequences that work in double quotes:
// \n  — newline         \t  — tab
// \r  — carriage return \$  — literal dollar sign
// \\  — backslash       \"  — literal double quote
// \0  — null byte       \u{1F600} — Unicode codepoint (PHP 7+)

echo "\u{1F600}"; // 😀

Important String Operations #

<?php
$text = "  Hello, PHP World!  ";

// String length — in BYTES, not characters
echo strlen($text);         // 22

// Length in multibyte characters (UTF-8)
echo mb_strlen($text);      // 22 (same if all ASCII)
echo mb_strlen("日本語");    // 3  — three characters
echo strlen("日本語");       // 9  — nine bytes (UTF-8: 3 bytes per kanji)

// Trim — remove whitespace
echo trim($text);           // "Hello, PHP World!"
echo ltrim($text);          // "Hello, PHP World!  " (left only)
echo rtrim($text);          // "  Hello, PHP World!" (right only)

// Case transformations
echo strtolower("HELLO");    // "hello"
echo strtoupper("hello");    // "HELLO"
echo ucfirst("hello world"); // "Hello world"
echo ucwords("hello world"); // "Hello World"

// For multibyte (non-ASCII) text, use the mb_ prefix
echo mb_strtolower("HÉLLO"); // "héllo" — correct
echo strtolower("HÉLLO");    // may be wrong depending on locale

// Search and replace
echo strpos("Hello PHP", "PHP");    // 6 — first position found
echo str_contains("Hello PHP", "PHP");  // true  (PHP 8+)
echo str_starts_with("Hello", "He");    // true  (PHP 8+)
echo str_ends_with("Hello", "lo");      // true  (PHP 8+)
echo str_replace("PHP", "World", "Hello PHP"); // "Hello World"

// Splitting and joining
$csv   = "apple,mango,orange";
$fruits  = explode(",", $csv);         // ["apple", "mango", "orange"]
$joined  = implode(" | ", $fruits);    // "apple | mango | orange"

// Substrings
echo substr("Hello PHP", 6);         // "PHP"
echo substr("Hello PHP", 0, 5);      // "Hello"
echo substr("Hello PHP", -3);        // "PHP" (from the end)

Strings as Character Arrays #

<?php
$text = "Hello";

// Access characters one by one like an array
echo $text[0];    // H
echo $text[1];    // e
echo $text[-1];   // o (from the end, PHP 7.1+)

// Iterate character by character
for ($i = 0; $i < strlen($text); $i++) {
    echo $text[$i] . " "; // H e l l o
}

// WARNING: this is per-byte access, not per-Unicode-character!
$unicode = "日本";
echo $unicode[0]; // first byte of 日 — not the whole character!
// For multibyte, use mb_substr()
echo mb_substr($unicode, 0, 1); // "日" — the correct first character

Boolean #

Boolean has only two values: true and false. The spelling isn’t case-sensitive (TRUE, True, true are all valid), but community convention uses lowercase.

<?php
$active   = true;
$inactive = false;

var_dump($active);    // bool(true)
var_dump($inactive);  // bool(false)

// Booleans in conditional contexts
if ($active) {
    echo "Active";
}

// Conversion to string
echo var_export(true, true);  // 'true'
echo var_export(false, true); // 'false'
echo (string) true;           // "1"
echo (string) false;          // "" (empty string!)

// this surprises many people:
echo true;   // 1
echo false;  // (no output — empty string)

Conversion to Boolean — Falsy Values #

Understanding which values count as false when converted to boolean is essential for writing correct conditions:

<?php
// All of these values count as FALSE:
var_dump((bool) false);    // false — boolean false
var_dump((bool) 0);        // false — integer zero
var_dump((bool) 0.0);      // false — float zero
var_dump((bool) -0.0);     // false — negative float zero
var_dump((bool) "");       // false — empty string
var_dump((bool) "0");      // false — string "0" (a common trap!)
var_dump((bool) []);       // false — empty array
var_dump((bool) null);     // false — null

// All other values count as TRUE, including:
var_dump((bool) -1);       // true  — non-zero negative integer
var_dump((bool) 0.1);      // true  — any positive float
var_dump((bool) "false");  // true! — non-empty string, even though it says "false"
var_dump((bool) "0.0");    // true! — string "0.0" isn't "0"
var_dump((bool) [0]);      // true  — array with one element
var_dump((bool) new stdClass()); // true — objects are always true

Array #

PHP arrays are extremely flexible data structures — they can act as lists, dictionaries, stacks, queues, or any combination. Every PHP array is fundamentally an ordered map (a map that preserves insertion order).

<?php
// Indexed array (integer indices starting at 0)
$fruits = ["apple", "mango", "orange"];

// Associative array (string indices)
$profile = [
    "name"  => "Budi",
    "age"   => 28,
    "city"  => "Jakarta",
];

// Mixed array — valid but best avoided
$mixed = [
    0       => "zero",
    "one"   => 1,
    2       => "two",
    "three" => 3,
];

// Multidimensional array
$table = [
    ["id" => 1, "name" => "Laptop",  "price" => 15000000],
    ["id" => 2, "name" => "Monitor", "price" => 5000000],
    ["id" => 3, "name" => "Keyboard","price" => 800000],
];

// Access
echo $fruits[0];          // apple
echo $profile["name"];    // Budi
echo $table[1]["name"];   // Monitor

// Modification
$fruits[] = "grape";      // append at the end
$fruits[0] = "red apple"; // change an element
unset($fruits[1]);         // remove an element (indices are NOT reindexed!)

Array Data Types and Essential Functions #

<?php
$numbers = [5, 3, 8, 1, 9, 2, 7, 4, 6];

// Information
echo count($numbers);           // 9
echo array_sum($numbers);       // 45
echo min($numbers);             // 1
echo max($numbers);             // 9

// Transformations — produce a new array, don't modify the original
$doubled = array_map(fn($n) => $n * 2, $numbers);
$evens   = array_filter($numbers, fn($n) => $n % 2 === 0);
$total   = array_reduce($numbers, fn($carry, $n) => $carry + $n, 0);

// Sorting — modifies the original array
$copy = $numbers;
sort($copy);                  // ascending: [1,2,3,4,5,6,7,8,9]
rsort($copy);                 // descending: [9,8,7,6,5,4,3,2,1]

$profile = ["name" => "Budi", "city" => "Bandung", "age" => 28];
asort($profile);              // sort by value, preserving keys
ksort($profile);              // sort by key

// Searching
echo in_array(8, $numbers);         // true
echo array_search(8, $numbers);     // 2 (index)

// Slicing and splicing
$slice = array_slice($numbers, 2, 4); // 4 elements starting at index 2
array_splice($numbers, 1, 2, [10, 11]); // replace 2 elements starting at index 1

// Merging
$a = [1, 2, 3];
$b = [4, 5, 6];
$merged     = array_merge($a, $b);    // [1,2,3,4,5,6]
$mergedSpread = [...$a, ...$b];       // [1,2,3,4,5,6] — spread operator

// Unique and flip
$duplicates = [1, 2, 2, 3, 3, 3];
$unique     = array_unique($duplicates);  // [1, 2, 3]
$flip       = array_flip(["a" => 1, "b" => 2]); // [1 => "a", 2 => "b"]

Object #

An object is an instance of a class — a package of related data (properties) and behavior (methods). PHP has had full OOP support since version 5.

<?php
class Product
{
    public function __construct(
        private int    $id,
        private string $name,
        private float  $price,
        private int    $stock = 0,
    ) {}

    public function getId(): int       { return $this->id; }
    public function getName(): string  { return $this->name; }
    public function getPrice(): float  { return $this->price; }
    public function getStock(): int    { return $this->stock; }

    public function isAvailable(): bool
    {
        return $this->stock > 0;
    }

    public function formattedPrice(): string
    {
        return 'Rp ' . number_format($this->price, 0, ',', '.');
    }
}

$laptop = new Product(1, "ProBook Laptop", 15_000_000, 5);

echo $laptop->getName();      // ProBook Laptop
echo $laptop->formattedPrice(); // Rp 15.000.000
var_dump($laptop->isAvailable()); // bool(true)

// Check the object type
var_dump($laptop instanceof Product); // bool(true)
echo get_class($laptop);              // "Product"

stdClass and Object Casting #

PHP has stdClass as a generic class with no built-in properties or methods — useful for creating ad-hoc objects:

<?php
// Create a stdClass directly
$config = new stdClass();
$config->host     = 'localhost';
$config->port     = 3306;
$config->database = 'app_db';

echo $config->host; // localhost

// Cast an array to an object
$data = ["name" => "Budi", "age" => 28];
$obj  = (object) $data;
echo $obj->name; // Budi
echo $obj->age;  // 28

// Cast an object to an array (useful for serialization)
$arr = (array) $obj;
echo $arr['name']; // Budi

// json_decode produces stdClass by default
$json = '{"name":"Budi","city":"Jakarta"}';
$user = json_decode($json);          // stdClass
echo $user->name;                    // Budi

$userArr = json_decode($json, true); // associative array
echo $userArr['name'];               // Budi

Null #

null represents the absence of a value — a variable that exists but is empty, either intentionally or because it hasn’t been filled yet. There’s only one null value: null itself.

<?php
$notSetYet  = null;           // explicit null
$also       = NULL;           // case-insensitive, but avoid the capitals
$automatic;                   // an unassigned variable is also null (but will warn)

var_dump($notSetYet);         // NULL
var_dump(is_null($notSetYet)); // bool(true)
var_dump(isset($notSetYet));  // bool(false)

// Null coalescing — the idiomatic way to handle null
$name  = null;
$result = $name ?? "Guest";  // "Guest" — because $name is null

// Nullable type — a type hint that accepts null or a specific type
function findUser(?int $id): ?array // ? means it can be null
{
    if ($id === null) return null;
    // ... find the user
    return ["id" => $id, "name" => "Budi"];
}

$user = findUser(null); // valid, returns null
$user = findUser(1);    // valid, returns an array

// Nullsafe operator (PHP 8.0+) — method chains without manual null checks
class User
{
    public ?Address $address = null;
}
class Address
{
    public ?City $city = null;
}
class City
{
    public string $name = "Jakarta";
}

$user = new User();

// ANTI-PATTERN: repeated null checks
$cityName = null;
if ($user !== null && $user->address !== null && $user->address->city !== null) {
    $cityName = $user->address->city->name;
}

// CORRECT: nullsafe operator (?->)
$cityName = $user?->address?->city?->name; // null if anything in the chain is null

Resource #

Resource is a special data type that stores a reference to an external resource — database connections, file handles, cURL connections, and the like. Resources are managed by PHP and freed automatically when no longer needed, but best practice is to close them explicitly.

<?php
// File resource
$file = fopen("data.txt", "r");
if ($file === false) {
    throw new \RuntimeException("Failed to open file");
}

var_dump(get_resource_type($file)); // string(6) "stream"
var_dump(is_resource($file));       // bool(true)

while (!feof($file)) {
    $line = fgets($file);
    echo $line;
}
fclose($file); // close the resource — free memory and the file handle

var_dump(is_resource($file)); // bool(false) — already closed

// Database connection resource (MySQLi)
$db = mysqli_connect('localhost', 'root', '', 'mydb');
var_dump(get_resource_type($db)); // string(5) "mysql link" (PHP < 8)
// In PHP 8, mysqli returns an object, not a resource

// cURL resource
$ch = curl_init('https://api.example.com/data');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
Since PHP 8.0, several extensions that used to use resources have switched to objects. For example, mysqli, GD (images), and XML now return objects instead of resources. This trend will continue in future PHP versions — resource is an old type that is gradually being replaced.

Modern PHP 7–8 Types #

As PHP evolves, its type system grows richer. Some important types and features introduced in PHP 7 and 8:

Union Types (PHP 8.0+) #

Union types allow a parameter or return value to accept more than one type:

<?php
// A function that accepts an int or string as an ID
function findProduct(int|string $id): array|null
{
    if (is_int($id)) {
        // search by numeric ID
    } else {
        // search by string SKU
    }
    return null;
}

findProduct(42);       // ✓ integer
findProduct("SKU-A");  // ✓ string
// findProduct(3.14);  // ✗ TypeError — float is not int|string

Intersection Types (PHP 8.1+) #

Intersection types ensure a value implements all of the listed interfaces:

<?php
interface Stringable
{
    public function __toString(): string;
}

interface Countable
{
    public function count(): int;
}

// The parameter must implement BOTH — Stringable AND Countable
function process(Stringable&Countable $collection): void
{
    echo "Count: " . $collection->count();
    echo "String: " . $collection;
}

mixed, never, and void #

<?php
// mixed — accepts any type (including null)
function debugDump(mixed $value): void
{
    var_dump($value);
}

// void — the function returns nothing at all
function writeLog(string $message): void
{
    file_put_contents('/tmp/app.log', $message, FILE_APPEND);
    // return; is allowed, but return $value; is an error
}

// never — the function never returns normally
// (always throws an exception or exits)
function fail(string $message): never
{
    throw new \RuntimeException($message);
    // or: exit(1);
}

// Useful as a return type for error handlers
function notFound(): never
{
    http_response_code(404);
    echo json_encode(["error" => "Not found"]);
    exit;
}

Type Declarations and strict_types #

Type declarations on parameters and return values help PHP (and IDEs) catch errors earlier:

<?php
// Without strict_types — PHP performs coercion (implicit conversion)
function add(int $a, int $b): int
{
    return $a + $b;
}

echo add(1, 2);      // 3 — OK
echo add("3", "4");  // 7 — PHP silently converts strings to int
echo add(1.9, 2.1);  // 3 — PHP truncates floats to int!

// ---

declare(strict_types=1); // enable at the first line of the file

function addStrict(int $a, int $b): int
{
    return $a + $b;
}

echo addStrict(1, 2);      // 3 — OK
// echo addStrict("3", "4"); // TypeError — string is not int
// echo addStrict(1.9, 2.1); // TypeError — float is not int

declare(strict_types=1) only applies to the file where it’s declared, not to other included files. This allows a gradual migration from legacy code that isn’t strict.


Summary #

  • PHP has 8 basic types — scalar (int, float, string, bool), composite (array, object), and special (null, resource) — plus modern types like union types, mixed, never, and void.
  • Floats aren’t accurate for money — use BCMath or store monetary values as integers (cents/whole rupiah) to avoid floating point errors.
  • PHP strings are byte-based, not character-based — for multibyte text (UTF-8, emoji, Asian characters), always use the mb_ functions like mb_strlen() and mb_substr().
  • "0" is falsy — the string "0" counts as false in a boolean context, unlike "0.0" or "false", which are true. Use === for safe comparisons.
  • The nullsafe operator ?-> shortens a chain of null checks into one elegant expression.
  • declare(strict_types=1) forces PHP to skip implicit type conversion — type errors are better caught during development than in production.
  • Union types (int|string) and nullable types (?string) allow more expressive type declarations without losing flexibility.
  • Resource is a legacy type — since PHP 8, many extensions have moved to objects. Always close resources explicitly with fclose(), curl_close(), etc. to avoid memory leaks.

← Previous: Constants   Next: Operators →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact