Type casting — (int), intval(), settype() — differences and gotchas
Concept
PHP offers three mechanisms for converting a value to a different type: cast operators (int), (string), etc.; type conversion functions like intval(), floatval(), strval(); and the settype() function that mutates a variable in place. Understanding their differences prevents subtle bugs, especially when dealing with user input and database values.
Cast operators ((int), (float), (string), (bool), (array), (object)) are evaluated inline and produce a new value without modifying the original variable. They are the fastest mechanism — essentially compile-time hints. The cast (int) truncates floats toward zero ((int)4.9 === 4, (int)-4.9 === -4) and silently discards non-numeric content from strings ((int)'42abc' === 42, (int)'abc' === 0). This silent behaviour is a common source of bugs with user input.
intval($value, $base) is the function equivalent of (int) with one crucial addition: the $base parameter allows conversion from binary, octal, or hexadecimal string representations. intval('0x1A', 16) returns 26. Without the base parameter, intval('0x1A') returns 0 because (int) cast ignores hex prefixes in strings. intval() is slightly slower than a cast due to the function call overhead, but the difference is negligible in practice.
settype(&$var, 'int') modifies the variable in place and returns a boolean indicating success. It is functionally equivalent to $var = (int)$var but mutates rather than returning a new value. It is rarely used in modern PHP because it requires a reference and is less readable than a cast.
strval() is identical to (string) in effect. boolval() maps non-zero integers, non-empty strings (except "0"), non-empty arrays, and objects to true. The gotcha: boolval("0") is false because the string "0" is a special falsy value in PHP.
In strict mode, explicit casting to pass a mismatched type to a typed function parameter is not allowed — you must cast before the call. Casting to (array) wraps non-array values in a numeric-keyed array ((array)'hello' becomes ['hello']), and casting an object to array exposes its properties as array keys (with mangled names for private properties, e.g. \0ClassName\0propertyName).
| Mechanism | Returns new value | Mutates | Base conversion | Speed |
|---|---|---|---|---|
(int)$v | Yes | No | No | Fastest |
intval($v, $base) | Yes | No | Yes | Fast |
settype($v, 'int') | bool | Yes | No | Moderate |
Code Example
<?php
declare(strict_types=1);
// (int) cast — truncates toward zero, silent on non-numeric
var_dump((int) 4.9); // int(4)
var_dump((int) -4.9); // int(-4) — truncation, not floor
var_dump((int) '42abc'); // int(42) — silent truncation
var_dump((int) 'abc'); // int(0) — silent, no warning
var_dump((int) true); // int(1)
var_dump((int) false); // int(0)
var_dump((int) null); // int(0)
// intval() with base parameter
var_dump(intval('1A', 16)); // int(26) — hex
var_dump(intval('0b1010', 2)); // int(0) — base 2 but '0b' prefix not recognized
var_dump(intval('1010', 2)); // int(10) — binary string without prefix
var_dump(intval('17', 8)); // int(15) — octal
var_dump(intval('0x1A')); // int(0) — no base given, stops at 'x'
// Contrast: PHP 8 accepts hex literals natively
$hex = 0x1A; // int(26) — this is a literal, not a string conversion
// (float) cast
var_dump((float) '3.14abc'); // float(3.14)
var_dump((float) '1e3'); // float(1000.0)
// (string) cast
var_dump((string) 42); // string(2) "42"
var_dump((string) 3.14); // string(4) "3.14"
var_dump((string) true); // string(1) "1"
var_dump((string) false); // string(0) "" — empty string!
var_dump((string) null); // string(0) ""
// (bool) cast — the "0" gotcha
var_dump((bool) 0); // false
var_dump((bool) ''); // false
var_dump((bool) '0'); // false — special case!
var_dump((bool) '0.0'); // true — only "0" is falsy, not "0.0"
var_dump((bool) []); // false
var_dump((bool) [0]); // true — non-empty array
// settype — in-place mutation (rarely used in modern code)
$val = '42.5';
settype($val, 'float');
var_dump($val); // float(42.5)
// (array) cast
var_dump((array) 'hello'); // array(1) { [0]=> string(5) "hello" }
var_dump((array) ['a' => 1]); // unchanged
class Point {
public function __construct(
public int $x,
private int $y,
) {}
}
$p = new Point(1, 2);
$arr = (array) $p;
// Keys: 'x' (public) and "\0Point\0y" (private — mangled)
var_dump(array_keys($arr)); // ['x', "\0Point\0y"]
// Safe numeric input parsing
function parsePositiveInt(string $input): ?int
{
if (!ctype_digit($input)) {
return null;
}
$val = (int) $input;
return $val > 0 ? $val : null;
}
// filter_var is more robust for validation before casting
$userInput = ' 42 ';
$clean = filter_var(trim($userInput), FILTER_VALIDATE_INT);
if ($clean === false) {
throw new \InvalidArgumentException('Not an integer');
}Interview Q&A
Q: What is the safest way to convert user-provided string input to an integer in PHP, and why is (int)$_GET['id'] insufficient?
(int)$_GET['id'] silently converts any non-numeric input to 0, which is dangerous because 0 might be a valid sentinel value (e.g. a valid record ID depending on your schema, or a false-y value that bypasses a truthiness check). The correct approach has two steps: validate first, then convert. Use filter_var($input, FILTER_VALIDATE_INT) which returns false for non-integer strings (including floats, hex prefixes, and empty strings) and returns the integer value for valid input. Alternatively, ctype_digit($input) validates that every character is a decimal digit (rejecting signs and leading spaces), then (int) is safe. For route parameter binding, Laravel's route model binding with typed parameters handles this automatically — the framework validates and casts before your controller receives the value.
Q: Why does (string) false return "" (empty string) while (string) true returns "1", and how does this affect serialization?
PHP's boolean-to-string conversion follows the rule that true maps to "1" and false maps to "" (empty string). This asymmetry means round-tripping a boolean through a string is lossy: (bool) "" is false, so (bool)(string)false === false (correct), but (bool)(string)true === (bool)"1" === true only by coincidence. If you serialize false to a database VARCHAR column as an empty string and later read it back, (bool)"" correctly gives false. However, if you naively compare (string)$flag === 'true'/'false', you get wrong results. The correct approach for boolean serialization is $flag ? '1' : '0' for storage and $stored === '1' for reading, or using a proper TINYINT(1) column in the database (which Eloquent's boolean cast handles via $casts = ['active' => 'boolean']).
Q: When would you use settype() over a cast operator, and are there any performance differences?
settype() modifies the variable in place via a reference and returns a boolean success indicator. In practice, its main use case is legacy code that handles variables from $_POST or $_GET where the original variable location needs to be mutated rather than returning a new value. Modern PHP prefers casting ($val = (int) $val) because it is more explicit, easier to read, and does not require the caller to remember that settype mutates. Performance-wise, settype() is marginally slower than a cast because it involves a function call and a reference dereference, though the difference is unmeasurable in application code. The only case where settype() is genuinely cleaner is inside a array_walk callback where you want to mutate array values: array_walk($data, fn(&$v) => settype($v, 'int')) — though even this is cleaner as array_map(fn($v) => (int)$v, $data).