Условия в PHP: if, else, elseif, тернарный оператор, switch и match

Условия в PHP: if, else, elseif, тернарный оператор, switch и match Полезное

Условный оператор if в PHP — это конструкция, которая выполняет блок кода только тогда, когда выражение в скобках после приведения к типу bool дает true. Для второй ветки служит else, для цепочки проверок — elseif. Кроме if, в PHP есть еще четыре способа ветвления: тернарный оператор ? : (и его короткая форма ?:), оператор объединения с null ??, switch и выражение match (с PHP 8.0).

Важно различать две вещи, которые часто смешивают: конструкции ветвления (if, switch, match) выбирают, какой код выполнить, а операторы сравнения (==, ===, < и другие) вычисляют само условие. Ошибки в условиях PHP чаще всего живут именно во втором: в нестрогом сравнении и приведении к bool. Все примеры ниже запускались на PHP 8.4.25 и 8.5.10 (CLI) 24.09.2026, вывод одинаковый.

if, elseif, else: минимальный рабочий пример

Функция переводит балл экзамена в оценку. Скопируйте код в файл grade.php и запустите php grade.php. Во всех примерах ниже путь в сообщениях об ошибках (/app/...) будет вашим путем к файлу.

<?php
function grade(int $score): string
{
    if ($score < 0 || $score > 100) {
        return 'ошибка: балл вне диапазона 0-100';
    } elseif ($score >= 85) {
        return 'отлично';
    } elseif ($score >= 70) {
        return 'хорошо';
    } elseif ($score >= 50) {
        return 'зачет';
    } else {
        return 'незачет';
    }
}

foreach ([92, 70, 51, 12, 140] as $s) {
    echo $s, ' -> ', grade($s), PHP_EOL;
}

В консоли появится:

92 -> отлично
70 -> хорошо
51 -> зачет
12 -> незачет
140 -> ошибка: балл вне диапазона 0-100

Как это читается:

  • if (условие) { ... } — PHP вычисляет выражение в круглых скобках и приводит результат к bool. Если true, выполняется блок в фигурных скобках.
  • elseif проверяется только тогда, когда все условия выше дали false. Проверка идет сверху вниз и останавливается на первом истинном условии, поэтому порядок важен: для балла 92 истинны и >= 85, и >= 70, но выполнится только первая ветка.
  • else не имеет условия и срабатывает, если не сработало ничего выше. Он необязателен и не может стоять без if.
  • Проверку некорректного ввода удобно ставить первой: так остальные ветки работают уже с допустимыми данными.

Фигурные скобки при одной инструкции формально можно опустить, но это источник ошибок: else всегда привязывается к ближайшему if без пары, а не к тому, под которым он выровнен отступом.

<?php
$a = true; $b = false;
if ($a)
    if ($b)
        echo 'a и b', PHP_EOL;
else
    echo 'этот else относится к if ($b)', PHP_EOL;

Результат — этот else относится к if ($b): отступ ничего не значит для интерпретатора. Исправление — всегда ставить фигурные скобки, тогда принадлежность else видна однозначно.

elseif и else if: есть ли разница

В обычном синтаксисе со скобками elseif и else if работают одинаково: второй вариант — это else, внутри которого вложен новый if. Разница появляется в альтернативном синтаксисе с двоеточием, который используют в HTML-шаблонах. Там допустим только elseif:

<?php $n = 2; ?>
<?php if ($n === 1): ?>
один
<?php else if ($n === 2): ?>
два
<?php endif; ?>
Parse error: syntax error, unexpected token "if", expecting ":" in /app/template.php on line 4

Рабочий шаблон с тем же синтаксисом выглядит так:

<?php $items = ['книга', 'ручка']; ?>
<?php if (count($items) === 0): ?>
<p>Корзина пуста</p>
<?php elseif (count($items) === 1): ?>
<p>В корзине 1 товар</p>
<?php else: ?>
<p>Товаров в корзине: <?= count($items) ?></p>
<?php endif; ?>

