In PHP 8, what does mixed accept, how does it differ from no type, and what are the null, false, true, iterable and callable types?
answer
- mixed includes null, since 8.0
- ?mixed and mixed|int rejected
- mixed return needs an actual return
- null, false, true standalone in 8.2
- iterable = array|Traversable; callable not for properties
basics
~20 smixed (PHP 8.0) accepts every value including null, like an untyped parameter, but states the intent explicitly and, as a return type, requires an actual return. null, false and true are literal types usable alone since 8.2; iterable means array|Traversable; callable cannot type a property.
solid answer
~40 s`mixed` is the **top type**: any value, `null` included. For a parameter it accepts the same values as no declaration, but it records that "anything" was a decision rather than a gap. It must stand alone: `?mixed` fails (mixed already includes null) and `mixed|int` fails. As a return type it still demands a value — a bare `return;` does not compile and falling off the end throws `TypeError: ... none returned` — whereas an untyped function silently returns `null`. The literal types: `false` has been a union member since 8.0, and since **8.2** `null`, `false` and the new `true` can stand alone; `true|false` must be `bool`. `iterable` has been a compile-time alias for `array|Traversable` since 8.2. `callable` works for parameters and returns but cannot type a property, and neither can join an intersection.
code
php · 19 lines<?php
declare(strict_types=1);
function contextValue(array $context, string $key): mixed
{
if (array_key_exists($key, $context)) {
return $context[$key];
}
// without the next line PHP throws: Return value must be of type mixed, none returned
return null;
}
final class ReadOnlyCrmSync
{
public function isEnabled(): false // standalone false, PHP 8.2+
{
return false;
}
}go deeper
Recall that mixed accepts any value including null and that it cannot be made nullable or combined with other types.
Explain the return-type difference between mixed and no type, the 8.2 literal types, iterable as array|Traversable, and why callable cannot type a property.
Use mixed deliberately so an absent declaration always means unfinished typing, and replace callable properties with Closure in refactors.
Set a type-coverage policy where mixed marks a decision, literal types document fixed answers, and missing declarations are tracked as debt.
## mixed: the top type `mixed`, added in **PHP 8.0**, is, in the manual's words, equivalent to the union `object|resource|array|string|float|int|bool|null`. It is called a **top type** because every value belongs to it. Three rules follow from that: 1. It must be **standalone**. `mixed|int` fails with `Type mixed can only be used as a standalone type`, because adding a member to "everything" is redundant. 2. It cannot be marked nullable. `?mixed` fails with `Type mixed cannot be marked as nullable since mixed already includes null`. 3. As a **return type** it still means "returns a value". A bare `return;` is a compile error, and a function declared `: mixed` that reaches the end of its body throws `TypeError: foo(): Return value must be of type mixed, none returned`. ## mixed versus no declaration For a **parameter**, `mixed $value` and `$value` accept exactly the same values. The difference is information: - `mixed` says the author considered the type and decided any value is valid, for example in a logger's context payload or a cache's stored value. - No declaration may mean the same thing, or may mean the code predates types. Readers, reviewers and static analysers cannot tell. For a **return**, there is a behavioural difference too: | | `function f()` (no return type) | `function f(): mixed` | |---|---|---| | Ends without `return` | returns `null` | `TypeError`, none returned | | `return;` | returns `null` | compile error | | `return null;` | returns `null` | returns `null` | A codebase that aims for full type coverage uses `mixed` where "any value" is the truth, so that a missing declaration always signals unfinished work. ## The literal types null, false and true PHP also has types that contain exactly one value: - **`false`** has been allowed as a union member since PHP 8.0, mainly so signatures like `strpos(): int|false` can be written honestly. - Since **PHP 8.2**, `null` and `false` may be used **standalone**, and a new **`true`** type was added. A method that always returns `false`, for instance an override that disables a feature, can now say so. - `true|false` is rejected: `Type contains both true and false, bool must be used instead`. Likewise `bool|false` is redundant. - `?null` is rejected; `?false` has been accepted since 8.2, meaning `false|null`. ## iterable `iterable` (PHP 7.1) accepts an array or any object implementing `Traversable`. Since **PHP 8.2** the engine treats it as a compile-time alias for `array|Traversable`, so error messages may print that union instead of the word `iterable`, and a union that already contains `iterable` rejects `array` or `Traversable` as redundant. It cannot be part of an intersection type. ## callable and its limits `callable` accepts anything PHP can call. It works on parameters and return types, with limits worth knowing: - It **cannot type a property**: `private callable $handler;` fails with `Property ...::$handler cannot have type callable`. The usual workaround is to type the property `Closure` and convert the value with `Closure::fromCallable()` or first-class callable syntax. - It cannot be part of an **intersection** type, because intersections accept only class and interface types. - Whether a string or array is callable can depend on the scope doing the call, which is one reason property typing was never allowed. ## Quick reference | Type | Since | Standalone | Notes | |---|---|---|---| | `mixed` | 8.0 | only standalone | includes `null`; `?mixed` rejected | | `false` | 8.0 in unions | 8.2 | not with `bool` or `true` | | `true` | 8.2 | 8.2 | not with `false` or `bool` | | `null` | 8.0 in unions | 8.2 | `?null` rejected | | `iterable` | 7.1 | yes | alias for `array\|Traversable` since 8.2 | | `callable` | 5.4 | yes | not for properties, not in intersections |
- In PHP 8, how do you store a callable in a typed class property when callable itself is not allowed there?Type the property as `Closure` and convert the incoming callable with `Closure::fromCallable($callable)` or first-class callable syntax such as `$this->format(...)`. A `Closure` is an object with a fixed class, so it can be a property type, and it captures the scope at creation time, which removes the scope-dependent callability that kept `callable` off properties.
- In PHP 8.2+, why might a TypeError message print array|Traversable when the parameter was declared iterable?Since PHP 8.2, `iterable` is a compile-time alias that the engine rewrites to `array|Traversable`, and messages about composite types use the rewritten form. Reflection still reports a standalone `iterable` or `?iterable` as a named type called `iterable`, so existing reflection-based code keeps working.
saying these in an interview costs you the question
- Says ?mixed is needed to allow null in a mixed parameter.
- Thinks a function declared : mixed may end without a return and yield null.
- Believes mixed and an omitted declaration are different at the parameter check.
- Claims private callable $handler; is a valid typed property.
- Writes true|false instead of bool in a union.