PHP's Type System: A Complete Reference

Identify and correctly describe every core PHP type — scalar, compound, special, and pseudo-types — so you can reason precisely about what a value or type declaration means anywhere in PHP code.

PHP is a dynamically and (optionally) strongly typed language: every value has a definite type at runtime, even though variables themselves aren't bound to a fixed type. Before writing type declarations, generics-like patterns, or reasoning about union/intersection types, you need a solid, precise reference for every type PHP recognizes — from the everyday scalars like int and string to special pseudo-types like never, mixed, self, and static that only appear in type declarations. This lesson is a foundational reference covering each one clearly.

Scalar Types: int, float, string, bool

int represents a whole number, positive or negative, stored using the platform's native integer size (typically 64-bit on modern systems). Integer literals can be written in decimal (42), hexadecimal (0x2A), octal (0o52 or legacy 052), or binary (0b101010), and PHP 7.4+ allows underscores as visual separators in numeric literals (1_000_000). float (also called double) represents a floating-point number following the IEEE 754 standard, written with a decimal point or in scientific notation (3.14, 1.5e3); like in virtually every language, floats have limited precision and should never be compared for exact equality without a tolerance.

string represents a sequence of bytes/characters — PHP strings are byte arrays under the hood, which makes them naturally binary-safe, but means multi-byte-aware operations (for UTF-8 text) need the mbstring extension's functions (mb_strlen, mb_substr, etc.) rather than the byte-oriented defaults (strlen, substr). Strings can be declared with single quotes (no interpolation, minimal escape sequences), double quotes (full variable interpolation and escape sequences like \n), or heredoc/nowdoc syntax for large blocks of text.

bool represents a truth value: exactly true or false. PHP's truthiness rules (used implicitly in if conditions and explicit (bool) casts) mark 0, 0.0, "", "0", null, and empty arrays as falsy, and essentially everything else — including the strings "false" and " " — as truthy. This is one of PHP's most commonly misunderstood behaviors and a frequent source of subtle bugs.

• int — whole numbers; decimal/hex/octal/binary literal forms; underscore separators allowed. • float (double) — IEEE 754 floating-point numbers; never compare for exact equality. • string — byte sequences; use mbstring functions for correct multi-byte/UTF-8 handling. • bool — true or false; PHP's falsy set is 0, 0.0, "", "0", null, [] — everything else is truthy.

null, array, object, resource, callable, iterable

null represents the deliberate absence of a value and has exactly one possible value, written null (case-insensitive, though lowercase is conventional). A variable that has never been assigned, has been explicitly unset(), or has been assigned null directly all report as null when checked. array is PHP's single, extremely flexible compound data structure — an ordered map that can behave as a list (sequential integer keys), a dictionary/hash map (string or mixed keys), a stack, a queue, or a matrix, all through the same array type.

object represents an instance of a class — the runtime, structured combination of properties (data) and methods (behavior). Every class you define, and every built-in class PHP provides (like DateTime or Exception), produces values of type object at the type-system level, even though PHP also lets you narrow a type declaration to a specific class name for more precision. resource is a special, largely legacy handle type representing an external resource, like an open file handle or a database connection obtained through certain older APIs (fopen() returns a resource); modern PHP extensions increasingly replace resources with proper objects (e.g. mysqli connections are now objects, not resources, as of PHP 8.1).

callable is a pseudo-type describing anything PHP can invoke as a function: a string function name ('strlen'), an array pointing to an object/method ([$obj, 'method']) or a static method (['ClassName', 'method']), an anonymous function/closure, an arrow function, or an object implementing __invoke(). iterable is a pseudo-type that accepts either a plain array or any object implementing the Traversable interface (which covers Generators and classes implementing Iterator or IteratorAggregate) — it exists specifically so a function can accept 'anything you can foreach over' without caring whether it's a concrete array or a lazy generator.

