Числовые строки (Numeric Strings)
Что такое числовая строка
В PHP числовая строка (numeric string) -- это строка, содержимое которой может быть интерпретировано как число. PHP различает три вида таких строк:
| Вид | Описание | Пример |
|---|---|---|
| Numeric string | Полностью числовая строка | '42', '3.14', '1e5' |
| Leading-numeric string | Начинается с числа, но содержит не-числовые символы | '42abc', '3.14xyz' |
| Non-numeric string | Не начинается с числа | 'abc', '', 'true' |
<?php
declare(strict_types=1);
// Numeric strings — fully numeric content
$a = '42'; // Integer numeric string
$b = '3.14'; // Float numeric string
$c = '1e5'; // Scientific notation — numeric string
$d = '0x1A'; // NOT numeric (since PHP 7.0)
$e = '0b1010'; // NOT numeric
$f = '0o17'; // NOT numeric
// Leading-numeric strings
$g = '42abc'; // Leading-numeric — starts with number
$h = '3.14xyz'; // Leading-numeric
// Non-numeric strings
$i = 'abc'; // Non-numeric
$j = ''; // Non-numeric (empty string)
$k = ' '; // Non-numeric (whitespace only — NOT numeric)
Пробелы (Leading/Trailing Whitespace)
PHP допускает пробелы в начале и конце числовой строки. Строка с пробелами вокруг числа все равно считается числовой.
<?php
declare(strict_types=1);
// Leading whitespace is allowed
var_dump(is_numeric(' 42')); // true
var_dump(is_numeric("\t42")); // true
var_dump(is_numeric("\n42")); // true
// Trailing whitespace is allowed
var_dump(is_numeric('42 ')); // true
var_dump(is_numeric("42\n")); // true
// Both sides
var_dump(is_numeric(' 42 ')); // true
var_dump(is_numeric(" 3.14 ")); // true
var_dump(is_numeric(" 1e5 ")); // true
// Whitespace inside — NOT numeric
var_dump(is_numeric('4 2')); // false
var_dump(is_numeric('3. 14')); // false
// Only whitespace — NOT numeric
var_dump(is_numeric(' ')); // false
var_dump(is_numeric('')); // false
Важно: Начальные и конечные пробелы (
\t,\n,\r,\v,\f,) допускаются в числовых строках. Пробелы внутри числа -- нет.
Правила преобразования строки в число
Неявное приведение (Type Juggling)
<?php
// Without strict_types — PHP auto-converts
// Numeric strings → exact number
var_dump('42' + 0); // int(42)
var_dump('3.14' + 0); // float(3.14)
var_dump('1e5' + 0); // float(100000)
var_dump(' 42 ' + 0); // int(42) — whitespace trimmed
// Leading-numeric strings → number + E_WARNING (PHP 8.0+)
var_dump('42abc' + 0); // int(42) + Warning
var_dump('3.14xyz' + 0); // float(3.14) + Warning
// Non-numeric strings → 0 + TypeError (PHP 8.0+)
// var_dump('abc' + 0); // TypeError in PHP 8.0+
// var_dump('' + 0); // TypeError in PHP 8.0+
Эволюция поведения по версиям PHP
| Ситуация | PHP 7.x | PHP 8.0+ |
|---|---|---|
'42abc' + 0 |
42 + Notice |
42 + Warning |
'abc' + 0 |
0 + Warning |
TypeError |
'' + 0 |
0 (тихо) |
TypeError |
'42' + 0 |
42 |
42 |
<?php
declare(strict_types=1);
// In strict mode — NO implicit conversions for function parameters
function add(int $a, int $b): int
{
return $a + $b;
}
// add('42', '10'); // TypeError! String given, int expected
// But arithmetic operators still do juggling even with strict_types
$result = '42' + '10'; // int(52) — arithmetic operators always juggle
Ловушка экзамена:
declare(strict_types=1)влияет только на вызовы функций и возвращаемые значения. Арифметические операторы (+,-,*,/) всегда выполняют type juggling, даже сstrict_types=1!
Явное приведение типов
<?php
declare(strict_types=1);
// (int) cast — truncates, no rounding
var_dump((int) '42'); // int(42)
var_dump((int) '42.9'); // int(42) — truncated!
var_dump((int) '42abc'); // int(42)
var_dump((int) 'abc'); // int(0)
var_dump((int) ''); // int(0)
var_dump((int) ' 42 '); // int(42)
// (float) cast
var_dump((float) '3.14'); // float(3.14)
var_dump((float) '42'); // float(42.0)
var_dump((float) '1e5'); // float(100000.0)
var_dump((float) '3.14abc'); // float(3.14)
var_dump((float) 'abc'); // float(0.0)
// intval() with base
var_dump(intval('0x1A', 16)); // int(26)
var_dump(intval('0b1010', 2)); // int(10)
var_dump(intval('0755', 8)); // int(493)
var_dump(intval('42', 10)); // int(42)
// settype()
$val = '42.5';
settype($val, 'int');
var_dump($val); // int(42)
Сравнение числовых строк
Оператор == (Loose Comparison)
PHP 8.0 кардинально изменил поведение сравнения строк с числами.
<?php
declare(strict_types=1);
// PHP 8.0+ behavior (sane comparison)
var_dump(0 == 'foo'); // false (was true in PHP 7!)
var_dump(0 == ''); // false (was true in PHP 7!)
var_dump(0 == ' '); // false
var_dump(0 == 'null'); // false
var_dump(0 == "\n"); // false
// Numeric string comparisons — compared as numbers
var_dump('42' == '42.0'); // true — both numeric, compared as numbers
var_dump('42' == '042'); // true — 42 == 42
var_dump('1e1' == '10'); // true — 10.0 == 10
var_dump('10' == '10.0'); // true
// String vs int — numeric string compared as number
var_dump(42 == '42'); // true
var_dump(42 == '42.0'); // true
var_dump(42 == ' 42 '); // true — whitespace trimmed
// Non-numeric strings compared as strings
var_dump('foo' == 'foo'); // true
var_dump('foo' == 'bar'); // false
PHP 7 vs PHP 8: Критические изменения
<?php
declare(strict_types=1);
// PHP 7.x: string compared to 0 → string cast to int → 0 == 0 = true
// PHP 8.0: 0 is cast to string "0", then "0" == "foo" = false
// BEFORE PHP 8.0 (dangerous behavior):
// 0 == 'any_string' → true (string cast to 0)
// '' == null → true
// 0 == null → true
// '' == false → true
// PHP 8.0+ (fixed):
var_dump(0 == 'any_string'); // false — string is not numeric, no conversion
var_dump(0 == '0'); // true — '0' IS numeric, compared as numbers
var_dump(0 == ''); // false
var_dump('' == null); // true — special case, both "empty"
var_dump(0 == null); // true — null cast to 0
Оператор === (Strict Comparison)
<?php
declare(strict_types=1);
// Strict comparison — no type juggling
var_dump(42 === '42'); // false — different types
var_dump(42 === 42); // true
var_dump('42' === '42'); // true
var_dump(0 === false); // false — int vs bool
var_dump(0 === null); // false — int vs null
var_dump('' === false); // false — string vs bool
var_dump('' === null); // false — string vs null
Правило: Всегда используйте
===для сравнений, если не нужно намеренное приведение типов. Это устраняет все неожиданности.
Spaceship Operator <=>
<?php
declare(strict_types=1);
// Returns -1, 0, or 1
var_dump('42' <=> '10'); // int(1) — numeric comparison: 42 > 10
var_dump('42' <=> '42'); // int(0)
var_dump('10' <=> '42'); // int(-1)
// Useful in sorting
$values = ['10', '9', '100', '2'];
usort($values, fn(string $a, string $b): int => $a <=> $b);
// Result: ['2', '9', '10', '100'] — compared as numbers!
// Force string comparison
usort($values, fn(string $a, string $b): int => strcmp($a, $b));
// Result: ['10', '100', '2', '9'] — compared as strings (lexicographic)
Таблица поведения операторов
<?php
declare(strict_types=1);
// Complete behavior table for PHP 8.0+
// Arithmetic operators: always convert to number
var_dump('5' + '3'); // int(8)
var_dump('5.5' + '3'); // float(8.5)
var_dump('5' - '3'); // int(2)
var_dump('5' * '3'); // int(15)
var_dump('5' / '3'); // float(1.666...)
var_dump('5' % '3'); // int(2)
var_dump('5' ** '3'); // int(125)
// String concatenation: always converts to string
var_dump(42 . 'px'); // string(4) "42px"
var_dump(3.14 . '!'); // string(5) "3.14!"
// Bitwise operators: convert to int (for strings)
var_dump('5' & '3'); // string(1) "1" — bitwise AND on chars!
var_dump('5' | '3'); // string(1) "7"
var_dump(5 & 3); // int(1) — bitwise AND on ints
var_dump(5 | 3); // int(7)
// Comparison operators: type juggling with == , strict with ===
var_dump('42' == 42); // true
var_dump('42' === 42); // false
var_dump('42' != 42); // false
var_dump('42' !== 42); // true
is_numeric()
Функция is_numeric() проверяет, является ли значение числом или числовой строкой.
<?php
declare(strict_types=1);
// Integers
var_dump(is_numeric(42)); // true
var_dump(is_numeric(-42)); // true
// Floats
var_dump(is_numeric(3.14)); // true
var_dump(is_numeric(INF)); // true
var_dump(is_numeric(NAN)); // true — NaN is technically float
// Integer strings
var_dump(is_numeric('42')); // true
var_dump(is_numeric('-42')); // true
var_dump(is_numeric('+42')); // true
// Float strings
var_dump(is_numeric('3.14')); // true
var_dump(is_numeric('.5')); // true — leading dot allowed
var_dump(is_numeric('1e5')); // true — scientific notation
var_dump(is_numeric('1E5')); // true — case insensitive
var_dump(is_numeric('1.5e-3')); // true
// With whitespace
var_dump(is_numeric(' 42 ')); // true
var_dump(is_numeric("\t42\n")); // true
// NOT numeric
var_dump(is_numeric('')); // false
var_dump(is_numeric('abc')); // false
var_dump(is_numeric('42abc')); // false — leading-numeric is NOT is_numeric
var_dump(is_numeric('0x1A')); // false — hex string (since PHP 7.0)
var_dump(is_numeric('0b1010')); // false — binary string
var_dump(is_numeric('0o17')); // false — octal string
var_dump(is_numeric('true')); // false
var_dump(is_numeric(null)); // false
var_dump(is_numeric(true)); // false — bool is not numeric
var_dump(is_numeric([])); // false
Ловушка экзамена:
is_numeric('0x1A')возвращаетfalseначиная с PHP 7.0. В PHP 5.x это былоtrue. Такжеis_numeric(NAN)возвращаетtrue, потому что NaN -- это float. А вотis_numeric(true)--false, bool не является numeric.
filter_var() для валидации чисел
filter_var() обеспечивает строгую валидацию, подходящую для пользовательского ввода.
FILTER_VALIDATE_INT
<?php
declare(strict_types=1);
// Basic integer validation
var_dump(filter_var('42', FILTER_VALIDATE_INT)); // int(42)
var_dump(filter_var('-42', FILTER_VALIDATE_INT)); // int(-42)
var_dump(filter_var('+42', FILTER_VALIDATE_INT)); // int(42)
var_dump(filter_var('0', FILTER_VALIDATE_INT)); // int(0)
// Returns false for non-integers
var_dump(filter_var('3.14', FILTER_VALIDATE_INT)); // false — float
var_dump(filter_var('42abc', FILTER_VALIDATE_INT)); // false
var_dump(filter_var('abc', FILTER_VALIDATE_INT)); // false
var_dump(filter_var('', FILTER_VALIDATE_INT)); // false
var_dump(filter_var(' 42 ', FILTER_VALIDATE_INT)); // false — whitespace!
// With range options
$options = [
'options' => [
'min_range' => 1,
'max_range' => 100,
],
];
var_dump(filter_var('50', FILTER_VALIDATE_INT, $options)); // int(50)
var_dump(filter_var('150', FILTER_VALIDATE_INT, $options)); // false
var_dump(filter_var('0', FILTER_VALIDATE_INT, $options)); // false
// Allow octal and hex
$flags = ['flags' => FILTER_FLAG_ALLOW_OCTAL | FILTER_FLAG_ALLOW_HEX];
var_dump(filter_var('0x1A', FILTER_VALIDATE_INT, $flags)); // int(26)
var_dump(filter_var('0755', FILTER_VALIDATE_INT, $flags)); // int(493)
FILTER_VALIDATE_FLOAT
<?php
declare(strict_types=1);
// Basic float validation
var_dump(filter_var('3.14', FILTER_VALIDATE_FLOAT)); // float(3.14)
var_dump(filter_var('42', FILTER_VALIDATE_FLOAT)); // float(42.0)
var_dump(filter_var('1e5', FILTER_VALIDATE_FLOAT)); // float(100000.0)
var_dump(filter_var('-3.14', FILTER_VALIDATE_FLOAT)); // float(-3.14)
// Returns false for non-floats
var_dump(filter_var('abc', FILTER_VALIDATE_FLOAT)); // false
var_dump(filter_var('42abc', FILTER_VALIDATE_FLOAT)); // false
var_dump(filter_var('', FILTER_VALIDATE_FLOAT)); // false
// Thousands separator
$options = ['options' => ['decimal' => '.'], 'flags' => FILTER_FLAG_ALLOW_THOUSAND];
var_dump(filter_var('1,234.56', FILTER_VALIDATE_FLOAT, $options)); // float(1234.56)
var_dump(filter_var('1,234,567', FILTER_VALIDATE_FLOAT, $options)); // float(1234567.0)
Сравнение is_numeric() и filter_var()
<?php
declare(strict_types=1);
$values = ['42', '3.14', '1e5', ' 42 ', '42abc', '0x1A', '', 'abc'];
foreach ($values as $val) {
$isNumeric = is_numeric($val) ? 'true' : 'false';
$filterInt = filter_var($val, FILTER_VALIDATE_INT) !== false ? 'true' : 'false';
$filterFloat = filter_var($val, FILTER_VALIDATE_FLOAT) !== false ? 'true' : 'false';
printf(
"%-10s | is_numeric: %-5s | FILTER_INT: %-5s | FILTER_FLOAT: %-5s\n",
"'{$val}'",
$isNumeric,
$filterInt,
$filterFloat,
);
}
// Output:
// '42' | is_numeric: true | FILTER_INT: true | FILTER_FLOAT: true
// '3.14' | is_numeric: true | FILTER_INT: false | FILTER_FLOAT: true
// '1e5' | is_numeric: true | FILTER_INT: false | FILTER_FLOAT: true
// ' 42 ' | is_numeric: true | FILTER_INT: false | FILTER_FLOAT: false
// '42abc' | is_numeric: false | FILTER_INT: false | FILTER_FLOAT: false
// '0x1A' | is_numeric: false | FILTER_INT: false | FILTER_FLOAT: false
// '' | is_numeric: false | FILTER_INT: false | FILTER_FLOAT: false
// 'abc' | is_numeric: false | FILTER_INT: false | FILTER_FLOAT: false
Ключевое отличие:
is_numeric()допускает пробелы и научную нотацию.filter_var()сFILTER_VALIDATE_INTстроже -- не допускает пробелы, дробные числа, научную нотацию. Для валидации пользовательского ввода предпочитайтеfilter_var().
Подводные камни
Строки в условиях
<?php
declare(strict_types=1);
// Strings in boolean context
var_dump((bool) '0'); // false — ONLY non-empty string that is false
var_dump((bool) ''); // false — empty string
var_dump((bool) ' '); // true — space is not empty
var_dump((bool) '0.0'); // true — only '0' is false, not '0.0'
var_dump((bool) 'false'); // true — string 'false' is truthy
var_dump((bool) 'null'); // true — string 'null' is truthy
var_dump((bool) '00'); // true — only exact '0' is false
Восьмеричные и шестнадцатеричные строки
<?php
declare(strict_types=1);
// Hex/octal strings are NOT auto-converted since PHP 7.0
var_dump('0x1A' + 0); // 0 + Warning — NOT 26
var_dump('0755' + 0); // int(755) — treated as decimal, NOT octal!
var_dump('0b1010' + 0); // 0 + Warning — NOT 10
// Use explicit conversion functions
var_dump(intval('0x1A', 16)); // int(26) — hex
var_dump(intval('0755', 8)); // int(493) — octal
var_dump(intval('1010', 2)); // int(10) — binary
var_dump(hexdec('1A')); // int(26)
var_dump(octdec('755')); // int(493)
var_dump(bindec('1010')); // int(10)
Сравнение с нулем
<?php
declare(strict_types=1);
// PHP 8.0+ — safe comparisons with 0
var_dump(0 == null); // true — null → 0
var_dump(0 == false); // true — false → 0
var_dump(0 == ''); // false — '' is non-numeric
var_dump(0 == '0'); // true — '0' → 0
var_dump(0 == 'foo'); // false — 'foo' is non-numeric
var_dump(0 == '0.0'); // true — '0.0' → 0.0
// Always prefer strict comparison
var_dump(0 === null); // false
var_dump(0 === false); // false
var_dump(0 === '0'); // false
Строки с пробелами в switch
<?php
declare(strict_types=1);
$value = '0';
// switch uses loose comparison (==)
switch ($value) {
case false:
echo 'false'; // This matches! '0' == false → true
break;
case '0':
echo 'zero string';
break;
}
// Output: 'false' — unexpected!
// Use match instead (PHP 8.0+) — strict comparison
$result = match ($value) {
false => 'false',
'0' => 'zero string', // This matches correctly
default => 'other',
};
echo $result; // 'zero string'
Практические паттерны
Безопасная валидация ID
<?php
declare(strict_types=1);
function validateId(mixed $input): int
{
$id = filter_var($input, FILTER_VALIDATE_INT, [
'options' => ['min_range' => 1],
]);
if ($id === false) {
throw new InvalidArgumentException(
sprintf('Invalid ID: %s', var_export($input, true))
);
}
return $id;
}
echo validateId('42'); // int(42)
echo validateId(42); // int(42)
// validateId('0'); // InvalidArgumentException
// validateId('-1'); // InvalidArgumentException
// validateId('abc'); // InvalidArgumentException
// validateId('42.5'); // InvalidArgumentException
Безопасная валидация цены
<?php
declare(strict_types=1);
function validatePrice(mixed $input): float
{
if (is_string($input)) {
$input = str_replace([' ', ','], ['', '.'], $input);
}
$price = filter_var($input, FILTER_VALIDATE_FLOAT);
if ($price === false || $price < 0) {
throw new InvalidArgumentException('Invalid price');
}
return round($price, 2);
}
echo validatePrice('19.99'); // float(19.99)
echo validatePrice('1 234,56'); // float(1234.56)
echo validatePrice(42); // float(42.0)
// validatePrice('free'); // InvalidArgumentException
// validatePrice('-10'); // InvalidArgumentException