Вывод: <p>Товаров в корзине: 2</p>. Формулировка «Товаров: N» выбрана намеренно: вариант «В корзине N товара» дал бы «5 товара», а склонение по числу — отдельная задача с тем же if/elseif. Блоки закрываются словом endif;, а <?= ... ?> — короткая запись для echo.

Что PHP считает истиной в условии

Выражение в if не обязано быть сравнением: туда можно передать переменную любого типа, и PHP приведет ее к bool. Для скаляров, массивов и null ложными считаются только значения с результатом false в таблице ниже, все остальное — истина.

Значение В if Комментарий
false, null false
0, 0.0 false ноль любого числового типа
'', '0' false пустая строка и строка «0»
[] false пустой массив
'0.0', ' ', 'false' true непустые строки, кроме '0'
[0] true непустой массив, даже если внутри ноль
-1 true любое ненулевое число

Все строки таблицы получены прогоном var_export($v) и $v ? 'true' : 'false'. Граница: у объектов свои правила (например, пустой объект SimpleXMLElement считается ложным), поэтому для объектов лучше проверять явно: $obj !== null, $obj instanceof Foo.

Из таблицы следует практическое правило: if ($x) подходит, когда нужно «что-нибудь непустое». Если 0 или '0' — допустимое значение (количество товаров, код ответа), пишите явную проверку: if ($count !== null) или if ($input !== '').

== и ===: как сравнивать в условиях

== (нестрогое равенство) перед сравнением приводит операнды к общему типу. === (строгое равенство) дает true, только если совпадают и тип, и значение. Результаты прогона на PHP 8.4/8.5:

Выражение == ===
0 и 'abc' false false
'1' и '01' true false
'10' и '1e1' true false
100 и '1e2' true false
null и false true false
[] и false true false
1.0 и 1 true false

Две строки, похожие на числа, при == сравниваются как числа, поэтому '10' == '1e1'. Это опасно при проверке токенов, хешей и кодов подтверждения: для них используйте ===, а для секретов — hash_equals(), которая к тому же сравнивает за время, не зависящее от места первого различия.

Версионная оговорка: строка 0 == 'abc' дает false начиная с PHP 8.0. В PHP 7 сравнение числа с нечисловой строкой приводило строку к числу, и результат был true. Если поддерживаете старый код, это место стоит проверить отдельно.

Правило по умолчанию: в условиях пишите === и !==, а == оставляйте для случаев, где приведение типов нужно осознанно.

Три частые ошибки в if

Первая — присваивание вместо сравнения. Один знак = присваивает значение, и условие проверяет уже его:

<?php
$role = 'guest';
if ($role = 'admin') {
    echo "доступ открыт, role = $role", PHP_EOL;
}

Результат: доступ открыт, role = admin — условие всегда истинно, а переменная испорчена. Исправление: if ($role === 'admin').

Вторая — проверка результата функции, которая может вернуть 0 как успех. strpos() возвращает позицию подстроки или false, а позиция в начале строки равна 0:

<?php
$pos = strpos('php', 'p');
if ($pos) {
    echo 'найдено', PHP_EOL;
} else {
    echo 'не найдено (а на самом деле pos = ', var_export($pos, true), ')', PHP_EOL;
}
if (strpos('php', 'p') !== false) {
    echo 'strpos !== false: найдено', PHP_EOL;
}
if (str_contains('php', 'p')) {
    echo 'str_contains: найдено', PHP_EOL;
}
не найдено (а на самом деле pos = 0)
strpos !== false: найдено
str_contains: найдено

Исправление — сравнивать строго с false или, если позиция не нужна, взять str_contains() (есть с PHP 8.0).

Третья — and/or вместо &&/||. У слов приоритет ниже, чем у присваивания: $r = true and false; сначала записывает true в $r, и var_dump($r) показывает bool(true). С && получится ожидаемый bool(false). В условиях используйте &&, || и !.

Тернарный оператор, ?: и ??

Тернарный оператор условие ? значение1 : значение2 — это выражение, а не инструкция: он возвращает значение, которое можно присвоить или вывести. Подходит, когда обе ветки короткие и нужно выбрать одно из двух значений.