• null — the single value representing 'no value'. • array — PHP's one flexible ordered map/list/dictionary structure. • object — an instance of any class, built-in or user-defined. • resource — legacy external-handle type (file handles, some DB connections); increasingly replaced by objects. • callable — anything invocable: function name string, [obj, method] array, closure, arrow function, __invoke object. • iterable — array OR any Traversable object (Generator, Iterator, IteratorAggregate).

Pseudo-Types for Declarations: mixed, void, never, true, false

These next types only ever appear in type declarations (parameter, return, or property types) — you never create a raw value 'of type mixed'. mixed (added in PHP 8.0) means 'accepts a value of any type whatsoever', making a type declaration explicit and self-documenting even when no narrowing is possible; it's strictly more precise than simply omitting a type declaration, because it also disables type coercion complaints and signals clear intent to readers and static analyzers.

void is a return-type declaration meaning a function does not return a meaningful value — it may return; with no value, or have no return statement at all, but it cannot return $something;. never (added in PHP 8.1) is a stronger return-type declaration than void: it asserts the function never returns control to the caller at all, because it always throws an exception, calls exit()/die(), or otherwise never completes normally — useful for helper functions whose entire job is to throw, so static analyzers know code after a call to them is unreachable.

PHP 8.2 added true and false as standalone types usable in union type declarations — for example, a function that returns either a specific array on success or the literal boolean false on failure can be typed array|false, making the contract precise rather than the vague bool.

• mixed — accepts a value of any type; explicit and intentional, unlike omitting a type entirely. • void — function returns no meaningful value (bare return; or no return statement). • never — function never returns control at all (always throws/exits) — PHP 8.1+. • true / false — standalone literal-boolean types, usable in unions like array|false — PHP 8.2+.

Class-Context Pseudo-Types: self, parent, static

self refers to the exact class in which the type declaration is written, regardless of which subclass the method is actually called on — it's resolved at compile time against the defining class. parent refers to the immediate parent class of the class the declaration appears in, and is used most often inside a constructor or overridden method to reference the superclass version being extended.

static (as a return type, distinct from the static keyword used for static properties/methods or static local variables) refers to 'whatever class this method was actually called on' — resolved at runtime rather than compile time, which is what makes it correctly support fluent interfaces and inheritance. A base class method returning static will, when called on a subclass instance, be understood to return an instance of that subclass, not the base class — this is called 'late static binding' and is essential for correct builder-pattern and fluent-chaining APIs in an inheritance hierarchy.

The distinction between self and static matters specifically in inherited, non-overridden methods: if a base class method is typed to return self but is inherited (not overridden) by a subclass, callers get a type that technically says 'the base class' even though at runtime it may actually be a subclass instance — static avoids that mismatch by tracking the calling class correctly.

• self — the class where the declaration is literally written; resolved at compile time. • parent — the immediate parent class; commonly used for parent::__construct() and its type context. • static (return type) — the actual calling/runtime class, supporting correct inheritance via late static binding. • Prefer static over self for fluent/builder methods meant to be inherited correctly.

Type Reference at a Glance

mixed and iterable are used purely as type declarations here, never as a literal 'kind' of value you construct directly. resource values (like the fopen() handle) must be released with fclose() when done.

<?php
declare(strict_types=1);

$a = 42;            // int
$b = 3.14;           // float
$c = "hello";         // string
$d = true;            // bool
$e = null;            // null
$f = [1, 2, 3];       // array
$g = new stdClass();  // object
$h = fopen('php://memory', 'r'); // resource
$i = 'strlen';        // callable (as a string function name)

function process(mixed $input): iterable {
    if ($input === null) {
        throw new InvalidArgumentException('no null allowed');
    }
    yield $input;
}

Code Examples

gettype() reports PHP's own internal name for a value's type at runtime — useful for exploring exactly how PHP classifies each of these values. Expected result: integer double string boolean NULL array object self is resolved to Model at compile time no matter who calls it, while static correctly resolves to whichever class the method was actually invoked on — this is late static binding. Expected result: Model Product

Declaring fail() as never tells both human readers and static analysis tools that control never returns from that call, which lets the analyzer correctly narrow $config's type to array (not ?array) on the next line.

