Функция как контракт
Функция в PHP — именованный или безымянный блок кода, который можно вызвать с аргументами и получить результат. В простом виде она выглядит как «входные значения → вычисление → возвращаемое значение», но в реальном проекте функция ещё задаёт контракт: какие типы принимает, какие ошибки может бросить, меняет ли внешнее состояние и можно ли передать её как callback.
<?php
declare(strict_types=1);
function formatPrice(int $amount, string $currency = 'KZT'): string
{
return number_format($amount, 0, '.', ' ') . ' ' . $currency;
}
echo formatPrice(12500); // 12 500 KZTСигнатура функции здесь сообщает больше, чем тело: нужен int, есть значение по умолчанию, результат всегда string. Это продолжает тему Типы и strict_types: типы не делают код «магически правильным», но фиксируют границы, на которых PHP может бросить TypeError.
flowchart TD
A[Вызов функции] --> B[Вычислить аргументы слева направо]
B --> C[Сопоставить с параметрами]
C --> D{Есть типы параметров?}
D -->|да| E[Проверить типы]
D -->|нет| F[Выполнить тело]
E -->|ошибка| G[TypeError]
E -->|ок| F
F --> H{Есть return type?}
H -->|да| I[Проверить возвращаемое значение]
H -->|нет| J[Вернуть результат]
I -->|ошибка| G
I -->|ок| JUser-defined и built-in функции
User-defined functions — функции, которые объявляет сам разработчик через function. Built-in functions — встроенные функции PHP и расширений: strlen(), array_map(), json_encode(), password_hash(), mb_strlen() и тысячи других. Они вызываются одинаково, но живут в разных местах: ваши функции — в коде проекта, встроенные — в ядре PHP или подключённых extension.
<?php
function normalizeEmail(string $email): string
{
return mb_strtolower(trim($email));
}
$email = normalizeEmail(' USER@Example.COM ');
var_dump($email); // string(16) "user@example.com"Функции часто становятся маленькими «именованными решениями». Если выражение из Операторы и управляющие конструкции начинает повторяться в нескольких местах, его стоит вынести в функцию с понятным именем.
Scope: что видно внутри функции
У функции своя локальная область видимости. Переменная снаружи не становится автоматически доступной внутри.
<?php
$taxRate = 0.12;
function addTax(int $amount): int
{
// $taxRate здесь не видна
return (int) round($amount * 1.12);
}Можно использовать global или $GLOBALS, но в прикладном коде это почти всегда хуже явного параметра. Функция, которая получает всё нужное через аргументы, легче читается, тестируется и анализируется.
<?php
function addTax(int $amount, float $rate): int
{
return (int) round($amount * (1 + $rate));
}Есть ещё static-переменные внутри функции: они сохраняют значение между вызовами, но видны только этой функции. Это полезно редко — например, для простого memoization, но может спрятать состояние там, где читатель его не ждёт.
Параметры, defaults, named arguments
Аргументы по умолчанию подставляются только когда аргумент не передали. Если передать null, это уже явное значение, а не «используй default».
<?php
function label(string $text = 'Без названия'): string
{
return $text;
}
echo label(); // Без названия
echo label('Отчёт'); // Отчёт
// label(null); // TypeError, потому что параметр stringС PHP 8.0 можно вызывать функции с named arguments. Это удобно у функций с несколькими опциями, где позиционный вызов плохо читается.
<?php
$html = htmlspecialchars(
string: $title,
flags: ENT_QUOTES | ENT_SUBSTITUTE,
encoding: 'UTF-8',
double_encode: false,
);Важный практический эффект: имена параметров становятся частью публичного API. Если библиотека выпустила функцию send(string $to, string $subject), а пользователи вызывают send(to: ..., subject: ...), переименование $subject в $title может сломать чужой код. Это уже не только стиль, а совместимость.
Variadic: переменное число аргументов
Оператор ... в параметрах собирает оставшиеся аргументы в массив. Такой параметр называют variadic.
<?php
function sum(int ...$numbers): int
{
return array_sum($numbers);
}
echo sum(10, 20, 30); // 60Тот же оператор используется при распаковке массива в аргументы:
<?php
$parts = [10, 20, 30];
echo sum(...$parts); // 60Variadic хорошо подходит для функций вроде sum(), builders, логирования или API, где количество однотипных значений заранее неизвестно. Если аргументы разнотипные и имеют смысловые имена, named arguments обычно читаются лучше.
Возврат значений и ссылки
return завершает функцию и возвращает значение. Если функция объявлена с return type, PHP проверяет результат на выходе.
<?php
function findUserName(array $users, int $id): ?string
{
return $users[$id]['name'] ?? null;
}void означает, что функция ничего не возвращает как значение. never — что функция вообще не возвращает управление обычным способом: например, всегда бросает исключение или завершает процесс.
Передача по ссылке через & позволяет функции менять переменную вызывающего кода. Это мощный, но шумный инструмент: по месту вызова не всегда видно, что значение изменится.
<?php
function appendSuffix(string &$value): void
{
$value .= '-archived';
}
$status = 'invoice';
appendSuffix($status);
echo $status; // invoice-archivedОбычно лучше вернуть новое значение. Ссылки уместны в низкоуровневом коде, совместимости со старым API или там, где это явно ожидаемая модель, как у некоторых built-in функций.
Анонимные функции и замыкания
Анонимная функция не имеет имени и обычно передаётся как значение. В PHP такие функции представлены объектом Closure.
<?php
$prices = [1200, 250, 80];
$withTax = array_map(
function (int $amount): int {
return (int) round($amount * 1.12);
},
$prices,
);
print_r($withTax); // [1344, 280, 90]Если анонимной функции нужна переменная из внешней области видимости, используется use. По умолчанию значение захватывается на момент создания closure.
<?php
$rate = 0.12;
$addTax = function (int $amount) use ($rate): int {
return (int) round($amount * (1 + $rate));
};
echo $addTax(1000); // 1120Чтобы менять внешнюю переменную, можно захватить её по ссылке: use (&$counter). Но это такой же риск скрытого состояния, как global: иногда нужно, но лучше не делать это привычкой.
Arrow functions
Arrow functions появились как короткая форма для одновыраженных callbacks. Они записываются через fn (...) => expression и автоматически захватывают внешние переменные по значению.
<?php
$rate = 0.12;
$prices = [1200, 250, 80];
$withTax = array_map(
fn (int $amount): int => (int) round($amount * (1 + $rate)),
$prices,
);Arrow function хороша, когда тело действительно помещается в одно выражение. Если внутри появляются guard-условия, несколько шагов, try/catch или побочные эффекты, обычная анонимная функция честнее: она не притворяется короткой.
callable и first-class callable syntax
callable — тип для значения, которое можно вызвать как функцию. Callback может быть closure, строкой с именем функции, массивом вида [$object, 'method'], массивом вида [ClassName::class, 'staticMethod'] или объектом с методом __invoke().
<?php
function applyDiscount(array $amounts, callable $discount): array
{
return array_map($discount, $amounts);
}
$result = applyDiscount(
[1000, 2000],
fn (int $amount): int => (int) round($amount * 0.9),
);С PHP 8.1 есть first-class callable syntax: strlen(...), $object->method(...), SomeClass::method(...). Он создаёт Closure из вызываемого выражения и обычно лучше старого строкового синтаксиса: IDE и статические анализаторы понимают его точнее.
<?php
$length = strlen(...);
echo $length('PHP'); // 3
$names = ['Ada', 'Rasmus', 'Grace'];
usort($names, strcmp(...));В проектах с PHPDoc, generics и статический анализ это особенно заметно: Closure(int): string или callable(User): bool в PHPDoc сообщает анализатору больше, чем просто callable в сигнатуре.
Типичные ловушки
Первая ловушка — слишком широкие функции: много параметров, много веток, неясный результат. Часто это признак, что функция смешала несколько задач.
Вторая — named arguments в публичном API. Они улучшают читаемость вызова, но закрепляют имена параметров как совместимый контракт.
Третья — захват переменных в closure. use ($x) берёт значение на момент создания, а не на момент вызова. Arrow functions делают это автоматически, поэтому иногда ошибка выглядит «слишком аккуратно».
Четвёртая — callable без уточнения формы. Для PHP достаточно, что значение вызывается. Для человека и статического анализатора полезнее знать: это Closure, метод объекта, invokable-класс или callback с конкретной сигнатурой.
См. также
- Типы и strict_types — scalar declarations, return types,
TypeErrorи границы строгого режима. - Операторы и управляющие конструкции — выражения,
match, циклы и условия, которые часто выносят в функции. - Ошибки, исключения и Throwable — что происходит, когда функция бросает исключение или нарушает типовой контракт.
- Массивы как ordered map — callbacks в
array_map,array_filter,usortи обходе данных. - SPL, итераторы и коллекции — функции и callbacks вокруг iterable-объектов.
- Классы, объекты и видимость — методы,
__invoke()и объектный контекст$this. - Неймспейсы и use — как имена функций и классов разрешаются в namespaced-коде.
- PHPDoc, generics и статический анализ — как уточнять callable-сигнатуры там, где нативного типа недостаточно.