Александр Некрасов · Автономное руководство

PHP: полный мануал с примерами, функциями и ООП

Разделы этой страницы
  1. Типы данных: значения, преобразования и проверка
  2. Вид значения определяет, что с ним можно сделать
  3. Проверить тип и увидеть, почему одинаковый вывод обманывает
  4. У значения есть тип: сначала научимся его наблюдать
  5. Карта типов: данные, составные значения и договоры
  6. null: значение ещё не задано по смыслу задачи
  7. bool: ответ на вопрос «да или нет»
  8. int: посчитать целые предметы
  9. float: измерения и дробные величины
  10. Приведение создаёт другое представление; оно не проверяет смысл
  11. Значение не меняется само, но операция выбирает контекст
  12. Следующая ступень: типы в функциях, объектах и внешних данных
  13. bool: почему строка "false" может включить настройку
  14. int: целые числа, системы счисления и предел размера
  15. float: измерения, погрешность и сравнение
  16. Числовая строка: можно посчитать, но сначала нужно понять смысл
  17. Тип переменной и объявление типа: где PHP проверяет договор
  18. null, mixed и специальные результаты без догадок
  19. Действие, прекращение работы и последовательность данных
  20. Ресурс и составной объектный тип: два разных договора
  21. Переменная хранит значение с конкретным смыслом
  22. Текст формы не равен целому количеству
  23. Приблизительные числа и денежный расчет
  24. Не путать проверку присутствия и истинности
  25. Объект и массив: похожие данные ещё не означают один тип
  26. PHP проверяет имя договора, а не только похожесть методов
  27. self, parent и static: тип относительно места в иерархии
  28. Ресурс: две переменные могут держать один открытый поток
  29. callable: передать действие вместо немедленного результата
  30. Какие формы callable встречаются в библиотеках
  31. true и false как точные типы результата
  32. Неявное преобразование при union-типе не идёт слева направо
  33. strict_types относится к месту вызова, а не к проекту целиком
  34. Функции и методы этой темы
  35. Код и руководство с собой
PHP 8.5.11149 глав4458 карточек справочника
Дерево руководства

Глава 8 · С нуля → практика

Типы данных: значения, преобразования и проверка

Как отличить количество от строки формы, отсутствие значения от нуля и деньги от приблизительного числа.

Вид значения определяет, что с ним можно сделать

В карточке товара нужны артикул 002, количество 2 и измеренная масса 0.5 кг. Внешне это похожие цифры, но смысл разный: артикул нельзя потерять как 2, количество можно складывать, массу измеряют дробным числом. Тип описывает вид значения и правила операций. Если присваивание пока незнакомо, начните с одной переменной и её чтения.

Три значения одной карточки
  1. Артикул «002» → string: сохраняем ведущие нули.
  2. Количество 2 → int: считаем целые упаковки.
  3. Масса 0.5 → float: измеряем дробную величину.
  4. echo собирает из них три понятные строки.
Разные значения образуют одну понятную карточкуСамостоятельный пример
<?php
$sku = '002';
$quantity = 2;
$weightKg = 0.5;
echo 'Артикул: ', $sku, "\n";
echo 'Упаковок: ', $quantity, "\n";
echo 'Масса упаковки: ', $weightKg, ' кг', "\n";
Ожидаемый результат
Артикул: 002
Упаковок: 2
Масса упаковки: 0.5 кг

Кавычки делают 002 текстом. У 0.5 десятичный разделитель в PHP — точка. Мы не выводим массу как 0,5: форматирование для человека рассматривается отдельно. Такой вывод в консоль не требует HTML.

ТипЗапись и смысл
string'002' — текст; даже если выглядит как число.
int2 — целое количество. Диапазон зависит от платформы; на границах нужны отдельные проверки.
float0.5 — число с плавающей точкой. Не все десятичные доли хранятся точно.
booltrue или false — ответ да/нет, без кавычек.
nullЯвное отсутствие значения по договору; не равно числу 0 или пустой строке.

Обычная переменная может позже получить значение другого типа. Это разрешение языка не обязывает смешивать смыслы: количество храните в quantity, а подпись для человека — в quantityLabel. Массив, объект и ресурс будут отдельными видами значений; типы параметров и возвращаемого результата появятся после объяснения функций.

Проверить тип и увидеть, почему одинаковый вывод обманывает

Строка "2" и число 2 могут одинаково выглядеть на экране. Встроенная диагностическая функция var_dump($value) получает значение в круглых скобках и показывает его тип вместе с содержимым. Это готовое средство наблюдения, а не объявление собственной функции: имя var_dump уже известно PHP. Команда echo предназначена для сообщения пользователю.

Сравнить данные для расчёта и данные для подписиСамостоятельный пример
<?php
$quantity = 2;
$quantityText = '2';
echo 'Для человека: ', $quantity, ' и ', $quantityText, "\n";
var_dump($quantity);
var_dump($quantityText);
Ожидаемый результат
Для человека: 2 и 2
int(2)
string(1) "2"

int(2) — целое 2. string(1) "2" — строка длиной один байт. Цифра 1 здесь длина строки, а не её значение. var_dump не форматирует готовую карточку товара, зато помогает понять неожиданную операцию.

Ответы true/false и отсутствие null тоже имеют практический смысл. Покажем их в минимальном выборе сообщения. Новая запись if ($isAvailable) { ... } else { ... } выполняет первый блок при true, второй при false. === null проверяет именно null. Подробный разбор скобок и ветвей находится в следующей главе.

Отличить недоступный товар от неуказанного комментарияСамостоятельный пример
<?php
$isAvailable = false;
$customerNote = null;
if ($isAvailable) {
    echo "Можно заказать\n";
} else {
    echo "Сейчас нет в наличии\n";
}
if ($customerNote === null) {
    echo "Комментарий покупателя не указан\n";
}
Ожидаемый результат
Сейчас нет в наличии
Комментарий покупателя не указан

false — известный ответ о доступности. null — отсутствие комментария по принятому договору. Строка "false" была бы непустым текстом и дала бы другое поведение условия: тип нельзя угадывать по слову в кавычках.

Дальше последовательно уточняем каждый тип: его запись, значения, преобразования, пределы и поведение при ошибках. Приведение типа не проверяет предметный смысл: превращение строки в число не доказывает, что это допустимое количество.

У значения есть тип: сначала научимся его наблюдать

Цена 150, артикул "150" и признак оплаты true отвечают на разные вопросы. Тип показывает, как PHP представляет значение и какие действия к нему применимы. 150 без кавычек — целое число. '150' в кавычках — текст из трёх символов. Внешне echo может показать их одинаково, поэтому для учебной проверки нужен инструмент, который показывает ещё и тип.

var_dump — встроенная диагностическая функция. В скобки передайте исследуемое значение; функция напечатает тип и содержимое. Это временное наблюдение разработчика, а не оформление для посетителя сайта. Перед выводом личных данных в журнал также нужно учитывать, кому доступен журнал.

Одинаково выглядящие число и строкаСамостоятельный пример
<?php
var_dump(150);
var_dump('150');
var_dump(true);
var_dump(null);
Ожидаемый результат
int(150)
string(3) "150"
bool(true)
NULL

int — целое число; string(3) — строка длиной 3 байта; bool — логическое значение; NULL — отсутствие значения. У кириллицы число байтов не обязано совпадать с количеством букв. var_dump добавляет диагностическую запись, а не меняет переданное значение.

Показать и новое значение, и его типСамостоятельный пример
<?php
$value = 150;
echo 'Цена: ', $value, ' руб.; тип ', get_debug_type($value), "\n";
$value = 'Цена пока не указана';
echo 'Подпись: ', $value, '; тип ', get_debug_type($value), "\n";
Ожидаемый результат
Цена: 150 руб.; тип int
Подпись: Цена пока не указана; тип string

Первое значение — число 150, второе — текст. Присваивание заменило значение под тем же именем; get_debug_type возвращает название текущего типа как строку для echo. Оно не изменяет value. Так можно наблюдать динамическую типизацию, но для расчётов яснее отдельные price и label: фразу «Цена пока не указана» складывать с доставкой нельзя.

Карта типов: данные, составные значения и договоры

Наглядная модель

Одна надпись «0» — разные состояния данных

Интеграция передаёт остаток товара. Внешне похожие значения требуют разных действий: нулевой остаток нельзя принять за отсутствие поля.

  • stock = 0

    Целое число: товар известен, на складе ноль штук.

  • stock = «0»

    Строка из формы. Проверяем формат и только затем преобразуем в число.

  • Ключ stock отсутствует

    Поставщик не сообщил остаток. Нужна отдельная политика обработки.

Переменная связывает имя со значением. Тип определяет, какие операции осмысленны. Проверка типа, допустимого диапазона и наличия поля отвечает на три разных вопроса.

Пример заказа: имя, значение и тип
  1. $orderId → 150 → int: номер в нашей системе
  2. $externalId → '00150' → string: внешний код
  3. $isPaid → false → bool: оплата не подтверждена
  4. $comment → null → комментария пока нет
ГруппаНазванияПример смысла
Скалярные значенияbool, int, float, stringОдин переключатель, счётчик, измерение, текст.
Набор данныхarrayПозиции заказа или значения по именованным ключам.
Объектobject; конкретный класс или enumКорзина с состоянием и действиями либо один из разрешённых статусов.
Специальный доступresourceОткрытый поток; это доступ к чтению, а не сам текст файла.
ОтсутствиеnullАдрес доставки ещё не выбран.
Договор входа или выходаmixed, iterable, callable, void, never, self, parent, static, true, falseОписывает, какие значения или способы завершения разрешены в конкретном месте.

Последняя строка не означает, что можно записать в переменную «значение void». Часть названий относится к объявлениям функций и классов. Сначала изучим обычные значения, потом — проверяемые договоры. PHP также позволяет сочетать типы: A|B — один из вариантов, A&B — одновременное выполнение двух объектных договоров.

PHP не даёт объявить произвольный псевдоним типа вроде type ProductRow = .... Формы массивов в PHPDoc помогают редактору и анализатору, но не становятся встроенной проверкой PHP. mixed и iterable — встроенные объединения, не образец создания собственных псевдонимов.

null: значение ещё не задано по смыслу задачи

Нужен понятный текст о комментарии к доставке. Сначала договоримся: null — покупатель ещё не отвечал, пустая строка — сознательно оставил без комментария, непустая строка — указание курьеру. Для выбора текста используем небольшую конструкцию if (проверка) { действия }: действия в фигурных скобках выполняются при true. elseif проверяет следующий случай, а else обрабатывает оставшийся. Полный разбор выбора — в уроке условий.

Покупатель не выбрал дату доставки. Ставить число 0 или текст «нет» неудобно: это другие виды данных. Значение null явно обозначает предусмотренное отсутствие. У этого типа ровно одно значение. Запись без кавычек имеет смысл для PHP; 'null' в кавычках — просто четыре буквы.

=== спрашивает о строгом равенстве значения и типа. Поэтому пустая строка и ноль не равны null. В следующем примере логический результат сравнения выбирает понятное сообщение.

Три разных ответа покупателя дают три понятных сообщенияСамостоятельный пример
<?php
$comment = null;
if ($comment === null) {
    echo "Комментарий ещё не заполнен\n";
}
$comment = '';
if ($comment === '') {
    echo "Покупатель выбрал доставку без комментария\n";
}
$comment = 'Позвонить у подъезда';
if ($comment !== null && $comment !== '') {
    echo 'Курьеру: ', $comment, "\n";
}
Ожидаемый результат
Комментарий ещё не заполнен
Покупатель выбрал доставку без комментария
Курьеру: Позвонить у подъезда

Каждое присваивание задаёт отдельную учебную ситуацию. !== означает строгое неравенство, && требует обе проверки одновременно. Последняя строка выходит только для уже заданного непустого текста. Ноль и пустая строка не равны null при ===; не заменяйте это условие общей проверкой истинности.

isset($note) отвечает «переменная существует и не равна null?». Поэтому false получится и у явно присвоенного null, и после удаления. unset($note) удаляет привязку имени в текущей области, а не присваивает ему новый полезный текст. Обе конструкции получают имя исследуемой переменной в круглых скобках.

Удалить временное указание после подготовки подписиСамостоятельный пример
<?php
$note = null;
if (!isset($note)) {
    echo "Указания для курьера нет\n";
}
$note = 'Позвонить заранее';
if (isset($note)) {
    echo 'Подпись на заказе: ', $note, "\n";
}
unset($note);
if (!isset($note)) {
    echo "Временная переменная удалена\n";
}
Ожидаемый результат
Указания для курьера нет
Подпись на заказе: Позвонить заранее
Временная переменная удалена

isset проверяет наличие имени с ненулевым значением, ! меняет ответ на противоположный. Поэтому первая и последняя проверки одинаково успешны, хотя сначала имя содержит null, а в конце имени уже нет. Вторая ветка использует реальный текст до удаления. unset не удаляет заказ или сохранённую запись из базы.

Приведение (unset) — старый синтаксис, удалённый в PHP 8.0. Для явного отсутствия присваивайте null; для удаления переменной используйте unset. Различие отсутствующего ключа и ключа со значением null подробно видно в массиве.

bool: ответ на вопрос «да или нет»

Переменная $isPaid должна отвечать на один вопрос: оплата подтверждена? Значения true и false пишутся без кавычек. Имена в PHP для них регистронезависимы, но единый нижний регистр проще читать. Не путайте логическое false со строкой "false" из конфигурации.