Common mistake: Declaring such a function as void instead of never loses this guarantee — void only promises 'no meaningful return value', not 'control never returns'.

<?php
$values = [42, 3.14, "hi", true, null, [1,2], new stdClass()];

foreach ($values as $v) {
    echo gettype($v) . PHP_EOL;
}
<?php
class Model {
    public static function create(): self {
        return new self();
    }
    public static function make(): static {
        return new static();
    }
}

class Product extends Model {}

echo get_class(Product::create()) . PHP_EOL; // Model  (self = defining class)
echo get_class(Product::make()) . PHP_EOL;   // Product (static = calling class)
<?php
function fail(string $message): never {
    throw new RuntimeException($message);
}

function loadConfig(?array $config): array {
    if ($config === null) {
        fail('Config missing'); // static analysis knows execution stops here
    }
    return $config; // analyzers know $config can't be null at this point
}

PHP's Type Landscape

Scalar and compound types describe real runtime values; the declaration-only column exists solely inside type declarations.

Scalar        Compound       Special        Declaration-only pseudo-types
  ─────         ────────       ───────        ─────────────────────────────
  int            array          null           mixed   void   never
  float          object         resource       true    false
  string                                        iterable  callable
  bool                                          self  parent  static

Important Rules

Never compare floats for exact equality — compare within an epsilon tolerance instead.

Prefer mixed explicitly over omitting a type declaration, to signal intent clearly.

Use never (not void) for helper functions whose entire purpose is to throw or exit.

Prefer static over self as a return type for any method meant to work correctly when inherited.

Treat resource as legacy — prefer APIs that return objects when a modern alternative exists.

iterable in Real APIs

Database query-result classes and ORMs commonly type their row-fetching methods as returning iterable rather than array, so they can return a memory-efficient Generator for huge result sets while still allowing callers to foreach over the result exactly as they would an array.

if ($price == "0") { /* treat as free */ } if ($price === 0 || $price === 0.0) { /* treat as free */ }

Loose comparison (==) triggers PHP's type juggling, which can produce surprising truthy/falsy matches; strict comparison (===) checks both value and type.

class Builder { public function withName(string $n): self { $this->name = $n; return $this; } } class SpecialBuilder extends Builder {} class Builder { public function withName(string $n): static { $this->name = $n; return $this; } } class SpecialBuilder extends Builder {}

With self, chaining ->withName() on a SpecialBuilder instance still types as Builder, losing access to SpecialBuilder-specific methods in a fluent chain; static preserves the actual runtime class.

What does the never return type guarantee that void does not? A. That the function returns an integer B. That the function never returns control to the caller at all (always throws or exits) C. That the function has no side effects D. That the function is static

B. That the function never returns control to the caller at all (always throws or exits) — void only means 'no meaningful return value' but the function can still return normally; never asserts execution never completes normally at all.

Which pseudo-type accepts either a plain array or any Traversable object like a Generator? A. mixed B. callable C. iterable D. resource

C. iterable — iterable is specifically array | Traversable, letting a function accept anything usable in a foreach loop.

Why does static behave differently from self as a return type when a method is inherited? A. static is resolved at compile time against the defining class, like self B. static is resolved at runtime against the actual calling class (late static binding), while self is fixed to the defining class C. They are exact synonyms in every PHP version D. static can only be used for properties, not return types

B. static is resolved at runtime against the actual calling class (late static binding), while self is fixed to the defining class — static uses late static binding to resolve to whatever class the method was actually called on at runtime, whereas self always resolves to the class where the declaration is written.

Write a function describe(mixed $value): string that uses gettype() to return a string like "This is of type: integer". Call it with an int, a string, and null, and print each result.

<?php function describe(mixed $value): string { return "This is of type: " . gettype($value); } echo describe(42) . PHP_EOL; echo describe("hi") . PHP_EOL; echo describe(null) . PHP_EOL;

gettype() returns PHP's internal type name as a string; concatenate it into your message.