<?php
$config = ['timeout' => 0, 'name' => ''];

$t1 = $config['timeout'] ?: 30;
$t2 = $config['timeout'] ?? 30;
$retries = $config['retries'] ?? 3;
echo "?: -> $t1, ?? -> $t2, retries -> $retries", PHP_EOL;

$age = 17;
$status = $age >= 18 ? 'взрослый' : 'несовершеннолетний';
echo $status, PHP_EOL;

$config['name'] ??= 'default';
$config['lang'] ??= 'ru';
echo var_export($config['name'], true), ' ', $config['lang'], PHP_EOL;
?: -> 30, ?? -> 0, retries -> 3
несовершеннолетний
'' ru

Здесь три разных оператора, и путать их нельзя:

Запись Когда берется значение справа Предупреждение при отсутствии ключа
$a ?: $b $a ложно (0, », null, [] …) да, если ключа нет
$a ?? $b $a не существует или равно null нет
$a ??= $b то же, что ??, и результат записывается в $a нет

В примере таймаут 0 — осознанная настройка «без ограничения», и ?: ее молча заменил на 30, а ?? сохранил. Для значений по умолчанию из массивов, $_GET и конфигов берите ??.

Вложенный тернарный оператор без скобок с PHP 8.0 не компилируется:

<?php
$x = 5;
echo $x > 0 ? 'плюс' : $x < 0 ? 'минус' : 'ноль';
Fatal error: Unparenthesized `a ? b : c ? d : e` is not supported. Use either `(a ? b : c) ? d : e` or `a ? b : (c ? d : e)` in /app/ternary.php on line 3

Исправление — скобки: $x > 0 ? 'плюс' : ($x < 0 ? 'минус' : 'ноль') выводит плюс. Если веток больше двух, читаемее if/elseif или match.

switch и match: выбор по значению

switch сравнивает одно выражение с набором case через нестрогое == и выполняет код, начиная с совпавшей метки, до ближайшего break. match (PHP 8.0+) — выражение: сравнивает строго через ===, возвращает значение и не «проваливается» в соседние ветки.

<?php
$code = '1';

switch ($code) {
    case 1:
        echo 'switch: создан', PHP_EOL;
    case 2:
        echo 'switch: обновлен', PHP_EOL;
        break;
    default:
        echo 'switch: неизвестно', PHP_EOL;
}

echo match ((int) $code) {
    1 => 'match: создан',
    2 => 'match: обновлен',
    default => 'match: неизвестно',
}, PHP_EOL;

echo match ($code) {
    1 => 'match: создан',
    2 => 'match: обновлен',
}, PHP_EOL;
switch: создан
switch: обновлен
match: создан

Fatal error: Uncaught UnhandledMatchError: Unhandled match case '1' in /app/switch_match.php:20
Stack trace:
#0 {main}
  thrown in /app/switch_match.php on line 20

Разбор по строкам вывода. Строка '1' совпала с case 1 из-за нестрогого сравнения, а забытый break выполнил и ветку case 2. match с явным приведением (int) сработал правильно. Последний match получил строку '1', ни одна ветка строго не совпала, default нет — и PHP выбросил UnhandledMatchError. Это полезное поведение: непредусмотренное значение не проходит молча.

match также умеет объединять значения в одной ветке и проверять диапазоны через match (true):

<?php
$age = 25;
$result = match (true) {
    $age < 14 => 'ребенок',
    $age < 18 => 'подросток',
    $age < 65 => 'взрослый',
    default   => 'пенсионер',
};
echo $result, PHP_EOL;

$method = 'PATCH';
echo match ($method) {
    'GET', 'HEAD' => 'чтение',
    'POST', 'PUT', 'PATCH' => 'запись',
    'DELETE' => 'удаление',
    default => 'метод не поддерживается',
}, PHP_EOL;

Вывод: взрослый и запись. В ветке match может стоять только одно выражение; если нужно несколько инструкций, вынесите их в функцию или используйте if/switch.