Запись условие ? первый_вариант : второй_вариант выбирает значение: при true — первое, при false — второе. Здесь это две строки для посетителя. После выбора echo получает и выводит одну из них.

Изменение признака оплаты меняет дальнейшее действиеСамостоятельный пример
<?php
$isPaid = false;
echo $isPaid ? "Можно комплектовать\n" : "Ждём оплату\n";
$isPaid = true;
echo $isPaid ? "Можно комплектовать\n" : "Ждём оплату\n";
Ожидаемый результат
Ждём оплату
Можно комплектовать

Первый выбор получает false и возвращает вторую строку. После присваивания true второй выбор возвращает первую строку. Echo выводит готовую строку, а не само логическое значение. Внешняя платёжная система в этом примере не вызывается: присваивания позволяют безопасно воспроизвести два состояния.

При переводе в bool получится falseПримеры
Логическое ложное значениеfalse
Нули0, 0.0, -0.0
Особые строкиПустая строка и ровно строка "0"
Пустой набор и отсутствие[], null

Остальные обычные значения истинны: строка "false", пробел, "00", "0.0", отрицательное число, непустой массив и даже NAN. У отдельных внутренних объектов, например SimpleXML без содержимого, есть специальные правила. Поэтому проверка существования товара через одну истинность ID может потерять законный нулевой ID. Ниже сравним bool-приведение с разбором настройки.

int: посчитать целые предметы

Для количества коробок удобно целое число. У него нет дробной части. Знак минус задаёт отрицательное значение; плюс можно написать явно, но обычно опускают. Цифры без кавычек — числовая запись. Для сохранения ведущих нулей внешнего артикула нужна строка, потому что номер как текст и количество — разные понятия.

Остаток после заказа становится доступным следующему покупателюСамостоятельный пример
<?php
$stock = 12;
$ordered = 3;
$remaining = $stock - $ordered;
echo 'Заказано: ', $ordered, ' шт.', "\n";
echo 'Осталось для продажи: ', $remaining, ' шт.', "\n";
$annualTarget = 1_000;
echo 'Годовой план: ', $annualTarget, ' шт.', "\n";
Ожидаемый результат
Заказано: 3 шт.
Осталось для продажи: 9 шт.
Годовой план: 1000 шт.

12 минус 3 даёт int 9. Он сохранён в remaining, поэтому доступен дальнейшему расчёту. Подчёркивание в литерале 1_000 помогает читать код и не входит в число или вывод; доступно с PHP 7.4. Его ставят только между цифрами, без удвоения. Отрицательное количество возврата можно записать как -3, но правила склада должны определить, где такой знак разрешён.

intdiv(int $num1, int $num2): int принимает делимое и делитель и возвращает целое частное с отсечением к нулю. Это обычная функция: оба числа входят в скобки, возвращённое число занимает место вызова в правой части присваивания. % возвращает остаток от целочисленного деления.

Разложить семь банок в коробки по триСамостоятельный пример
<?php
$items = 7;
$capacity = 3;
$fullBoxes = intdiv($items, $capacity);
$looseItems = $items % $capacity;
echo 'Полных коробок: ', $fullBoxes, "\n";
echo 'Банок отдельно: ', $looseItems, "\n";
echo 'Проверка комплектации: ', $fullBoxes * $capacity + $looseItems, "\n";
$exactShare = 6 / 3;
echo 'Точный результат 6 / 3: ', $exactShare, ' (', get_debug_type($exactShare), ')', "\n";
$average = 7 / 2;
echo 'Среднее на двух получателей: ', $average, ' (', get_debug_type($average), ')', "\n";
Ожидаемый результат
Полных коробок: 2
Банок отдельно: 1
Проверка комплектации: 7
Точный результат 6 / 3: 2 (int)
Среднее на двух получателей: 3.5 (float)

Возврат intdiv 2 используется как число полных коробок; остаток 1 нельзя потерять. У / результат int, если оба операнда целые и деление точное, иначе float. Дробное среднее не означает возможность отправить половину банки. Делитель должен быть ненулевым; intdiv(PHP_INT_MIN, -1) не помещается в int и вызывает ArithmeticError.

Размер int зависит от сборки платформы, часто это 64 бита, а на некоторых платформах 32. Границы сообщают PHP_INT_MIN, PHP_INT_MAX, размер в байтах — PHP_INT_SIZE. PHP не предоставляет отдельный обычный unsigned int. При выходе результата арифметики за предел int он может стать float; точные огромные идентификаторы так обрабатывать нельзя. Проверку переполнения и системы счисления разберём ниже.

float: измерения и дробные величины

Вес посылки и температура допускают дробную часть. В PHP она отделяется точкой: 1.25. Запятая не является десятичным разделителем в исходном коде. Экспоненциальная запись обозначает степень десяти: 2.5e3 — 2500, 2.5e-3 — 0.0025. Даже целое по математическому значению число может иметь тип float.

Рассчитать массу посылки из двух упаковокСамостоятельный пример
<?php
$unitWeightKg = 1.25;
$quantity = 2;
$packageWeightKg = 0.2;
$totalWeightKg = $unitWeightKg * $quantity + $packageWeightKg;
echo 'Товар: ', $unitWeightKg * $quantity, ' кг', "\n";
echo 'С упаковкой: ', $totalWeightKg, ' кг', "\n";
$gramsInBatch = 2.5e3;
echo 'Та же масса товара в граммах: ', $gramsInBatch, "\n";
echo 'Тип граммов: ', get_debug_type($gramsInBatch), "\n";
Ожидаемый результат
Товар: 2.5 кг
С упаковкой: 2.7 кг
Та же масса товара в граммах: 2500
Тип граммов: float

1.25 умножается на 2, затем к 2.5 прибавляется 0.2: в totalWeightKg лежит float для следующего выбора тарифа. 2.5e3 означает 2.5 × 1000 и остаётся float, хотя echo показывает 2500. Литерал 2500 без точки был бы int. Экранное представление 2.7 не означает точную десятичную арифметику внутри.

is_nan(float $num): bool распознаёт специальное значение NAN. is_finite(float $num): bool разрешает конечные числа и отклоняет INF, -INF, NAN. Функции получают исследуемое число в скобках; их логический результат сразу становится проверкой if. В четырёх отдельных ситуациях ниже меняется только поступившее измерение.

Показать пригодное измерение и причины отказа остальныхСамостоятельный пример
<?php
$temperature = 12.5;
if (is_finite($temperature)) {
    echo 'Температура: ', $temperature, ' °C', "\n";
}
$temperature = INF;
if (!is_finite($temperature)) {
    echo "Измерение отклонено: бесконечность\n";
}
$temperature = NAN;
if (is_nan($temperature)) {
    echo "Измерение отклонено: число не определено\n";
}
$temperature = -INF;
if (!is_finite($temperature)) {
    echo "Измерение отклонено: бесконечность\n";
}
Ожидаемый результат
Температура: 12.5 °C
Измерение отклонено: бесконечность
Измерение отклонено: число не определено
Измерение отклонено: бесконечность

Первое значение проходит проверку is_finite. NAN распознаётся отдельной функцией, потому что оно не равно даже самому себе, в том числе при ===. INF и -INF не проходят is_finite. Это проверка представления; допустимый диапазон температуры конкретного датчика нужно проверять дополнительно. Здесь нет JSON и внешнего устройства.

В дробной арифметике проверяется допустимая погрешность, а не количество красивых цифр на экране. Точность зависит от представления числа; преобразование в форматированную строку меняет только вывод. Для денег выбор int в копейках или десятичной библиотеки должен соответствовать валюте, правилам округления и пределам суммы.

Приведение создаёт другое представление; оно не проверяет смысл

Приведение пишется перед значением: (int) $text, (float) $text, (string) $value, (bool) $value. Получится новое значение выбранного типа. Сама исходная переменная от одного такого выражения не изменится; её можно заменить отдельным присваиванием.

Сохранить исходный код и отдельно получить количествоСамостоятельный пример
<?php
$raw = '012';
$quantity = (int) $raw;
echo 'Исходный текст: [', $raw, ']', "\n";
echo 'Количество для расчёта: ', $quantity, "\n";
echo 'За три одинаковые партии: ', $quantity * 3, "\n";
echo 'Опасный хвост 12items превращается в: ', (int) '12items', "\n";
echo 'Слово items превращается в: ', (int) 'items', "\n";
echo 'Дроби отсекаются к нулю: ', (int) 3.9, ', ', (int) -3.9, "\n";
Ожидаемый результат
Исходный текст: [012]
Количество для расчёта: 12
За три одинаковые партии: 36
Опасный хвост 12items превращается в: 12
Слово items превращается в: 0
Дроби отсекаются к нулю: 3, -3

В скобках (int) указано требуемое представление. Raw остаётся строкой 012, quantity получает int 12, который используется в умножении. Последние строки показывают предел преобразования: принятие числа PHP не подтверждает допустимость пользовательского текста. Для идентификатора 012 следует оставить строку, если ведущий ноль значим.

settype(mixed &$var, string $type): bool получает изменяемую переменную и название типа. Знак & в справочной сигнатуре предупреждает о записи в эту переменную. В нашем вызове он не пишется. Возврат сообщает об успехе; преобразованное значение нужно читать из самой переменной.

Различить результат преобразования и подтверждение его выполненияСамостоятельный пример
<?php
$quantity = '12';
$converted = settype($quantity, 'int');
if ($converted) {
    echo 'Тип количества после преобразования: ', get_debug_type($quantity), "\n";
    echo 'В двух партиях: ', $quantity * 2, ' шт.', "\n";
}
Ожидаемый результат
Тип количества после преобразования: int
В двух партиях: 24 шт.

Converted получил true, quantity стал int 12. Умножение использует quantity, а не converted: true не является преобразованным количеством. Когда не нужно менять входную переменную, отдельное присваивание $count = (int) $raw яснее сохраняет исходник. Допустимые type: boolean/bool, integer/int, float/double, string, array, object, null; неизвестное имя вызывает ValueError в PHP 8.

Используйте современные краткие имена кастов. Псевдонимы (integer), (boolean), (double), (real) нужно оценивать по конкретной версии; в PHP 8.5 integer/boolean/double/binary устарели, а real удалён ещё в PHP 8.0. Скалярное преобразование целиком не зависит от strict_types: явный каст остаётся вашим явным решением.

Значение не меняется само, но операция выбирает контекст

Одна строка "12" может участвовать в сложении и в соединении текста. В первом случае PHP интерпретирует её как число для конкретной операции; во втором — как строку. Это называется неявным преобразованием. Оно не означает, что исходная переменная стала числом навсегда.

Количество прибавляют, части кода соединяютСамостоятельный пример
<?php
$raw = '12';
$stockAfterDelivery = $raw + 3;
$combinedCode = $raw . '3';
echo 'После поступления трёх штук: ', $stockAfterDelivery, "\n";
echo 'Код из двух текстовых частей: ', $combinedCode, "\n";
echo 'Исходное значение: ', $raw, ' (', get_debug_type($raw), ')', "\n";
if (12 == $raw) {
    echo "Значения равны после допустимого сравнения с преобразованием\n";
}
if (12 !== $raw) {
    echo "При строгом сравнении типы различаются\n";
}
Ожидаемый результат
После поступления трёх штук: 15
Код из двух текстовых частей: 123
Исходное значение: 12 (string)
Значения равны после допустимого сравнения с преобразованием
При строгом сравнении типы различаются

Плюс выбрал числовое действие и дал int 15; точка соединяет строки и дала string 123. Ни то, ни другое не переписало raw. == допускает преобразование по правилам сравнения, === требует одинаковый тип и значение, !== — отрицание строгого равенства. Не используйте арифметическое преобразование как проверку формата внешнего ввода.

КонтекстГде появляетсяЧто помнить
Числовой+, -, *, / и другие арифметические действияНе все значения интерпретируются как числа; нечисловой текст может дать TypeError. У + над двумя массивами другой смысл: объединение по ключам.
Строковыйecho, print, сцепление и интерполяцияЧисло представляется текстом; массив нельзя превратить таким образом в содержательный список.
Логическийif, логические операторы, условный выборПрименяются правила bool, а не перевод слов true/false.
Целочисленный/строковыйПобитовые &, |, ^ и ~; сдвиги << и >>Для &, | и ^ две строки обрабатываются по байтам; ~ также работает по байтам у строки. В остальных случаях эти операции рассматривают значения как int. У сдвигов << и >> операнды и результат всегда рассматриваются как int.
Сравнение==, <, > и похожие операцииПреобразование зависит от пары типов и выбранного оператора.
Вызов и возвратОбъявленные типы функцийЗависит от договора и режима скалярных вызовов; рассмотрено ниже.

Проверьте понимание. Можно ли заменить строковый артикул "0012" на int только потому, что он складывается с 0?

Разбор

Нет. Возможность арифметического преобразования не задаёт смысл поля. Для артикула ведущие нули и текстовый формат могут быть частью договора с поставщиком.

Следующая ступень: типы в функциях, объектах и внешних данных

Теперь можно объяснять случаи, где тип проверяется на границе вызова, а данные приходят из формы или API. Далее встречаются собственные функции, условия, перебор и исключения. Если вы идёте с нуля, сначала пройдите устройство функции и return, условия и цикл и путь ошибки, затем вернитесь к этим задачам.

