Выражения: почти всё имеет значение
В PHP выражение — это фрагмент кода, у которого есть значение. Литерал 10, переменная $price, вызов trim($name), присваивание $x = 5, сравнение $age >= 18 — всё это выражения. Управляющие конструкции используют выражения, чтобы решить, какую ветку выполнить, когда остановить цикл или какое значение вернуть.
<?php
$subtotal = 900;
$delivery = $subtotal >= 1000 ? 0 : 250;
$total = $subtotal + $delivery;
echo $total; // 1150Здесь $subtotal >= 1000 даёт false, тернарный оператор выбирает 250, а затем арифметическое выражение считает итог. Это пересекается с \Типы и strict_types\: типы помогают описать допустимые значения, но сами условия всё равно строятся из выражений.
flowchart TD
A["Выражение"] --> B{"Нужно принять булево решение?"}
B -->|Да: if, while, тернарный оператор, логическое ИЛИ| C["Значение приводится к bool"]
C --> D["Ветка выполняется или пропускается"]
B -->|Нет: присваивание, return, ветка match| E["Исходное значение участвует дальше"]
E --> F["Его можно сохранить, вернуть или сравнить"]Приоритет операторов и скобки
Приоритет отвечает за то, как PHP группирует выражение. * связывается сильнее, чем +, поэтому 1 + 5 * 3 равно 16, а не 18. Скобки меняют группировку:
<?php
echo 1 + 5 * 3; // 16
echo (1 + 5) * 3; // 18В реальном коде скобки нужны не только машине, но и человеку. Особенно там, где смешиваются конкатенация, арифметика, ??, тернарный оператор и логические операторы.
<?php
$x = 4;
echo 'x minus one equals ' . ($x - 1); // x minus one equals 3С PHP 8.0 у конкатенации . приоритет ниже, чем у + и -, но привычка ставить скобки вокруг вычисляемой части всё равно остаётся хорошей: она делает намерение явным.
Отдельная ловушка — and / or против && / ||. Они похожи по смыслу, но имеют другой приоритет относительно присваивания.
<?php
$a = true && false;
$b = true and false;
var_dump($a); // bool(false)
var_dump($b); // bool(true), потому что читается как ($b = true) and falseВ прикладном коде обычно выбирают && и ||, а and / or оставляют для редких случаев, где низкий приоритет действительно читается лучше.
Сравнение: == не то же самое, что ===
PHP различает нестрогое и строгое сравнение. == сравнивает значения с приведением типов, === требует совпадения и значения, и типа.
<?php
var_dump('10' == 10); // bool(true)
var_dump('10' === 10); // bool(false)Для бизнес-условий, идентификаторов, статусов и enum-подобных строк чаще выбирают ===. Нестрогое сравнение полезно реже: оно может быть удобным на грязной границе ввода, но эту границу лучше нормализовать явно. Для данных из формы см. \GET, POST и фильтрация ввода\.
Оператор <=>, «spaceship», сравнивает два значения и возвращает -1, 0 или 1: левое меньше, равно или больше правого. Он особенно удобен в сортировках.
<?php
$users = [
['name' => 'Ada', 'age' => 37],
['name' => 'Grace', 'age' => 85],
['name' => 'Rasmus', 'age' => 56],
];
usort($users, fn (array $a, array $b): int => $a['age'] <=> $b['age']);
print_r(array_column($users, 'name'));
// Array
// (
// [0] => Ada
// [1] => Rasmus
// [2] => Grace
// )??, ?: и выбор значения
Null coalescing operator ?? возвращает левую часть, если она существует и не равна null; иначе возвращает правую. Он часто встречается при чтении массивов, query params и конфигов.
<?php
$page = $_GET['page'] ?? '1';Это не то же самое, что ?:. Сокращённый тернарный оператор проверяет truthy/falsy-значение, поэтому пустая строка, 0, '0', пустой массив и false поведут себя как «нет значения».
<?php
$limit = $_GET['limit'] ?? 20; // отсутствует или null -> 20
$label = $input ?: 'Без названия'; // любое falsy -> строка по умолчаниюЕсли важно отличить «ключа нет» от «значение есть, но оно пустое», используйте array_key_exists() или явную проверку. Это особенно важно рядом с \Массивы как ordered map\, потому что ключи и значения в PHP-массивах имеют собственные правила приведения.
if, switch и match
if — базовая конструкция для ветвления. Выражение внутри скобок приводится к bool.
<?php
if ($userId === null) {
throw new InvalidArgumentException('User id is required');
}
if ($role === 'admin') {
$canEdit = true;
} elseif ($role === 'viewer') {
$canEdit = false;
} else {
$canEdit = false;
}Исключение в guard-условии связано с \Ошибки, исключения и Throwable\: ветка не «возвращает false», а останавливает некорректный сценарий.
switch удобен для нескольких вариантов, но сравнение в case исторически нестрогое, и без break выполнение проваливается в следующий case.
<?php
switch ($status) {
case 'draft':
$label = 'Черновик';
break;
case 'published':
$label = 'Опубликовано';
break;
default:
$label = 'Неизвестно';
}match появился как более строгая альтернатива для выбора значения. Он использует строгое сравнение, не проваливается между ветками и сам является выражением.
<?php
$label = match ($status) {
'draft' => 'Черновик',
'published' => 'Опубликовано',
'archived' => 'Архив',
default => 'Неизвестно',
};Если у match нет default и ни одна ветка не подошла, PHP бросит UnhandledMatchError. Поэтому match хорошо подходит для закрытых наборов статусов, особенно когда позже появятся \Enum в PHP\.
Циклы и foreach
PHP поддерживает while, do while, for и foreach. while проверяет условие до каждой итерации, do while — после, поэтому тело выполнится хотя бы один раз.
<?php
$i = 0;
while ($i < 3) {
echo $i . PHP_EOL;
$i++;
}for компактно собирает инициализацию, условие и шаг:
<?php
for ($i = 0; $i < 3; $i++) {
echo $i . PHP_EOL;
}foreach — основной способ обходить массивы и объекты, которые можно итерировать. Для массивов он хорошо сочетается с моделью «ordered map»: порядок обхода соответствует порядку элементов в массиве.
<?php
$prices = [
'book' => 1200,
'delivery' => 250,
];
foreach ($prices as $code => $amount) {
echo $code . ': ' . $amount . PHP_EOL;
}Можно обходить по ссылке, но это место требует дисциплины: после foreach (&$value) переменная остаётся ссылкой на последний элемент. Обычно её сразу сбрасывают через unset($value).
<?php
$items = [1, 2, 3];
foreach ($items as &$item) {
$item *= 10;
}
unset($item);
print_r($items); // [10, 20, 30]Для ленивого обхода больших последовательностей лучше смотреть в сторону \Генераторы и Fiber\, а для объектных итераторов — \SPL, итераторы и коллекции\.
Типичные ловушки
Первая ловушка — полагаться на нестрогое сравнение там, где нужен точный контракт. Вторая — писать слишком плотные выражения без скобок: PHP их поймёт, но следующий разработчик может прочитать иначе. Третья — забывать break в switch, если fall-through не был осознанным приёмом.
Четвёртая — использовать foreach по ссылке и потом случайно переиспользовать ту же переменную. Это редкий баг, но неприятный: код выглядит нормально, а последний элемент массива меняется не там, где ожидается.
См. также
- \Типы и strict_types\ — как объявления типов и
TypeErrorсвязаны с выражениями и сравнениями. - \Массивы как ordered map\ — ключи, порядок и поведение массивов, которые чаще всего обходят через
foreach. - \Функции, замыкания и callable\ — callback-функции для сортировок, фильтрации и преобразования данных.
- \Ошибки, исключения и Throwable\ — что происходит, когда условие приводит к
throw,TypeErrorилиUnhandledMatchError. - \Enum в PHP\ — удобная пара для
matchпри закрытых наборах значений. - \SPL, итераторы и коллекции\ — объектная итерация за пределами обычных массивов.