Условие на пользовательском вводе

Условия чаще всего проверяют данные из запроса, а там может прийти что угодно, включая массив вместо строки (?page[]=x). Надежная схема: значение по умолчанию через ??, проверка типа, затем выбор из белого списка через match:

<?php
function pageTitle(mixed $page): string
{
    if (!is_string($page)) {
        return '400 Bad Request';
    }
    return match ($page) {
        'home'     => 'Главная',
        'courses'  => 'Курсы',
        'contacts' => 'Контакты',
        default    => '404 Not Found',
    };
}

$page = $_GET['page'] ?? 'home';
echo pageTitle($page), PHP_EOL;
echo pageTitle('courses'), PHP_EOL;
echo pageTitle('../../etc/passwd'), PHP_EOL;
echo pageTitle(['courses']), PHP_EOL;

В CLI массив $_GET пуст, поэтому вывод: Главная, Курсы, 404 Not Found, 400 Bad Request. Строка с ../ не пройдет, потому что допустимы только перечисленные значения. Граница примера: это выбор по белому списку, а не полная защита страницы. Вывод данных в HTML все равно нужно экранировать (htmlspecialchars), а в SQL — передавать через параметры запроса.

Что выбрать: сводная таблица

Задача Конструкция Почему
Выполнить или пропустить блок кода if условие любого вида
Несколько условий-диапазонов по порядку if/elseif/else проверка сверху вниз до первого совпадения
Выбрать одно из двух коротких значений ? : выражение, можно присвоить
Значение по умолчанию для null или отсутствующего ключа ??, ??= без предупреждений, не трогает 0 и »
Выбор значения по точному совпадению match строгое ===, ошибка на неучтенный вариант
Несколько инструкций на каждый вариант, legacy-код switch помнить про == и break
Условие в HTML-шаблоне if: ... elseif: ... endif; читаемо среди разметки

Выводы

  • if/elseif/else проверяет условия сверху вниз и выполняет только первую истинную ветку, поэтому порядок условий определяет результат.
  • В условии значение приводится к bool: 0, '0', '', [], null ложны, а '0.0' и ' ' — истинны; где 0 допустим, нужна явная проверка.
  • В условиях по умолчанию используйте ===: == приводит типы, и '10' == '1e1' дает true.
  • ?? подставляет значение по умолчанию только для null и отсутствующего ключа, ?: — для любого ложного значения; вложенный тернарный оператор без скобок с PHP 8.0 — ошибка.
  • match сравнивает строго, возвращает значение и падает на неучтенном варианте; switch сравнивает через == и требует break.

Где применяется / связь с практикой

Освойте тему на практике

Условия — часть почти каждого обработчика на PHP: маршрутизация запросов, проверка прав доступа, валидация форм, выбор статуса ответа API. Ошибки из этой статьи (нестрогое сравнение токена, strpos без !== false, switch без break) регулярно встречаются на код-ревью и в уязвимостях реальных проектов. Системно выстроить работу с PHP от синтаксиса до фреймворков, тестов и архитектуры можно на курсе «PHP-разработчик. Продвинутый уровень». Попробовать формат обучения бесплатно можно на открытых уроках Otus.

FAQ

Можно ли написать if в одну строку?
Да: if ($x > 0) echo 'плюс'; работает, но при добавлении второй инструкции без скобок она выполнится всегда. Для присваивания одного из двух значений в одну строку удобнее тернарный оператор.

Чем isset($a) отличается от $a ?? null?
isset() возвращает bool (существует и не null), а ?? сразу возвращает само значение или запасное. Оба не выдают предупреждение о неопределенной переменной или ключе.

Есть ли в PHP оператор «космический корабль» и зачем он в условиях?
Оператор <=> возвращает -1, 0 или 1 в зависимости от того, меньше, равно или больше левое значение. В условиях он встречается редко, основное применение — функции сравнения для usort().

OTUS Журнал
Скидка 5% 12-19 октября на курсы (popup)
Бесплатные открытые уроки (поп-ап)