Смысл объявленного типа — обнаружить несовместимое значение в конкретной точке. Смысл проверки данных — установить, что значение допустимо для нашей задачи. Положительная сумма, существующий номер заказа и доступный пользователю заказ не становятся правильными только от объявления int.

bool: почему строка "false" может включить настройку

Здесь начинается применение уже известных значений. Для следующей функции нужны параметр и return, а match, foreach и catch кратко разобраны непосредственно перед их использованием. Первый опыт ниже обходится без собственной функции.

Представьте настройку уведомлений. Нам нужны два состояния: включено и выключено. Значения true и false относятся к типу bool. Но из файла конфигурации или формы часто приходит текст. Приведение (bool) отвечает на вопрос о логическом значении текста по правилам PHP; оно не переводит английские слова.

Два разных действия
  1. Исходная строка "false"
  2. Приведение к bool: непустой текст
  3. true — неожиданное включение

Проверим последствия преобразования в отдельных состояниях. (bool) перед выражением создаёт логическое значение; запись условие ? строка_да : строка_нет выбирает понятную подпись. Никаких уведомлений этот код не отправляет — он показывает, какое решение получилось бы.

Почему слово false включает переключательСамостоятельный пример
<?php
$raw = 'false';
$enabled = (bool) $raw;
echo 'Текст [', $raw, '] после (bool): ', $enabled ? 'включено' : 'выключено', "\n";
$raw = '0';
echo 'Текст [0] после (bool): ', (bool) $raw ? 'включено' : 'выключено', "\n";
$raw = '';
echo 'Пустой текст после (bool): ', (bool) $raw ? 'включено' : 'выключено', "\n";
$selected = [];
echo 'Пустой выбор: ', (bool) $selected ? 'позиции выбраны' : 'позиций нет', "\n";
$selected = [0];
echo 'Выбор с кодом 0: ', (bool) $selected ? 'позиции выбраны' : 'позиций нет', "\n";
Ожидаемый результат
Текст [false] после (bool): включено
Текст [0] после (bool): выключено
Пустой текст после (bool): выключено
Пустой выбор: позиций нет
Выбор с кодом 0: позиции выбраны

Строка false содержит обычные буквы и даёт true. Исключения среди строк — пустая строка и ровно 0. У массива проверяется наличие элементов: [0] непустой, хотя единственное значение само по себе ложно. Исходный raw остаётся строкой; enabled — отдельный bool. Нулевой код может быть допустимым, поэтому значение элемента и наличие элемента — разные вопросы.

Теперь функция notificationSetting получает один текст raw и должна вернуть bool. Trim возвращает строку без крайних пробелов; strtolower возвращает её вариант с нижним регистром ASCII. Match сравнивает полученную строку строго с вариантами: две строки слева от => разделены запятой и ведут к одному результату. Default обрабатывает остальные входы; throw передаёт управление в catch вместо return. Foreach последовательно даёт функции три независимых входа.

Место вызоваМестные действияВозврат и дальнейшее действие
notificationSetting('false')trim → false, strtolower → false, ветка false/0return false попадает в ?: → echo «Выключено»
notificationSetting(' TRUE ')trim → TRUE, strtolower → true, ветка true/1return true попадает в ?: → echo «Включено»
notificationSetting('maybe')После нормализации maybe, подходящей ветки нетthrow → catch; печатается причина, bool не возвращён
Теперь читаем настройку по явному договоруСамостоятельный пример
<?php
declare(strict_types=1);

function notificationSetting(string $raw): bool
{
    return match (strtolower(trim($raw))) {
        'true', '1' => true,
        'false', '0' => false,
        default => throw new InvalidArgumentException('Неизвестная настройка'),
    };
}
foreach (['false', ' TRUE ', 'maybe'] as $raw) {
    try {
        echo notificationSetting($raw) ? "Включено\n" : "Выключено\n";
    } catch (InvalidArgumentException $error) {
        echo $error->getMessage(), "\n";
    }
}
Ожидаемый результат
Выключено
Включено
Неизвестная настройка

Функция сначала нормализует пробелы и регистр, затем принимает только согласованные варианты. Альтернатива — FILTER_VALIDATE_BOOLEAN с FILTER_NULL_ON_FAILURE, если его набор допустимых слов подходит вашему протоколу. Неизвестное значение не превращается молча в false.

В реальном вопросе о строках true/false ожидалось, что обычное условие распознает слова. Отсюда полезное разделение: истина по правилам языка и разбор выбранного текстового протокола. Наша функция оставляет неизвестное значение ошибкой, вместо молчаливого изменения настройки.

int: целые числа, системы счисления и предел размера

Количество коробок и цена в копейках — целые числа. В коде число можно записать в десятичной, двоичной, восьмеричной или шестнадцатеричной системе. Это разные записи одного значения. Они пригодятся для флагов, прав доступа и двоичных протоколов; обычное количество товара записывайте десятичным числом.

Разные записи одинакового количестваСамостоятельный пример
<?php
echo 26, ' ', 0b11010, ' ', 0o32, ' ', 0x1A, "\n";
echo 1_000_000, "\n";
echo intdiv(17, 5), ' коробки, остаток ', 17 % 5, "\n";
Ожидаемый результат
26 26 26 26
1000000
3 коробки, остаток 2

0b — двоичная запись, 0o — восьмеричная (PHP 8.1+), 0x — шестнадцатеричная; подчёркивания помогают читать литерал и не входят в число. В старой восьмеричной записи 032 — тоже26: не добавляйте ведущий ноль к десятичным литералам. intdiv возвращает целую часть частного, % — остаток.

Размер int зависит от платформы: используйте PHP_INT_SIZE, PHP_INT_MIN и PHP_INT_MAX, а не предположение о числе разрядов. При выходе за диапазон результат арифметики может стать float. Это особенно опасно для длинных идентификаторов из внешней системы: идентификатор лучше хранить строкой, если над ним не требуется арифметика.

В следующем договоре числа неотрицательны. Функция принимает имеющийся остаток current и поступление incoming. Проверка диапазона идёт до сложения: PHP_INT_MAX - incoming показывает максимально допустимый current. Исключение означает, что новый остаток не получен и записывать его нельзя.

Принять поставку или сохранить прежний остатокСамостоятельный пример
<?php
function addNonNegative(int $current, int $incoming): int {
    if ($current < 0 || $incoming < 0 || $current > PHP_INT_MAX - $incoming) {
        throw new OverflowException('Сумма выходит за допустимый диапазон');
    }
    return $current + $incoming;
}
$stock = 120;
$stock = addNonNegative($stock, 30);
echo 'После поставки: ', $stock, ' шт.', "\n";
try {
    $stock = addNonNegative(PHP_INT_MAX, 1);
} catch (OverflowException $error) {
    echo 'Поставка отклонена: ', $error->getMessage(), "\n";
}
echo 'Последний допустимый остаток: ', $stock, ' шт.', "\n";
Ожидаемый результат
После поставки: 150 шт.
Поставка отклонена: Сумма выходит за допустимый диапазон
Последний допустимый остаток: 150 шт.

Первый вызов получает current120/incoming30, возвращает int150 в правую часть присваивания, stock становится150. Во втором опытном вызове проверяется предельная пара PHP_INT_MAX/1. До return дело не доходит: catch получает отказ, присваивание stock не завершилось, поэтому в нём остаётся150. Эта граница работает на поддерживаемой 32- или 64-битной платформе; конкретное максимальное число в ожидаемый вывод не зашито.

float: измерения, погрешность и сравнение

Температура датчика или результат деления часто не являются целыми. Float хранит приближённое двоичное число. Даже короткая десятичная дробь может не иметь точного двоичного представления. Отсюда разница между визуально одинаковой строкой и точным сравнением значений.

Принять измерение с выбранной погрешностьюСамостоятельный пример
<?php
$measuredKg = 0.1 + 0.2;
$expectedKg = 0.3;
$toleranceKg = 0.000001;
echo 'Измерено: ', number_format($measuredKg, 3, '.', ''), ' кг', "\n";
echo 'Ожидается: ', number_format($expectedKg, 3, '.', ''), ' кг', "\n";
if ($measuredKg !== $expectedKg) { echo "Точное двоичное значение отличается\n"; }
$differenceKg = abs($measuredKg - $expectedKg);
if ($differenceKg <= $toleranceKg) {
    echo "Масса принята: отклонение укладывается в допуск\n";
}
Ожидаемый результат
Измерено: 0.300 кг
Ожидается: 0.300 кг
Точное двоичное значение отличается
Масса принята: отклонение укладывается в допуск

Оба значения выглядят как0.300 после округления для экрана, но === сравнивает хранимые числа. Abs возвращает положительный модуль разницы; сравнение с допуском даёт true и выбирает приёмку. Допуск задан в килограммах и выбран только для опыта. Number_format возвращает строку с тремя десятичными знаками и не исправляет внутреннее float. Измерения реального прибора требуют его диапазона, единиц и точности.

Отбрасываем бесконечность перед сохранением показанийСамостоятельный пример
<?php
declare(strict_types=1);

function sensorValue(float $value): string
{
    if (!is_finite($value)) {
        throw new InvalidArgumentException('Показание не является конечным числом');
    }
    return number_format($value, 1, '.', '');
}
foreach ([21.25, INF, NAN] as $value) {
    try {
        echo sensorValue($value), "\n";
    } catch (InvalidArgumentException $error) {
        echo $error->getMessage(), "\n";
    }
}
Ожидаемый результат
21.3
Показание не является конечным числом
Показание не является конечным числом

INF и NAN — специальные float-значения. NAN нельзя надёжно искать обычным сравнением с самим собой; используйте is_nan, а для допустимого конечного числа — is_finite. Перед записью в JSON отдельно проверяйте ограничения формата. Деньги с фиксированными младшими единицами храните int или используйте согласованную десятичную арифметику.

Согласовать абсолютный и относительный допускСамостоятельный пример
<?php
$expected = 1000000.0;
$measured = 1000000.2;
$absoluteTolerance = 0.01;
$relativeTolerance = 0.000001;
$allowedDifference = max($absoluteTolerance, abs($expected) * $relativeTolerance);
echo 'Разрешённое отклонение: ', number_format($allowedDifference, 2, '.', ''), "\n";
echo 'Полученное отклонение: ', number_format(abs($measured - $expected), 2, '.', ''), "\n";
if (abs($measured - $expected) <= $allowedDifference) {
    echo "Показание принято по согласованному допуску\n";
}
Ожидаемый результат
Разрешённое отклонение: 1.00
Полученное отклонение: 0.20
Показание принято по согласованному допуску

Относительная часть равна1000000×0.000001=1. Max выбирает большее из1 и абсолютного минимума0.01. Разница около0.2 укладывается в1. У ожидаемого нуля останется абсолютный допуск0.01. Это конкретный договор сравнения с опорным expected, а не универсальная замена ===: для разных задач выбирают другую формулу и проверяют конечность входов.

Вызов sensorValueПуть значенияИспользование
sensorValue(21.25)Местный value конечен; number_format(value,1,'.','') даёт строку21.3return21.3 → место вызова → echo21.3
sensorValue(INF) / sensorValue(NAN)is_finite даёт false, ! превращает в truethrow → catch → сообщение; строки измерения нет

Числовая строка: можно посчитать, но сначала нужно понять смысл

Из HTML-поля приходит "12", а из CRM может прийти артикул "0012". Обе строки похожи на числа, но только первая обязана стать количеством. is_numeric проверяет числовую форму, включая экспоненту, а не бизнес-условие «целое количество от 1 до 99».

Отличить арифметику строки от проверки количестваСамостоятельный пример
<?php
$raw = '1e2';
if (is_numeric($raw)) {
    echo 'Числовая форма [', $raw, '] даёт в расчёте ', $raw + 0, "\n";
}
$raw = '12';
echo 'После поступления трёх штук: ', $raw + 3, ' шт.', "\n";
$damaged = '12units';
echo 'Явный (int) отбросил хвост и получил: ', (int) $damaged, "\n";
try {
    $total = 'units' + 3;
} catch (TypeError $error) {
    echo "Расчёт не выполнен: слово нельзя сложить с числом\n";
}
Ожидаемый результат
Числовая форма [1e2] даёт в расчёте 100
После поступления трёх штук: 15 шт.
Явный (int) отбросил хвост и получил: 12
Расчёт не выполнен: слово нельзя сложить с числом

1e2 означает1×10² и подходит is_numeric, но наш магазин не обязан принимать такой ввод количества. Строка12 в сложении даёт15. Явное приведение12units даёт12 без подтверждения допустимого формата; исходный damaged при этом не исправлен. Нечисловое слово вызывает TypeError, поэтому значение total в этом пути не получено.

Для количества договор уже, чем «число PHP»: от1 до99, только обычные ASCII-цифры, без нуля в начале. Ниже выражение /\A[1-9][0-9]?\z/ используется после изучения обычной строки; это отдельная проверка формата, а не определение типа int. Слеши ограничивают шаблон; \A — начало, [1-9] — одна первая цифра, [0-9]? — необязательная вторая, \z — конец. Preg_match возвращает1 при полном совпадении,0 при несовпадении или false при ошибке шаблона. Проверка !==1 отклоняет оба неуспеха; шаблон здесь постоянный и корректный.

Проверенное количество становится суммой заказаСамостоятельный пример
<?php
function quantityFromText(string $text): int {
    if (preg_match('/\A[1-9][0-9]?\z/', $text) !== 1) {
        throw new InvalidArgumentException('Нужно целое от 1 до 99');
    }
    return (int) $text;
}
$unitPrice = 150;
foreach (['12', '1e2', '0012', '12units'] as $text) {
    try {
        $quantity = quantityFromText($text);
        echo '[', $text, ']: ', $quantity, ' шт., к оплате ', $quantity * $unitPrice, ' руб.', "\n";
    } catch (InvalidArgumentException $error) {
        echo '[', $text, ']: заказ не рассчитан — ', $error->getMessage(), "\n";
    }
}
Ожидаемый результат
[12]: 12 шт., к оплате 1800 руб.
[1e2]: заказ не рассчитан — Нужно целое от 1 до 99
[0012]: заказ не рассчитан — Нужно целое от 1 до 99
[12units]: заказ не рассчитан — Нужно целое от 1 до 99

Foreach по очереди даёт четыре текста. Только12 целиком совпадает: функция возвращает int12 в quantity, вызывающий код умножает12×150 и печатает1800. На остальных входах throw прерывает расчёт этой строки, catch показывает конкретный исходный текст и причину; следующая итерация продолжается. Внешнее поле до вызова ещё проверяют на is_string: этот пример уже задаёт четыре строки сам. Разрешение пробелов или ведущих нулей потребует явного изменения договора.

Правило PHP для числовой строкиГраница
Целая формаНеобязательные крайние пробелы, знак +/− и цифры; в PHP8 крайние пробелы допустимы с обеих сторон.
Дробная формаДесятичная точка и цифры; экспонента e/E с необязательным знаком степени. Десятичная запятая сюда не входит.
Числовой префикс с хвостомВ допускающем его числовом контексте берётся префикс и возникает E_WARNING в PHP8; это не корректная полная числовая строка.
Вообще нет числового началаАрифметика PHP8 даёт TypeError. До8.0 поведение включало предупреждение и0; старый совет нельзя переносить механически.
Очень большое целое в текстеКогда оно не помещается в int, числовой результат может стать float и потерять точность. Внешний идентификатор храните строкой.

До PHP8 хвостовые пробелы относили строку к числовому префиксу. До PHP8 для префикса выдавался E_NOTICE, а до7.1 соответствующей диагностики не было. Проверка нового ввода должна задавать собственную политику; историческое поведение не заменяет её.

Тип переменной и объявление типа: где PHP проверяет договор

Вызов boxTotal(3) возвращается в присваивание units
  1. Аргумент int3 → параметр int boxes
  2. Тело: boxes3 × 6 → int18
  3. return18 проходит выходной договор : int
  4. units=18 → подпись о количестве банок

Присваивание $count = 3 создаёт переменную со значением int, но не запрещает позднее присвоить ей строку. Объявление int $count в параметре функции устанавливает договор на её входе. После имени функции : int описывает выход. Сначала прочитайте устройство функции, если эти позиции пока незнакомы.

Проверка на границе
  1. Вызов с аргументом
  2. Проверка типа параметра
  3. Тело функции
  4. Проверка типа результата
Проверенный параметр даёт пригодное число банокСамостоятельный пример
<?php
declare(strict_types=1);
function boxTotal(int $boxes): int {
    return $boxes * 6;
}
$units = boxTotal(3);
echo 'Три коробки содержат ', $units, ' банок', "\n";
try {
    $units = boxTotal('3');
} catch (TypeError $error) {
    echo "Текстовое количество отклонено до расчёта\n";
}
echo 'Последний рассчитанный итог: ', $units, ' банок', "\n";
Ожидаемый результат
Три коробки содержат 18 банок
Текстовое количество отклонено до расчёта
Последний рассчитанный итог: 18 банок

Вызов с int3 создаёт местный boxes3; тело получает18; return после проверки int отдаёт18 в units. Строка3 во втором вызове отвергается на входе из строгого файла: тело вообще не выполняется, новое присваивание units не завершается. Старое18 сохранено. Тип int ещё допускает отрицательные числа: проверка диапазона количества — отдельное правило задачи.

ДоговорЧто разрешаетКогда пригодится
?string или string|nullСтроку либо отсутствиеПоиск необязательного комментария
int|falseОдин из двух типовЧисловой результат либо договорённый признак неудачи
mixedЛюбое обычное значение PHPВход проверки внешних данных; затем тип уточняется
iterableМассив либо TraversableОбработка списка без привязки к способу загрузки
callableВызываемую функцию или методПередать обработку в обход
object / имя классаЛюбой объект / нужный объектРазница общего контейнера и конкретного договора
void / neverБез полезного результата / нормального возврата вообще нетДействие / прерывание исключением
A&B, (A&B)|nullОдновременно два объектных договора / или nullОбъект умеет и перебираться, и сообщать размер

Ресурс имеет тип во время выполнения, но resource нельзя объявить как пользовательский тип параметра. Callable нельзя использовать как тип свойства. Пишите int и bool, а не integer и boolean. Объединения появились в 8.0, пересечения в 8.1, сочетание в DNF в 8.2. Повторяющиеся и заведомо избыточные типы, например bool|false, запрещены. Типы свойств и наследование разбираются в ООП.

null, mixed и специальные результаты без догадок

Отсутствующий комментарий не равен пустой строкеСамостоятельный пример
<?php
declare(strict_types=1);

function commentLabel(?string $comment): string
{
    if ($comment === null) {
        return 'Не запрашивали';
    }
    return $comment === '' ? 'Оставлено пустым' : $comment;
}
echo commentLabel(null), "\n";
echo commentLabel(''), "\n";
echo commentLabel('Позвонить'), "\n";
Ожидаемый результат
Не запрашивали
Оставлено пустым
Позвонить

?string разрешает null, но не делает аргумент необязательным. Для пропуска нужен default, например ?string $comment = null. В PHP 8.4 неявная nullable-запись string $comment = null устарела: отсутствие указывается в типе явно.

mixed — место, где ещё нужно разобраться во входеСамостоятельный пример
<?php
declare(strict_types=1);

function skuFromInput(mixed $raw): string
{
    if (!is_string($raw) || $raw === '') {
        throw new InvalidArgumentException('Нужен непустой артикул');
    }
    return $raw;
}
foreach (['0012', 12, null] as $raw) {
    try {
        echo skuFromInput($raw), "\n";
    } catch (InvalidArgumentException $error) {
        echo "Неверный артикул\n";
    }
}
Ожидаемый результат
0012
Неверный артикул
Неверный артикул

Неизвестный тип принимается намеренно, затем сужается проверкой. mixed не объединяют с другими типами: они уже в него входят. Литеральные true, false и null можно объявлять самостоятельно начиная с PHP 8.2; они полезны для точного договора, но обычный переключатель чаще имеет тип bool.

Действие, прекращение работы и последовательность данных

Здесь сравниваются два результата выполнения: печать продолжает вызывающий код, а отказ передаёт управление обработчику. Null ниже показан намеренно как диагностическое наблюдение вызова void; он не является напечатанной строкой и не используется как статус заказа.

Функция печатает строку; вызывающий код продолжает работуСамостоятельный пример
<?php
function showStatus(string $text): void {
    echo $text, "\n";
}
$result = showStatus('Заказ 41: комплектуется');
echo 'Значение вызова для наблюдения: ';
var_dump($result);
echo "Можно напечатать следующую строку отчёта\n";
Ожидаемый результат
Заказ 41: комплектуется
Значение вызова для наблюдения: NULL
Можно напечатать следующую строку отчёта

Строка из аргумента попадает в местный text. Echo внутри сразу отправляет её в вывод. Завершение функции возвращает управление к месту вызова; наблюдаемое значение — null, а не текст статуса. Следующий echo выполняется. У void разрешён пустой return; для раннего выхода, но return с выражением запрещён.

Отказ не возвращает значение в расчёт заказаСамостоятельный пример
<?php
function rejectOrder(): never {
    throw new DomainException('Нет остатка');
}
try {
    rejectOrder();
    echo "Заказ можно выдать\n";
} catch (DomainException $error) {
    echo 'Заказ отклонён: ', $error->getMessage(), "\n";
}
echo "Переходим к следующей проверке\n";
Ожидаемый результат
Заказ отклонён: Нет остатка
Переходим к следующей проверке

RejectOrder не возвращается обычным путём; выполнение перескакивает в ближайший подходящий catch. Строка о выдаче пропущена. После catch программа продолжается, что видно в последней строке. Never доступен с PHP8.1 и запрещает любой return; выход из процесса и бесконечное выполнение — другие способы не вернуться, но здесь использовано исключение.

Один расчёт принимает массив и объект-последовательностьСамостоятельный пример
<?php
declare(strict_types=1);

function totalUnits(iterable $counts): int
{
    $total = 0;
    foreach ($counts as $count) {
        if (!is_int($count) || $count < 0) {
            throw new InvalidArgumentException('Нужно целое количество');
        }
        $total += $count;
    }
    return $total;
}
echo totalUnits([2, 3]), "\n";
echo totalUnits(new ArrayIterator([4, 5])), "\n";
Ожидаемый результат
5
9

Iterable гарантирует возможность перебора, но не тип каждого элемента и не возможность повторного прохода. Например, генератор может быть одноразовым. Поэтому проверка count остаётся внутри расчёта. Объекты ArrayIterator и генераторы подробно объясняются в главе об итераторах.

Вход totalUnitsВнутри функцииВозврат
Массив[2,3]foreach берёт2, затем3; каждое целое и неотрицательное; total0→2→5int5 используется внешним echo как итог единиц
ArrayIterator с[4,5]Тот же foreach получает4, затем5 из объекта-последовательности; total0→4→9int9 используется тем же вызывающим кодом

Iterable — встроенное сокращение array|Traversable, а не обещание одинакового устройства этих значений. Это не автоматически countable, не тип каждого элемента и не гарантия повторного прохода. ArrayIterator в опыте хранит готовую последовательность; его устройство разбирается в связанном уроке итераторов.

Ресурс и составной объектный тип: два разных договора

Открыли ресурс, использовали, закрылиСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen('php://memory', 'w+b');
if ($stream === false) {
    throw new RuntimeException('Не удалось открыть поток');
}
try {
    var_dump(is_resource($stream));
    fwrite($stream, 'SKU-17');
    rewind($stream);
    echo stream_get_contents($stream), "\n";
} finally {
    fclose($stream);
}
var_dump(is_resource($stream));
Ожидаемый результат
bool(true)
SKU-17
bool(false)

Ресурс — выданный PHP доступ к внешнему или внутреннему механизму, например открытому потоку. Это не содержимое файла. После fclose такой доступ закрыт. Некоторые расширения в современных версиях используют специальные объекты вместо ресурсов; проверяйте контракт конкретной функции.

Когда нужны и перебор, и число элементовСамостоятельный пример
<?php
declare(strict_types=1);

function batchSize((Countable&Traversable)|null $batch): int
{
    return $batch === null ? 0 : count($batch);
}
echo batchSize(new ArrayIterator(['A', 'B'])), "\n";
echo batchSize(null), "\n";
try {
    batchSize(['A', 'B']);
} catch (TypeError $error) {
    echo "Массив не является требуемым объектом\n";
}
Ожидаемый результат
2
0
Массив не является требуемым объектом

Countable и Traversable — имена интерфейсов. Знак & требует оба, |null допускает отсутствие, скобки отделяют пересечение. Это DNF-тип(PHP 8.2+). Массив можно пересчитать и обойти, но он не реализует интерфейсы объектов. Если реально нужно поддержать массив, добавьте его в договор или выберите iterable и иной способ подсчёта. Новичку достаточно вернуться к этому примеру после интерфейсов.

Переменная хранит значение с конкретным смыслом

У заказа есть идентификатор, сумма, признак оплаты и список позиций. Эти данные ведут себя по-разному. int удобен для количества и копеек, string — для артикула и телефона, bool — для решения «оплачен», null — для осмысленного отсутствия, array — для набора. Строка "00127" может быть артикулом, где нули значимы; превращать любой цифровой текст в число нельзя. Тип отвечает на вопрос «какие операции допустимы», а имя — «что означает значение в нашем сервисе».

ЗначениеСмыслПроверка
0Нулевой остаток товараДопустим, если контракт разрешает
nullПоставщик не сообщил остатокНе подменять нулем автоматически
"0"Текст из HTML-формыПроверить и нормализовать
[]Пустой список позицийРешить, допустим ли пустой заказ

PHP динамически типизирован: переменной можно присвоить значение другого типа. Типы аргументов и результата ограничивают границы функций. declare(strict_types=1) влияет на скалярные вызовы из данного файла; он не превращает массив $_POST в типизированные данные и не запрещает все преобразования языка. Проверка внешнего контракта остается отдельной обязанностью.

Текст формы не равен целому количеству

Проверка и нормализация количестваСамостоятельный пример
<?php
declare(strict_types=1);

function parseQuantity(mixed $raw): int
{
    if (!is_string($raw) || preg_match('/\A[1-9][0-9]{0,2}\z/', $raw) !== 1) {
        throw new InvalidArgumentException('Количество: целое от1 до999');
    }

    return (int) $raw;
}
foreach (['12', '0', '12товаров', ['12']] as $raw) {
    try {
        echo parseQuantity($raw), "\n";
    } catch (InvalidArgumentException $exception) {
        echo "Некорректно\n";
    }
}
Ожидаемый результат
12
Некорректно
Некорректно
Некорректно

Сначала проверяется тип: пользователь может отправить quantity[]=12. Затем ограниченная грамматика и диапазон. Здесь ведущие нули намеренно запрещены контрактом. Числовое приведение происходит только после проверки, поэтому "12 товаров" не станет заказом из 12 штук.

Приблизительные числа и денежный расчет

Целые единицы и явная политика скидкиСамостоятельный пример
<?php
declare(strict_types=1);

$subtotalKopecks = 19990 * 3;
$discountPercent = 10;
// Для этого магазина дробная копейка скидки отбрасывается.
$discountKopecks = intdiv($subtotalKopecks * $discountPercent, 100);
echo $subtotalKopecks - $discountKopecks, "\n";
echo ((0.1 + 0.2) === 0.3) ? "равно\n" : "не строго равно\n";
Ожидаемый результат
53973
не строго равно

Это демонстрация одной объявленной политики округления, не универсальное правило бухгалтерии. Проверьте ограничения суммы, чтобы произведение не вышло за PHP_INT_MAX. Для десятичных расчетов сложной финансовой области выбирают decimal/BCMath и согласованную точность. float уместен для измерений с допуском.

Не путать проверку присутствия и истинности

isset($row["stock"]) вернет false и при отсутствии ключа, и при значении null. array_key_exists различает эти случаи. empty объединяет еще больше состояний, включая 0 и "0". Это полезно для некоторых флагов, но опасно для остатков, кодов и платежных значений. Оператор ?? задает резервное значение при отсутствии/null; он не лечит неверную вложенную схему. Для каждого поля заранее сформулируйте: обязательное ли оно, может ли быть null, какой диапазон и что означает пустая строка.

Передача массива по значению использует механизмы PHP, позволяющие откладывать копирование до изменения; это не обещание нулевой стоимости для любой операции. Объектные переменные указывают на один объект после обычного присваивания. Эти две модели подробно сравниваются в главах об объектах и клонировании. Отладочный get_debug_type показывает фактический тип, но не заменяет бизнес-проверки.

Попробуйте сами. Поставщик прислал {"stock":0,"price":null}. Можно ли скрыть оба поля через empty?

Разбор решения

Нет. Ноль остатка означает «нет в наличии», null цены — «цена неизвестна». Оба состояния важны пользователю. Проверяйте ключ, затем тип и допустимость null, отображайте разные объяснения.

Объект и массив: похожие данные ещё не означают один тип

У массива значение читается по ключу, например $row['name']. У объекта свойство читается через стрелку: $object->name. Класс задаёт устройство объекта; new создаёт экземпляр. Объект может также выполнять методы. Для знакомства с полной моделью есть отдельная начальная глава. Здесь разбираем именно преобразование представления.

Преобразование одной записи каталога
  1. Массив: ключ name → Чай
  2. Каст (object) создаёт stdClass
  3. Свойство name → Чай
  4. Вложенные значения сохраняют свои прежние типы
Преобразование не уходит рекурсивно внутрьСамостоятельный пример
<?php
$row = ['name' => 'Чай', 'sizes' => ['small', 'large']];
$product = (object) $row;
echo $product->name, "\n";
var_dump(is_array($product->sizes));
$again = (object) $product;
var_dump($again === $product);
Ожидаемый результат
Чай
bool(true)
bool(true)

Получился объект встроенного класса stdClass. Ключи массива стали именами свойств, а sizes остался массивом. Каст уже существующего объекта в object не клонирует его. === подтверждает, что это тот же экземпляр, поэтому изменение его свойств видно через обе переменные.

null даёт пустой объект, скаляр — свойство scalarСамостоятельный пример
<?php
$empty = (object) null;
var_dump(get_object_vars($empty));
$label = (object) 'SKU-12';
echo $label->scalar, "\n";
$numeric = (object) [12 => 'Двенадцать'];
echo $numeric->{'12'}, "\n";
Ожидаемый результат
array(0) {
}
SKU-12
Двенадцать

get_object_vars здесь получает публичные свойства простого stdClass. Числовое имя свойства читается через фигурные скобки и строку. Преобразование произвольного слова в объект со свойством scalar почти никогда не создаёт полезную предметную модель: лучше явно назвать необходимые данные.

Свойства внутреннего класса могут иметь особые правила, а приватные и защищённые поля требуют учёта видимости при обратном преобразовании. Не заменяйте проектирование модели цепочкой JSON encode/decode ради случайного приведения типов. Для API выбирайте заранее: массив с проверенной схемой или конкретный объект данных.

PHP проверяет имя договора, а не только похожесть методов

Два разных объекта могут иметь одинаковый метод send, но это не делает их экземплярами одного интерфейса. PHP использует номинальные типы: принадлежность задаётся объявленным классом, наследованием и implements. Интерфейс — названный договор доступных действий; класс явно сообщает, что выполняет его.

Похожий метод без implements не выполняет объявленный договорСамостоятельный пример
<?php
interface NoticeSender
{
    public function send(string $message): void;
}
class ScreenSender implements NoticeSender
{
    public function send(string $message): void
    {
        echo $message, "\n";
    }
}
class UnrelatedSender
{
    public function send(string $message): void
    {
        echo $message, "\n";
    }
}
function notify(NoticeSender $sender): void
{
    $sender->send('Заказ готов');
}
notify(new ScreenSender());
try {
    notify(new UnrelatedSender());
} catch (TypeError $error) {
    echo "Нет объявленного договора NoticeSender\n";
}
Ожидаемый результат
Заказ готов
Нет объявленного договора NoticeSender

public означает доступность метода вызывающему коду, : void — отсутствие полезного результата. Одинаковое тело send у двух классов не заменило implements. Это помогает связать части сервиса через явный договор, который проверяется PHP.

Соответствие объявлений методов при наследовании проверяется при загрузке объявления класса; значения аргументов и результатов проверяются при выполнении вызовов. Допустимое расширение входа и сужение результата называется контравариантностью и ковариантностью; практическая сторона разобрана в интерфейсах и в наследовании.

self, parent и static: тип относительно места в иерархии

Когда базовый класс расширяют, абсолютное имя типа иногда связывает код с конкретным названием сильнее, чем нужно. Относительные типы описывают связь с текущей иерархией. self означает класс, где написано объявление; parent — его прямого родителя. Эти два имени допустимы только внутри класса. static разрешён как тип результата и означает класс, через который метод фактически вызвали.

Два разных вопроса о типе
  1. self → значение должно быть экземпляром класса объявления или его потомка
  2. parent → значение должно быть экземпляром родительского класса или его потомка
  3. static в результате → вернуть экземпляр фактически вызванного класса
  4. PHP проверяет переданное или возвращённое значение и при нарушении выдаёт TypeError
Один метод принимает только товар, другой — любой элемент каталогаСамостоятельный пример
<?php
class CatalogItem
{
    public function __construct(public string $name) {}
}
class Product extends CatalogItem
{
    public function productLabel(self $item): string
    {
        return 'self: ' . $item->name;
    }
    public function catalogLabel(parent $item): string
    {
        return 'parent: ' . $item->name;
    }
}
$product = new Product('Чай');
$category = new CatalogItem('Категория напитков');
echo $product->productLabel($product), "\n";
echo $product->catalogLabel($category), "\n";
echo $product->catalogLabel($product), "\n";
try {
    $product->productLabel($category);
} catch (TypeError $error) {
    echo "Категория не является Product\n";
}
Ожидаемый результат
self: Чай
parent: Категория напитков
parent: Чай
Категория не является Product

Внутри Product имя self обозначает Product, поэтому обычный CatalogItem отклонён. parent обозначает CatalogItem; экземпляр Product тоже подходит, потому что наследуется от CatalogItem. Имена параметров item локальны для каждого вызова, стрелка читает публичное свойство name.

Фабрика базового класса сохраняет конкретный тип потомкаСамостоятельный пример
<?php
class ExportJob
{
    public static function fromDefinition(): self
    {
        return new self();
    }
    public static function fromCalledClass(): static
    {
        return new static();
    }
}
class CsvExportJob extends ExportJob {}
echo get_class(CsvExportJob::fromDefinition()), "\n";
echo get_class(CsvExportJob::fromCalledClass()), "\n";
Ожидаемый результат
ExportJob
CsvExportJob

Метод унаследован и оба раза вызван через CsvExportJob. new self создаёт класс места объявления — ExportJob. new static учитывает позднее статическое связывание и создаёт CsvExportJob; тип результата static обещает это вызывающему коду. Пример намеренно использует пустые конструкторы: если потомок потребует другие обязательные аргументы, универсальная фабрика new static() перестанет быть корректной.

Относительные типы не заменяют интерфейс для независимых реализаций. Если складской и платёжный клиенты должны выполнять общий договор, используйте интерфейс. Если нужна фабрика, возвращающая потомка, изучите позднее статическое связывание и закройте возможность несовместимого конструктора. Для обычной функции вне класса укажите конкретный класс или интерфейс.

Ресурс: две переменные могут держать один открытый поток

Нужен временный текст отчёта, который несколько участков кода читают через один открытый поток. Поток — последовательность байтов; ресурс здесь хранит доступ к нему. php://memory создаёт поток в памяти текущего процесса, а режим w+b разрешает запись и чтение байтов. Он не создаёт сохраняемый файл на диске.

Если открыть файл, PHP возвращает специальное значение — handle ресурса. Его передают операциям чтения и закрытия. Нельзя получить рабочий файл, приведя число к resource: нужен результат конкретной функции открытия. Некоторые современные расширения возвращают специальные объекты вместо старых ресурсов; это определяется API и версией.

Записать и прочитать полезный текст через один ресурсСамостоятельный пример
<?php
$stream = fopen('php://memory', 'w+b');
if ($stream === false) {
    throw new RuntimeException('Не удалось открыть поток');
}
fwrite($stream, "Заказ 12: упакован\n");
rewind($stream);
$sameStream = $stream;
$report = stream_get_contents($sameStream);
if ($report === false) {
    throw new RuntimeException('Не удалось прочитать отчёт');
}
echo $report;
fclose($stream);
if (!is_resource($sameStream)) {
    echo "Повторное чтение запрещено: доступ уже закрыт\n";
}
echo 'Оставшееся значение: ', get_debug_type($sameStream), "\n";
Ожидаемый результат
Заказ 12: упакован
Повторное чтение запрещено: доступ уже закрыт
Оставшееся значение: resource (closed)

Fwrite получает ресурс и строку и возвращает число записанных байтов или false; здесь маленький текст пишется в локальную память. Rewind возвращает указатель к началу; stream_get_contents возвращает оставшиеся байты как строку или false. Report получает текст, echo его выводит. SameStream не открыл второй поток: fclose закрывает общий доступ. На диске отчёт не сохраняется. В производственном потоке проверяют успех записи/rewind и обеспечивают закрытие через finally.

PHP освобождает обычный ресурс, когда больше нет удерживающих его ссылок, но это не заменяет своевременное закрытие нужного вам файла. Явный finally полезен, чтобы не держать файл до конца длинного задания. Постоянные соединения отдельных расширений имеют особый жизненный цикл. Тип параметра resource в пользовательской функции объявить нельзя: проверяют значение и договор функции внутри, либо используют собственный объект-обёртку.

callable: передать действие вместо немедленного результата

Как выполняется prepareCode во втором опыте
  1. Вызов передаёт строку с пробелами и значение trim(...), которое можно вызвать позже.
  2. Местный code хранит строку, местный normalize — действие. Callable не является уже готовым результатом.
  3. Выражение normalize(code) вызывает переданное действие с этим текстом и получает tea-12.
  4. Return отдаёт tea-12 в prepareCode(...); наружный echo использует именно эту строку.

Иногда одному обработчику нужно отдать правило преобразования: например, перед выгрузкой нормализовать артикул. Если записать strtoupper('tea'), функция выполнится сейчас и получится строка TEA. Если передать вызываемое значение, другой участок выполнит действие позже со своими данными. Такой параметр часто называют callback — переданный обработчик.

Создать вызываемое значение и вызвать его позжеСамостоятельный пример
<?php
$normalize = strtoupper(...);
echo get_debug_type($normalize), "\n";
echo $normalize('tea-12'), "\n";
Ожидаемый результат
Closure
TEA-12

Многоточие внутри этих скобок — специальный синтаксис создания first-class callable в PHP 8.1+, а не пропущенный фрагмент учебника и не распаковка массива. Первая строка не вызывает strtoupper для строки; она создаёт Closure. Вторая форма скобок с tea-12 уже вызывает сохранённое действие.

Одному обработчику передаются разные правилаСамостоятельный пример
<?php
function prepareCode(string $code, callable $normalize): string
{
    return $normalize($code);
}
echo prepareCode(' tea-12 ', trim(...)), "\n";
echo prepareCode('tea-12', strtoupper(...)), "\n";
Ожидаемый результат
tea-12
TEA-12

callable гарантирует возможность вызова в данном контексте, но не описывает число, типы аргументов и смысл результата для каждого обработчика. Наш договор дополнительно требует действие string → string. Если нужно одновременно обрезать пробелы и изменить регистр, передайте функцию, которая явно делает оба шага.

Параметр callable не разрешён как тип свойства класса. Для хранения уже подготовленного обработчика подходит Closure; преобразование из обычного callable выполняет Closure::fromCallable. Проверка is_callable не выполняет действие и не доказывает его безопасность. Не берите имя исполняемой функции напрямую из параметра запроса.

Какие формы callable встречаются в библиотеках

ФормаЧто передано
'trim'Имя доступной функции; в пространстве имён полагаться на автоматическое добавление текущего namespace нельзя.
[Formatter::class, 'clean']Имя класса и доступный статический метод.
[$formatter, 'clean']Конкретный объект и его доступный метод.
$object с __invokeОбъект, специально умеющий вызываться как функция.
function (...) {...}, fn(...) => ...Анонимная или стрелочная функция; результатом создания является Closure.
$object->method(...)Closure, привязанный к допустимому методу в момент получения; PHP 8.1+.
Метод конкретного объекта и вызываемый объектСамостоятельный пример
<?php
class CodePrefix
{
    public function add(string $code): string
    {
        return 'SHOP-' . $code;
    }
    public function __invoke(string $code): string
    {
        return $this->add($code);
    }
}
$prefix = new CodePrefix();
$method = [$prefix, 'add'];
echo $method('12'), "\n";
echo $prefix('13'), "\n";
var_dump(is_callable([CodePrefix::class, 'add']));
Ожидаемый результат
SHOP-12
SHOP-13
bool(false)

add — нестатический метод: ему нужен экземпляр. Начиная с PHP 8 нельзя выдавать такую пару имя класса + нестатический метод за допустимый статический callback. __invoke делает сам объект вызываемым; $this обозначает текущий экземпляр.

Конструкции языка echo, isset, empty, unset, list и подобные не являются обычными функциями для такого вызова. Когда нужно обернуть их в обработчик, пишут собственную функцию с понятным договором. Контекстно-зависимые старые записи вроде 'self::method' как callable устарели с PHP 8.2; используйте конкретный класс либо Closure, полученный в разрешённой области. Видимость методов продолжает действовать.

true и false как точные типы результата

Точный тип true подходит, когда вызывающий код хочет явное подтверждение, а не произвольный bool. Ниже функция проверяет только учебный признак готовности: она не создаёт файл и не отправляет заказ. Return true — подтверждение её договора, исключение — другой путь завершения.

Обычный bool допускает два значения. Иногда контракт точнее: действие либо выполнено и возвращает true, либо прерывается исключением. Тогда тип true помогает не вернуть false случайно. Это тип с единственным значением, а не паттерн «Singleton-объект».

Подтверждение используется для следующего действияСамостоятельный пример
<?php
function confirmPrepared(bool $prepared): true
{
    if (!$prepared) {
        throw new LogicException('Сначала подготовьте данные');
    }
    return true;
}
$confirmed = confirmPrepared(true);
if ($confirmed) {
    echo "Данные готовы: можно показывать кнопку печати\n";
}
try {
    confirmPrepared(false);
} catch (LogicException $error) {
    echo 'Кнопка недоступна: ', $error->getMessage(), "\n";
}
Ожидаемый результат
Данные готовы: можно показывать кнопку печати
Кнопка недоступна: Сначала подготовьте данные

Первый вызов возвращает true в confirmed, и условие выбирает дальнейшее сообщение. Второй вызов останавливается на throw и до return не доходит; catch выводит причину. Тип true разрешён с PHP 8.2; одиночный false тоже с 8.2, false в union — с 8.0. True|false записывают как bool. Если подтверждение не требуется, часто достаточно void.

Нельзя объявить произвольный литеральный тип 'paid' как собственный singleton-тип. Для конечного набора статусов есть перечисления enum. Отдельный тип null также допускает одно значение; его не следует путать с void, который описывает отсутствие полезного возвращаемого результата.

Неявное преобразование при union-типе не идёт слева направо

Вход в старый API без strict_typesВыбранный типПочему это имеет практическое значение
Код "12" в int|stringstring: уже разрешённый исходный типМожно сохранить внешний код текстом.
"12" в int|boolint 12Допускается числовое преобразование.
"no" в int|boolbool trueНепустая строка не является парсером слова «нет».
"12.5" в int|floatfloat 12.5Дробную часть сохраняет выбор float.

В слабом режиме сначала сохраняется допустимый исходный тип. Если требуется скалярное преобразование, PHP выбирает из поддерживаемых типов по правилам, а не по порядку записи через |. Для подходящих скалярных вариантов общий приоритет: int, затем float, затем string, затем bool; у числовых строк при сочетании int и float учитывается форма числа. Это ещё одна причина разбирать внешние значения явно.

Преобразование union меняет подпись, лимит и массуСамостоятельный пример
<?php
function orderIdLabel(int|string $value): string {
    return 'Заказ ' . $value;
}
function legacyRowLimit(int|bool $value): int {
    return is_int($value) ? $value : ($value ? 100 : 0);
}
function packedWeight(int|float $kg): float {
    return $kg + 0.2;
}
echo orderIdLabel('0012'), "\n";
echo 'Лимит из текста 12: ', legacyRowLimit('12'), ' строк', "\n";
echo 'Лимит из текста no: ', legacyRowLimit('no'), ' строк', "\n";
echo 'Посылка: ', packedWeight('12.5'), ' кг', "\n";
Ожидаемый результат
Заказ 0012
Лимит из текста 12: 12 строк
Лимит из текста no: 100 строк
Посылка: 12.7 кг

Этот файл намеренно нестрогий. У orderIdLabel исходный string уже разрешён: ведущие нули остаются в подписи. У legacyRowLimit строка12 преобразуется в int12, и функция возвращает этот лимит. Слово no не годится для int, зато превращается в booltrue: старый договор трактует его как100 строк, хотя человек мог ожидать запрет. У packedWeight строка12.5 выбирает float, тело добавляет упаковку0.2 и возвращает12.7 в echo. Такие перегруженные договоры показаны для чтения старого API; новый текстовый ввод лучше разбирать явно.

Array, object и ресурс не превращаются в подходящий скаляр просто потому, что в union есть несколько вариантов. Возможность строкового представления отдельных объектов также имеет собственный договор. Значения null и числовые строки требуют проверки, особенно при устаревших вызовах внутренних функций. Точная сигнатура конкретной функции важнее привычки к автоматическим преобразованиям.

strict_types относится к месту вызова, а не к проекту целиком

Каждый из двух ZIP ниже — отдельный законченный проект из example.php и price.php. Распакуйте в разные папки и запустите php example.php. require загружает объявления второго файла; __DIR__ даёт папку текущего файла. PackagePrice получает число коробок, умножает его на 150 и возвращает цену, которую использует внешний echo. Критическая разница опытов — strict_types именно в файле с вызовом.

Объявление declare(strict_types=1); в библиотечном файле не делает строгими все сторонние вызовы её функций. Для проверки скалярного аргумента важен вызывающий файл. Для скалярного результата — файл объявления возвращающей функции. Это можно наблюдать без сервера и базы.

Слабый вызывающий файл и строгая библиотекаСамостоятельный пример
<?php
require __DIR__ . '/price.php';
echo packagePrice('2'), "\n";
Ожидаемый результат
300

price.php содержит strict_types=1 и объявление int $boxes, но место вызова в example.php работает в слабом режиме. Строка 2 допускает преобразование в int. В ZIP лежат оба файла.

Строгий вызывающий файл отклоняет тот же аргументСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__ . '/price.php';
try {
    echo packagePrice('2'), "\n";
} catch (TypeError $error) {
    echo "Строка вместо int отклонена\n";
}
Ожидаемый результат
Строка вместо int отклонена

Теперь strict_types=1 стоит именно перед вызовом. Библиотека при этом может не объявлять строгий режим. В обоих случаях правила самой арифметики и явных кастов не превратились в другую разновидность PHP.

Даже строгий скалярный договор float допускает int. Strict_types также не проверяет автоматически содержимое каждого элемента array и не превращает внутренние callback-вызовы расширений в обычные вызовы из вашего файла. Допустимость связанного результата и поведение конкретного callback отдельно исследуются на его API.

У возвращаемого значения место строгой проверки другое: файл объявления самой функции. Следующий ZIP содержит три файла; даже при строгом example.php функция из weak-return.php может допустимо преобразовать свой строковый результат. Такой эксперимент помогает обнаружить несоответствие библиотечного договора и её тела.

Два файла возвращают одну и ту же строку под договором intСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__ . '/weak-return.php';
require __DIR__ . '/strict-return.php';
$total = weakTotal();
echo 'Слабая библиотека: ', $total, ' руб., тип ', get_debug_type($total), "\n";
try {
    $total = strictTotal();
} catch (TypeError $error) {
    echo "Строгая библиотека: строка не прошла договор результата int\n";
}
echo 'Последний пригодный итог: ', $total, ' руб.', "\n";
Ожидаемый результат
Слабая библиотека: 300 руб., тип int
Строгая библиотека: строка не прошла договор результата int
Последний пригодный итог: 300 руб.

В обеих функциях литерал300 намеренно написан в кавычках. В weakTotal возвращаемая числовая строка допускает преобразование в int300; вызывающий код использует его как сумму. StrictTotal объявлена в строгом файле и выдаёт TypeError до возврата в присваивание. Тело нужно исправить на числовой расчёт или правильный договор; catch здесь позволяет увидеть оба пути. Ни один из файлов не получает случайное текущее состояние окружения.

Строгий вызов не отменяет правила арифметики внутриСамостоятельный пример
<?php
declare(strict_types=1);
function orderTotal(int $goods): int {
    $deliveryText = '50';
    return $goods + $deliveryText;
}
$total = orderTotal(300);
echo 'Товары и доставка: ', $total, ' руб.', "\n";
Ожидаемый результат
Товары и доставка: 350 руб.

На входе int300 соответствует параметру. Затем + использует числовой контекст: строка50 участвует как число, получается int350. Выходной договор int тоже выполнен. Strict_types не запрещает это преобразование внутри выражения. Для внешнего deliveryText нужна явная проверка до расчёта; в опыте литерал заранее известен.

Похожее ожидание описано в вопросе о сложении строки внутри строгой функции: автор ожидал запрета любой конверсии. Следите отдельно за входом функции, её выражениями и выходом; у них разные правила.

Функции и методы этой темы

Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.

get_debug_typeПолучить понятное имя типа для сообщения об ошибке или локальной диагностики.

PHP 8.0+ · standard

get_debug_type(mixed $value): string

Получить понятное имя типа для сообщения об ошибке или локальной диагностики.

Часть контрактаЧто это означает
valueЗначение любого типа.
ВозвратСтрока: bool/int/float/string/array/null, класс объекта либо описание ресурса. Не возвращает само значение.
Получить имя типа строкойСамостоятельный пример
<?php
echo get_debug_type(12), "
";
echo get_debug_type(12.0), "
";
echo get_debug_type(null);
Ожидаемый результат
int
float
null

Функция возвращает строку; echo отдельно показывает её. В отличие от gettype здесь имена int/float/null согласованы с привычной записью типов.

Сообщить о неожиданном поле без его содержимогоСамостоятельный пример
<?php
$payload = ["sku" => ["TEA"]];
$sku = $payload["sku"] ?? null;
if (!is_string($sku)) {
    echo "sku: ожидалась string, получена ", get_debug_type($sku);
}
Ожидаемый результат
sku: ожидалась string, получена array

Сообщение не выводит значение поля и токены запроса. Для обработчика HTTP выберите корректный статус/формат ошибки; этот пример показывает только локальную диагностику.

Открытый и закрытый поток имеют разные обозначенияСамостоятельный пример
<?php
$stream = fopen("php://memory", "r+");
echo get_debug_type($stream), "
";
fclose($stream);
echo get_debug_type($stream);
Ожидаемый результат
resource (stream)
resource (closed)

Переменная с закрытым дескриптором ещё существует, но поток уже нельзя использовать. Для проверки работоспособного ресурса служит is_resource.

Для именованного объекта возвращается полное имя класса; для анонимного — обозначение с @anonymous, которое не следует принимать за стабильный идентификатор объекта. Строка "12" даст string. Для ветвления по типу применяйте соответствующую is_*-проверку или instanceof, не сравнение диагностических сообщений.

Рядом: gettype, var_dump, is_resource. К основам: переменная и присваивание; тип значения.

Ещё один прикладной разбор. После основных правил можно перейти к следующему примеру.

Возвращает понятное имя скалярного типа или класса объекта. Помогает объяснить ошибку контракта, не печатая само содержимое значения.

Ошибка поля без раскрытия содержимогоСамостоятельный пример
<?php
declare(strict_types=1);

$quantity = ['12'];
echo 'Ожидалась строка, получен ', get_debug_type($quantity), "\n";
Ожидаемый результат
Ожидалась строка, получен array

Массив может прийти из quantity[]=12. В журнале достаточно пути поля и типа; не печатайте целиком POST с паролями.

Проверить результат адаптераСамостоятельный пример
<?php
declare(strict_types=1);

$result = new DateTimeImmutable('2026-10-12');
echo get_debug_type($result), "\n";
Ожидаемый результат
DateTimeImmutable

Диагностика показывает конкретный класс. Для принятия значения используйте DateTimeInterface или нужный контракт, а не сравнение имени строкой.

Сравните. var_dump показывает также значения и может раскрыть секреты; instanceof проверяет объектный контракт.

is_arrayПроверить, что получено именно PHP-значение array.

PHP 4+ · standard

is_array(mixed $value): bool

Проверить, что получено именно PHP-значение array.

Часть контрактаЧто это означает
value: mixedУже полученное значение любого PHP-типа. Чтение неизвестной переменной происходит до вызова; функция не скрывает такое предупреждение.
Возврат: bool[] и непустой массив дают true; объект ArrayAccess и JSON-текст — false. Схему элементов проверяют отдельно.
Изменение данныхНе преобразует и не заменяет value. true/false — ответ на конкретную проверку.
Увидеть ответ проверкиСамостоятельный пример
<?php
$value = ["tea", "bread"];
var_dump(is_array($value));
var_dump(is_array("[1,2]"));
Ожидаемый результат
bool(true)
bool(false)

Квадратные скобки создают массив, кавычки — строку. Похожее написание внутри строки не создаёт массив автоматически.

Следующая ступень использует условия и циклы. Проверяем вход до действия; исключение сообщает о нарушенном договоре, а не исправляет данные автоматически.

Проверить список и напечатать остаток каждой позицииСамостоятельный пример
<?php
$payload = ['items' => [['sku' => 'TEA-01', 'stock' => 0], ['sku' => 'COF-02', 'stock' => 5]]];
$items = $payload['items'] ?? null;
if (!is_array($items)) {
    throw new UnexpectedValueException('Нет массива items');
}
foreach ($items as $item) {
    if (!is_array($item) || !is_string($item['sku'] ?? null)
        || !is_int($item['stock'] ?? null) || $item['stock'] < 0) {
        throw new UnexpectedValueException('Повреждённая позиция');
    }
    echo $item['sku'], ': доступно ', $item['stock'], ' шт.', "\n";
}
Ожидаемый результат
TEA-01: доступно 0 шт.
COF-02: доступно 5 шт.

Is_array(items) возвращает true, поэтому !true ложно и первый throw не выполняется. Каждая item проверяется отдельно: общий array не обещает форму детей. После короткого замыкания || читать stock безопасно только в подходящей ветви. В отчёт попадает и полезный ноль, и пять штук. Здесь payload уже является PHP-массивом; строка JSON ещё не прошла бы is_array.

Рядом: is_iterable, is_object. К основам: переменная и присваивание; тип значения.

Ещё один прикладной разбор. После основных правил можно перейти к следующему примеру.

Возвращает bool без преобразования входа. Первый шаг перед обращением к вложенному JSON или списку формы.

Поле items действительно коллекцияСамостоятельный пример
<?php
declare(strict_types=1);

$payload = ['items' => 'unexpected'];
echo is_array($payload['items'] ?? null) ? "Можно проверять элементы\n" : "Неверный типitems\n";
Ожидаемый результат
Неверный типitems

После этой границы проверяют ключи и типы каждой записи. Не приводите любую строку к(array): это скроет нарушение API.

Отличить объект от массива после JSONСамостоятельный пример
<?php
declare(strict_types=1);

$value = json_decode('{"id":7}', false, 16, JSON_THROW_ON_ERROR);
echo is_array($value) ? "array\n" : "object\n";
Ожидаемый результат
object

Параметр associative определяет представление JSON-объектов. Выберите одно представление в адаптере и не смешивайте $x->id и $x["id"] произвольно.

Сравните. array_is_list проверяет форму ключей; is_iterable принимает array или Traversable.

is_stringПроверить строковый тип без приведения массивов и чисел.

PHP 4+ · standard

is_string(mixed $value): bool

Как использовать результат. Вызов is_string(raw) решает, разрешено ли передать raw строковым операциям. Он не возвращает артикул. После проверки trim возвращает новую строку sku; именно sku затем выводится. Пустота, длина и существование товара — самостоятельные проверки.

Проверить строковый тип без приведения массивов и чисел.

Часть контрактаЧто это означает
value: mixedУже полученное значение любого PHP-типа. Чтение неизвестной переменной происходит до вызова; функция не скрывает такое предупреждение.
Возврат: boolПустая строка, "0" и бинарная строка дают true. Кодировка UTF-8, длина и формат проверяются отдельно. Объект Stringable не становится строкой до явного преобразования.
Изменение данныхНе преобразует и не заменяет value. true/false — ответ на конкретную проверку.
Увидеть ответ проверкиСамостоятельный пример
<?php
var_dump(is_string("0"));
var_dump(is_string(""));
var_dump(is_string(0));
Ожидаемый результат
bool(true)
bool(true)
bool(false)

Тип string не обещает наличие видимого текста. Даже пустая строка остаётся строкой.

Следующая ступень использует условия и циклы. Проверяем вход до действия; исключение сообщает о нарушенном договоре, а не исправляет данные автоматически.

Применить проверку к данным приложенияСамостоятельный пример
<?php
$fields = ["sku" => " TEA-2 "];
$raw = $fields["sku"] ?? null;
if (!is_string($raw)) {
    throw new InvalidArgumentException("Артикул должен быть строкой");
}
$sku = trim($raw);
if ($sku === "" || strlen($sku) > 40) {
    throw new InvalidArgumentException("Пустой или слишком длинный артикул");
}
echo $sku;
Ожидаемый результат
TEA-2

Сначала тип, затем trim и длина. strlen здесь измеряет байты; предел условного ASCII-артикула — 40. Проверка существования артикула и разрешённых символов является отдельным правилом магазина, а не свойством is_string.

Рядом: strval, is_scalar, empty. К основам: переменная и присваивание; тип значения.

Ещё один прикладной разбор. После основных правил можно перейти к следующему примеру.

Возвращает true только для строки. Полезно до trim, регулярного выражения и проверки длины данных формы.

Защитить строковое поле от массива POSTСамостоятельный пример
<?php
declare(strict_types=1);

$input = ['email' => ['a@example.test']];
echo is_string($input['email'] ?? null) ? "Проверить адрес\n" : "Ожидается одно значение\n";
Ожидаемый результат
Ожидается одно значение

Массив нужно отклонить, а не взять случайно первый элемент. Затем проверяются длина, формат и бизнес-ограничения адреса.

Артикул должен сохранять нулиСамостоятельный пример
<?php
declare(strict_types=1);

$sku = '00127';
if (!is_string($sku)) {
    throw new UnexpectedValueException('SKU должен быть строкой');
}
echo $sku, "\n";
Ожидаемый результат
00127

Цифровой текст остается идентификатором. Приведение к int уничтожило бы нули и могло склеить разные товары.

Сравните. Для грамматики используйте preg_match после проверки типа; для вывода HTML — htmlspecialchars.

is_intПроверить фактический целочисленный тип.

PHP 4+ · standard

is_int(mixed $value): bool

Проверить фактический целочисленный тип.

Часть контрактаЧто это означает
value: mixedУже полученное значение любого PHP-типа. Чтение неизвестной переменной происходит до вызова; функция не скрывает такое предупреждение.
Возврат: boolЦелое 4 даёт true, строка "4" и float 4.0 — false. Входные поля HTTP обычно являются строками или массивами, а не int.
Изменение данныхНе преобразует и не заменяет value. true/false — ответ на конкретную проверку.
Увидеть ответ проверкиСамостоятельный пример
<?php
var_dump(is_int(4));
var_dump(is_int("4"));
var_dump(is_int(4.0));
Ожидаемый результат
bool(true)
bool(false)
bool(false)

Числовой вид строки ещё не меняет тип. Проверка ничего не присваивает обратно переменной.

Следующая ступень использует условия и циклы. Проверяем вход до действия; исключение сообщает о нарушенном договоре, а не исправляет данные автоматически.

Использовать проверенное количество в сумме позицииСамостоятельный пример
<?php
$order = ['sku' => 'TEA-01', 'quantity' => 3];
$quantity = $order['quantity'] ?? null;
if (!is_int($quantity) || $quantity < 1 || $quantity > 20) {
    throw new InvalidArgumentException('Количество должно быть целым от 1 до 20');
}
$unitPriceKopecks = 12000;
$lineTotalKopecks = $quantity * $unitPriceKopecks;
echo 'Товар: ', $order['sku'], "\n";
echo 'Количество: ', $quantity, ' уп.', "\n";
echo 'Сумма строки: ', $lineTotalKopecks, ' коп.', "\n";
Ожидаемый результат
Товар: TEA-01
Количество: 3 уп.
Сумма строки: 36000 коп.

Is_int получает 3 и возвращает true без преобразования. Затем проверяется отдельное правило диапазона. Получившееся количество участвует в произведении 3 × 12000; сумма 36000 сохраняется для следующего расчёта. Строка "3" и float 3.0 нарушили бы этот внутренний договор; для текста формы нужен предварительный этап разбора.

Рядом: intval, is_numeric, is_float. К основам: переменная и присваивание; тип значения.

Ещё один прикладной разбор. После основных правил можно перейти к следующему примеру.

Возвращает true для int. Подходит для уже декодированного JSON-контракта с целым значением или внутренних данных после нормализации.

Проверенный JSON-остатокСамостоятельный пример
<?php
declare(strict_types=1);

$data = json_decode('{"stock":0}', true, 16, JSON_THROW_ON_ERROR);
echo is_int($data['stock']) && $data['stock'] >= 0 ? "Остаток принят\n" : "Ошибка\n";
Ожидаемый результат
Остаток принят

Ноль считается корректным. Поставщик со строковыми числами требует явного адаптера, а не случайного ослабления всех проверок.

Тип результата деленияСамостоятельный пример
<?php
declare(strict_types=1);

$value = 5 / 2;
echo is_int($value) ? "int\n" : "неint\n";
Ожидаемый результат
неint

Если нужна целая часть, используйте intdiv с объявленной политикой остатка. Приведение не должно незаметно отбрасывать важную дробную часть.

Сравните. Для формы — валидатор целого с диапазоном; is_numeric шире и принимает экспоненциальную запись.

is_numericПроверить число или строку, соответствующую числовому синтаксису PHP.

PHP 4+ · standard

is_numeric(mixed $value): bool

Проверить число или строку, соответствующую числовому синтаксису PHP.

Часть контрактаЧто это означает
value: mixedУже полученное значение любого PHP-типа. Чтение неизвестной переменной происходит до вызова; функция не скрывает такое предупреждение.
Возврат: boolПринимает int, float и числовые строки, включая экспоненту. Не гарантирует целое, конечность, положительность или допустимый денежный формат. В PHP 8 конечный обычный пробел допускается.
Изменение данныхНе преобразует и не заменяет value. true/false — ответ на конкретную проверку.
Увидеть ответ проверкиСамостоятельный пример
<?php
var_dump(is_numeric("12"));
var_dump(is_numeric("1e2"));
var_dump(is_numeric("12items"));
var_dump(is_numeric("12 "));
Ожидаемый результат
bool(true)
bool(true)
bool(false)
bool(true)

1e2 — числовая запись 100. Если договор поля разрешает только десятичные цифры без пробелов, is_numeric слишком широк.

Следующая ступень использует условия и циклы. Проверяем вход до действия; исключение сообщает о нарушенном договоре, а не исправляет данные автоматически.

После разбора числа выбрать тариф по массеСамостоятельный пример
<?php
$raw = '1.25';
if (!is_string($raw) || !is_numeric($raw)) {
    throw new InvalidArgumentException('Нужна числовая строка');
}
$weightKg = (float) $raw;
if (!is_finite($weightKg) || $weightKg <= 0 || $weightKg > 50) {
    throw new InvalidArgumentException('Вес вне диапазона');
}
$deliveryRubles = $weightKg <= 2 ? 200 : 350;
echo 'Масса посылки: ', $weightKg, ' кг', "\n";
echo 'Стоимость доставки: ', $deliveryRubles, ' руб.', "\n";
Ожидаемый результат
Масса посылки: 1.25 кг
Стоимость доставки: 200 руб.

Is_numeric возвращает bool о числовой грамматике PHP; само значение raw не меняется. После true явный float-каст создаёт weightKg. Конечность и диапазон проверяются отдельно, потом 1.25 <= 2 выбирает тариф 200. Договор допускает экспоненту и пробелы, разрешённые числовой грамматикой PHP; запятая или единицы «кг» не принимаются. Для более строгой формы нужен другой разбор.

Рядом: is_int, is_float, floatval, intval. К основам: переменная и присваивание; тип значения.

Ещё один прикладной разбор. После основных правил можно перейти к следующему примеру.

Принимает int, float и числовые строки в синтаксисе PHP, включая некоторые формы, неприемлемые для номера или денежного поля.

Проверить измерение датчикаСамостоятельный пример
<?php
declare(strict_types=1);

$raw = '1.25e2';
if (!is_numeric($raw)) {
    throw new InvalidArgumentException('Не число');
}
$reading = (float) $raw;
echo $reading, "\n";
Ожидаемый результат
125

Экспоненциальная запись уместна для измерений. После преобразования добавьте is_finite и физический диапазон, чтобы не принять бесконечность или невозможное значение.

Почему недостаточно для количестваСамостоятельный пример
<?php
declare(strict_types=1);

$raw = '1e3';
echo is_numeric($raw) ? "Числовая запись\n" : "Нет\n";
echo preg_match('/\A[0-9]+\z/', $raw) === 1 ? "Целые цифры\n" : "Не формат формы\n";
Ожидаемый результат
Числовая запись
Не формат формы

Одна проверка отвечает на синтаксис PHP, другая — на конкретный контракт поля. Сначала назовите допустимый формат, потом выбирайте функцию.

Сравните. Для количества задайте грамматику целого и диапазон; для десятичных сумм — согласованный формат и точность.

intvalПолучить целое значение или разобрать строку в заданной системе счисления.

PHP 4+ · standard

intval(mixed $value, int $base = 10): int

Получить целое значение или разобрать строку в заданной системе счисления.

Часть контрактаЧто это означает
valueИсходное число/проверенная строка; не изменяется.
base = 10Основание для строки; 0 означает распознавание префикса.
Возвратint. Преобразование с потерей части входа возможно, отдельного однозначного маркера неверного текста нет.
Отбросить дробную часть и увидеть потерю текстаСамостоятельный пример
<?php
var_dump(intval(3.9));
var_dump(intval(-3.9));
var_dump(intval("12items"));
var_dump(intval("items12"));
Ожидаемый результат
int(3)
int(-3)
int(12)
int(0)

Дробная часть отсекается к нулю. Строка с допустимым началом может стать числом; ноль может быть как полезным результатом, так и следствием неподходящего входа.

Основание относится к строковому представлениюСамостоятельный пример
<?php
echo intval("101", 2), "
";
echo intval("0x20", 0), "
";
echo intval("010", 0), "
";
echo intval("010", 10), "
";
echo intval(101, 2), "
";
echo intval("1e2", 10), "
";
echo intval("1e2", 0);
Ожидаемый результат
5
32
8
10
101
100
1

base=0 включает распознавание префиксов: 0x/0X — 16, 0b/0B — 2, начальный 0 — 8, иначе 10. Для числового аргумента base не переинтерпретирует его цифры. Артикулы с ведущими нулями обычно вообще не следует превращать в числа.

Разобрать проверенную двоичную маску и использовать её флагиСамостоятельный пример
<?php
$rawBits = '101';
$allowedCharacters = strspn($rawBits, '01');
if ($rawBits === '' || strlen($rawBits) > 8 || $allowedCharacters !== strlen($rawBits)) {
    throw new InvalidArgumentException('Нужны от 1 до 8 двоичных цифр');
}
$flags = intval($rawBits, 2);
echo 'Двоичный текст: ', $rawBits, "\n";
echo 'Числовая маска: ', $flags, "\n";
echo 'Флаг 1: ', ($flags & 1) !== 0 ? 'включён' : 'выключен', "\n";
echo 'Флаг 2: ', ($flags & 2) !== 0 ? 'включён' : 'выключен', "\n";
echo 'Флаг 4: ', ($flags & 4) !== 0 ? 'включён' : 'выключен', "\n";
Ожидаемый результат
Двоичный текст: 101
Числовая маска: 5
Флаг 1: включён
Флаг 2: выключен
Флаг 4: включён

Strspn возвращает длину начального участка только из 0 и 1; равенство с байтовой strlen подтверждает весь ASCII-текст. Ограничение восьмью цифрами даёт максимум 255 на всех поддерживаемых int-платформах. Intval(rawBits, 2) получает основание 2, возвращает 5; побитовое & проверяет отдельные биты. При аргументе int основание не переинтерпретировало бы цифры, поэтому строковый договор здесь важен.

Основание задавайте как 0 или 2–36. При base=10 экспонента вроде "1e2" разбирается как число 100. Не переносите это правило на другие основания: base=0 в этом случае даёт 1, а base=16 воспринимает e как шестнадцатеричную цифру и даёт 482. Диапазон int зависит от платформы: ориентируйтесь на PHP_INT_MIN/MAX, храните большие идентификаторы строками. Массивы превращаются в 0/1, обычный объект вызывает E_WARNING; это не надёжная ошибка валидации. Не используйте int для округления денег после вычислений float.

Рядом: is_int, is_numeric, floatval. К основам: переменная и присваивание; тип значения.

Ещё один прикладной разбор. После основных правил можно перейти к следующему примеру.

Возвращает целое представление. Для строк можно указать основание системы счисления. Преобразование способно потерять часть исходного текста.

Преобразовать уже проверенное количествоСамостоятельный пример
<?php
declare(strict_types=1);

$raw = '12';
if (preg_match('/\A[1-9][0-9]{0,2}\z/', $raw) !== 1) {
    throw new InvalidArgumentException('Неверное количество');
}
echo intval($raw), "\n";
Ожидаемый результат
12

Проверка гарантирует диапазон 1..999, поэтому преобразование не скрывает посторонний хвост или переполнение.

Прочитать технический hex-кодСамостоятельный пример
<?php
declare(strict_types=1);

$hex = 'ff';
echo intval($hex, 16), "\n";
Ожидаемый результат
255

Основание 16 задается явно для известного технического формата. Внешний код сначала ограничьте допустимыми символами и длиной.

Сравните. Для проверенного значения обычно достаточно(int); FILTER_VALIDATE_INT или собственная ограниченная грамматика выполняют валидацию.

roundОкруглите показание до десятых либо расчёт до сотен, заранее определив, куда отправлять точную половину.

PHP 4+ · standard

round(int|float $num, int $precision = 0, int|RoundingMode $mode = RoundingMode::HalfAwayFromZero): float

Округлите показание до десятых либо расчёт до сотен, заранее определив, куда отправлять точную половину.

Перед первым опытом: запись round(...) вызывает функцию; в круглых скобках передаются значения через запятую, возвращённое значение можно сохранить через =. echo печатает, var_dump показывает ещё и тип, "\n" переносит строку. Параметр, вызов и возврат; Как читать числовые типы и результаты.

Вход или результатТочный договор
numЧисло до округления.
precisionПо умолчанию 0: целое; 2: сотые; −2: сотни. Это позиция разряда, а не количество значащих цифр всего числа.
modeДо PHP 8.4 — одна из четырёх PHP_ROUND_HALF_*; с 8.4 также RoundingMode с восемью вариантами. По умолчанию ближайшее, половина от нуля.
ВозвратВсегда float; функция не дописывает в тексте нули до precision.
Ошибки и границыС PHP 8.4 неизвестный int-режим даёт ValueError; раньше молча использовался HALF_UP. С PHP 8 не принимаются внутренние численно преобразуемые объекты.

Числа float хранят двоичное приближение. Входной int разрешён и для параметра float. Массив и неподходящая строка не являются числом: возможен TypeError; правила нестрогого преобразования строк и режима strict_types разобраны в главе о типах. Данные формы сначала проверяют, а не исправляют математической функцией.

В примерах printf("%.3f\n", $value) печатает число с тремя знаками после точки; это формат вывода, само значение в памяти не становится точным десятичным числом. if выбирает ветку, function объявляет повторяемое действие, return возвращает его результат: начальные объяснения. В опытах с отказом try выполняет попытку, catch ловит указанный тип ошибки; как работает обработка исключений.

Обозначения последующих шагов: foreach ($list as $value) последовательно берёт элементы списка; [a, b] создаёт список. Условия и обход списка.

Путь данных в первом примере
  1. Показание 18.26 °C
  2. round(18.26, 1): выбираем десятые
  3. Получаем 18.3; затем отдельно печатаем
Десятые и сотниСамостоятельный пример
<?php
echo round(18.26, 1), "\n";
echo round(1260, -2), "\n";
var_dump(round(7.0, 2));
Ожидаемый результат
18.3
1300
float(7)

precision = −2 выбирает ближайшую сотню. Тип float остаётся даже у числа без дробной части.

Четыре правила половины для совместимости PHP 8.2+Самостоятельный пример
<?php
foreach ([PHP_ROUND_HALF_UP, PHP_ROUND_HALF_DOWN, PHP_ROUND_HALF_EVEN, PHP_ROUND_HALF_ODD] as $mode) {
    echo round(2.5, 0, $mode), ',', round(-2.5, 0, $mode), "\n";
}
Ожидаемый результат
3,-3
2,-2
2,-2
3,-3

UP означает от нуля именно на половине; это не ceil для всех дробей. EVEN выбирает чётного соседа, ODD — нечётного.

Неизвестная настройка округления, PHP 8.4+Самостоятельный пример
<?php
try { round(2.5, 0, 99); }
catch (ValueError $error) { echo "unknown rounding mode\n"; }
echo round(2.9, 0, RoundingMode::TowardsZero), "\n";
Ожидаемый результат
unknown rounding mode
2

Последняя строка намеренно усекает все дроби к нулю. Она требует PHP 8.4; режимы enum разобраны рядом.

Когда подходит и где остановиться. Сначала задайте правило предметной области: округлять строку заказа или общий итог — разные задачи. round не делает float точным десятичным числом. number_format нужен для текста интерфейса, не для следующего вычисления. RoundingMode показывает все восемь правил.

Сравните: ceil, floor, RoundingMode. Выбор по задаче.

Продолжение: ранее разобранная практика.

Возвращает float. Precision задает десятичную позицию, mode — правило половин. Перечисление RoundingMode доступно с PHP 8.4; примеры используют совместимые константы.

Показать измерение с одной дробной цифройСамостоятельный пример
<?php
declare(strict_types=1);

$temperature = 21.26;
echo round($temperature, 1), "\n";
Ожидаемый результат
21.3

Округленный результат — представление измерения. Если исходная точность нужна для статистики, храните исходное значение отдельно.

Сравнить правила половинСамостоятельный пример
<?php
declare(strict_types=1);

echo round(2.5, 0, PHP_ROUND_HALF_UP), "\n";
echo round(2.5, 0, PHP_ROUND_HALF_EVEN), "\n";
Ожидаемый результат
3
2

Оба результата корректны для разных правил. Укажите выбранную политику в расчете, иначе два сервиса могут по-разному получить итог чека.

Сравните. floor/ceil направленно округляют; intdiv работает с целыми; BCMath нужен для согласованных десятичных вычислений.

intdivРазделите 29 задач на полные пакеты по 10 без промежуточного float: получатся два полных пакета.

PHP 7+ · standard

intdiv(int $num1, int $num2): int

Разделите 29 задач на полные пакеты по 10 без промежуточного float: получатся два полных пакета.

Перед первым опытом: запись intdiv(...) вызывает функцию; в круглых скобках передаются значения через запятую, возвращённое значение можно сохранить через =. echo печатает, var_dump показывает ещё и тип, "\n" переносит строку. Параметр, вызов и возврат; Как читать числовые типы и результаты.

Вход или результатТочный договор
num1Целое делимое.
num2Целый ненулевой делитель.
ВозвратЦелое частное с отбрасыванием дробной части в сторону нуля.
Ошибки и границыДелитель 0: DivisionByZeroError. PHP_INT_MIN / −1 не помещается в int: ArithmeticError. Дробный float не входит в контракт int.

Оператор % в первом примере возвращает остаток целочисленного деления. 29 % 10 равно 9; это не процент от числа.

В опытах с отказом try выполняет попытку, catch ловит указанный тип ошибки; как работает обработка исключений.

Путь данных в первом примере
  1. 29 задач, размер пакета 10
  2. intdiv(29, 10) убирает неполный пакет
  3. Возвращает int 2; 9 задач остаются для следующего пакета
Полные пакеты и остатокСамостоятельный пример
<?php
echo intdiv(29, 10), ' full; ', 29 % 10, " remain\n";
echo intdiv(-29, 10), "\n";
Ожидаемый результат
2 full; 9 remain
-2

% даёт целочисленный остаток. Для отрицательного делимого частное −2, тогда как floor(−2.9) даёт −3.

Две разные причины отказаСамостоятельный пример
<?php
try { intdiv(29, 0); }
catch (DivisionByZeroError $error) { echo "zero divisor\n"; }
try { intdiv(PHP_INT_MIN, -1); }
catch (ArithmeticError $error) { echo "integer overflow\n"; }
Ожидаемый результат
zero divisor
integer overflow

Ловим конкретные ошибки; не подменяем ошибку исходных данных результатом 0.

Когда подходит и где остановиться. Для числа страниц нужна ещё страница при ненулевом остатке: intdiv($count, $size) + (int) ($count % $size !== 0), при count ≥ 0 и size > 0. Не заменяйте intdiv на (int)($a/$b) для больших целых: float может потерять младшие разряды.

Сравните: fdiv, floor, ceil. Выбор по задаче.

Продолжение: ранее разобранная практика.

Возвращает целое частное, округляя к нулю. Остаток вычисляется отдельно через%. Нулевой делитель вызывает DivisionByZeroError.

Распределить копейки по трем частямСамостоятельный пример
<?php
declare(strict_types=1);

$total = 100;
$parts = 3;
echo intdiv($total, $parts), ' + остаток ', $total % $parts, "\n";
Ожидаемый результат
33 + остаток 1

Три части по 33 оставляют 1 копейку. Политика распределения остатка должна быть явной, иначе сумма частей не совпадет с итогом.

Номер полной партии импортаСамостоятельный пример
<?php
declare(strict_types=1);

$processed = 250;
$batchSize = 100;
echo intdiv($processed, $batchSize), "\n";
Ожидаемый результат
2

Завершены две полные партии, но остается 50 обработанных элементов. Для прогресса храните также остаток или точное число, а не только номер партии.

Сравните. Для дробного результата нужен/; для страниц с округлением вверх учтите остаток, а не отбрасывайте его.

Источники и обсуждения

Код и руководство с собой

Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.

Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.

Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.