Перейти к основному содержимому

Функции и замыкания в PHP

Разработчику Архитектору

Материал лучше читается в связке с управляющими конструкциями и циклами и ООП в PHP: функции дают базовую декомпозицию, а классы и методы масштабируют её на большие проекты.


Функции

Функция в PHP — это именованный блок программного кода, предназначенный для выполнения конкретной задачи, который можно вызывать многократно в разных контекстах без дублирования реализации. Функции являются базовым строительным элементом процедурного и функционального стиля программирования в PHP и активно используются также в объектно-ориентированном коде — как методы классов и как статические утилиты.

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


Интерактивное демо — вызов функции и стек на примере JavaScript. В PHP объявление другое, но вызов, локальные переменные и возврат устроены так же. Обобщённо: функции в коде.

Play ITЗагрузка интерактивного демо…


Объявление именованной функции

Синтаксис объявления именованной функции в PHP начинается с ключевого слова function, за которым следует уникальное в пределах области видимости имя функции, список формальных параметров в круглых скобках, и тело функции в фигурных скобках:

function имя_функции(список_параметров): тип_возврата {
// тело функции
return значение;
}

Разбор:

  • function объявляет новую функцию в текущем пространстве имен.
  • имя_функции — идентификатор, по которому функция вызывается из другого кода.
  • Параметры в скобках формируют входной контракт функции.
  • : тип_возврата фиксирует ожидаемый тип результата.
  • return завершает выполнение и отдает значение вызывающему коду.

Объявление именованной функции:

function имя_функции(тип_параметра $параметр): тип_возврата {
// тело функции
return значение;
}

Разбор:

  • Тип перед $параметр задает требования к входным данным на уровне сигнатуры.
  • Переменная с $ хранит фактический аргумент, переданный при вызове.
  • Такая форма облегчает автодополнение IDE и статический анализ.
  • При несовместимом типе PHP выбрасывает TypeError в строгом режиме.
  • Явный тип возврата поддерживает стабильный контракт функции.

Объявление функции с параметрами по умолчанию:

function имя_функции(тип $параметр = значение_по_умолчанию): тип_возврата {
return результат;
}

Разбор:

  • = значение_по_умолчанию делает параметр опциональным для вызывающего кода.
  • Если аргумент не передан, функция использует значение из сигнатуры.
  • Такой подход уменьшает дублирование в месте вызова при типовых сценариях.
  • Опциональные параметры обычно размещают после обязательных.
  • Контракт возврата остается строгим и независимым от значения по умолчанию.

Вызов функции с именованными аргументами (PHP 8.0+):

имя_функции(параметр: значение);

Разбор:

  • Это именованный аргумент из PHP 8+, где указывается имя параметра.
  • Вызов становится самодокументируемым и легче читается в длинных сигнатурах.
  • Именованный стиль снижает риск перепутать порядок однотипных аргументов.
  • При переименовании параметров важно учитывать обратную совместимость API.

Объявление анонимной функции (замыкания):

$переменная = function(список_параметров) use ($внешняя_переменная): тип_возврата {
return выражение;
};

Разбор:

  • Анонимная функция объявляется без имени и присваивается переменной.
  • use (...) явно захватывает значения из внешней лексической области.
  • Тип возврата работает так же, как в обычной именованной функции.
  • Точка с запятой после } обязательна, потому что это выражение присваивания.
  • Такой шаблон применяют для callback-логики и фабрик функций.

Имя функции в PHP должно соответствовать правилам идентификаторов — начинаться с буквы или символа подчёркивания, далее — любая комбинация букв, цифр и подчёркиваний. Регистр символов в имени функции не учитываетсяfoo(), FOO(), Foo() — одно и то же объявление с точки зрения движка. Однако с точки зрения стиля кода и читаемости принято придерживаться camelCase или snake_case (в экосистеме PHP исторически чаще встречается snake_case, особенно в built-in функциях).

Формальные параметры — это переменные, объявленные в сигнатуре функции. При вызове функции им присваиваются фактические аргументы. PHP поддерживает:

  • Позиционные параметры — передаются строго по порядку;
  • Именованные аргументы — доступны начиная с PHP 8.0, позволяют указывать аргументы по имени, игнорируя порядок;
  • Параметры по умолчанию — позволяют опускать аргумент при вызове;
  • Типизация параметров — начиная с PHP 7.0, возможна явная декларация типов для скалярных значений (int, float, string, bool), а также для составных типов (array, callable, iterable, классов и интерфейсов).

Пример с типизацией и явным типом возвращаемого значения:

function greet(string $name): string {
return "Привет, $name!";
}

Разбор:

  • string $name требует строковый аргумент при вызове функции.
  • Внутри используется интерполяция: $name подставляется в строковый шаблон.
  • return отдает сформированное приветствие вызывающему коду.
  • : string запрещает возвращать типы, отличные от строки.
  • Пример показывает минимальный полностью типизированный контракт.

В этом примере:

  • string $name означает, что функция принимает ровно один аргумент, который должен быть строкой. При передаче значения другого типа (например, целого числа 42) PHP в режиме strict types выбросит TypeError. В режиме по умолчанию (coercive mode) PHP попытается выполнить неявное приведение: 42 станет "42" — и вызов завершится успешно.
  • : string после скобок объявляет, что функция гарантирует возврат значения типа string. Если функция завершается без return, или возвращает null, или возвращает значение другого типа (например, int), — в strict mode будет выброшено исключение; в coercive mode будет предпринята попытка приведения, но для null и intstring это сработает не всегда корректно, и в случае несовместимости — тоже будет TypeError.

Строгий режим типизации включается директивой declare(strict_types=1);, которая должна быть первой инструкцией в файле (после открывающего тега <?php, если он есть). Без этой директивы действует коэрцитивный режим, где возможно автоматическое приведение примитивных типов в ограниченных сценариях (например, intfloat, string содержащая число → int при передаче в int-параметр и т.п.).


Область видимости переменных и доступ к глобальному контексту

Функция в PHP создаёт собственную локальную область видимости. Переменные, объявленные вне функции, не доступны внутри неё, за исключением случаев, когда явно используется ключевое слово global или суперглобальные массивы ($_GET, $_POST, $_SERVER, $_ENV и др.). Использование global считается плохой практикой, так как нарушает принципы инкапсуляции и затрудняет тестирование и сопровождение.

Рекомендуемый подход — передавать все необходимые данные через параметры и возвращать результат явно. Это делает функцию чистой (в смысле функционального программирования — не имеет побочных эффектов, не зависит от внешнего состояния) или, по крайней мере, предсказуемой.

Пример нежелательного использования global:

$prefix = "Уважаемый";

function greet(string $name): string {
global $prefix;
return "$prefix $name";
}

Разбор:

  • Переменная $prefix объявлена во внешней области файла.
  • global $prefix подключает эту переменную внутрь функции.
  • Функция начинает зависеть от внешнего состояния, что ухудшает тестируемость.
  • Такой стиль усложняет повторное использование и рефакторинг.
  • Пример демонстрирует технически рабочий, но архитектурно слабый подход.

Альтернатива — инъекция зависимостей:

function greet(string $prefix, string $name): string {
return "$prefix $name";
}

echo greet("Уважаемый", "Иван");

Разбор:

  • Оба значения передаются явно параметрами, без скрытых зависимостей.
  • Функция становится детерминированной: одинаковые входы дают одинаковый результат.
  • echo выводит возвращенную строку в ответ или консоль.
  • Такой контракт проще покрывать unit-тестами.
  • Подход хорошо сочетается с DI и чистыми функциями.

Возврат значения и множественные выходы

Функция завершает своё выполнение и возвращает управление вызывающему коду при достижении инструкции return или при конце тела функции. Если return не указан — функция возвращает null. Даже если функция ничего не должна возвращать, явное указание return; (без значения) предпочтительнее неявного завершения: это улучшает читаемость и сигнализирует, что выход из функции — осознанное действие.

PHP не поддерживает возврат нескольких значений напрямую. Однако это легко эмулируется через:

  • возврат массива — return [$x, $y, $status];
  • использование передачи по ссылке (через & в параметре), что позволяет функции модифицировать переменную в вызывающем контексте;
  • применение объектов-обёрток или DTO (Data Transfer Objects), когда логически связанные данные группируются в структуру.

Передача по ссылке — мощный, но рискованный инструмент. Её явное указание в сигнатуре (function modify(&$value)) делает намерение очевидным, но скрывает семантику изменения из вызова: modify($x) выглядит как обычный вызов, хотя $x может быть изменена. Такой стиль требует особой осторожности и чёткой документации.


Анонимные функции и замыкания

Интерактивная схема — цепочка лексических окружений на примере JavaScript. В PHP захват — через use в замыкании, но идея внешнего окружения та же.

Play ITЗагрузка интерактивного демо…

Начиная с PHP 5.3, язык поддерживает анонимные функции — функции без имени, которые могут быть присвоены переменным, переданы как аргументы в другие функции или возвращены из функций. Они объявляются с помощью конструкции function(...) { ... }, без имени после function, и часто используются для реализации callback-логики (например, в array_map, usort, array_filter).

Пример:

$greet = function($name) {
return "Здравствуй, $name";
};
echo $greet("Ольга");

Разбор:

  • В переменной $greet хранится объект Closure.
  • Вызов $greet("Ольга") использует closure как обычную функцию.
  • Текст приветствия формируется при каждом вызове из переданного аргумента.
  • Такой формат удобен для передачи поведения в array_map, usort, middleware.
  • Замыкание можно передавать, хранить и возвращать как значение.

Тип переменной $greetClosure. Это встроенный класс PHP, инкапсулирующий исполняемый код анонимной функции. Экземпляры Closure являются callable: их можно вызывать как обычные функции — с помощью синтаксиса $closure(...). Кроме того, они реализуют магический метод __invoke(), что позволяет использовать их в любом контексте, требующем вызываемого объекта.

Важнейшее свойство анонимных функций — поддержка замыканий. Замыкание — это анонимная функция, которая "захватывает" переменные из окружающей лексической области видимости даже после того, как эта область перестаёт существовать. В PHP захват переменных осуществляется явно, с помощью ключевого слова use:

$greeting = "Добро пожаловать";

$greeter = function(string $name) use ($greeting): string {
return "$greeting, $name!";
};

echo $greeter("Мария"); // Добро пожаловать, Мария!

Разбор:

  • Внешняя переменная $greeting захватывается по значению через use.
  • Внутри closure она доступна даже после выхода из исходного контекста.
  • string $name и : string сохраняют строгий контракт входа и выхода.
  • Итоговая строка складывается из захваченного приветствия и текущего имени.
  • Такой паттерн полезен для частичного применения параметров.

Захват может быть по значению (по умолчанию) или по ссылке — с помощью амперсанда:

$count = 0;

$increment = function() use (&$count) {
$count++;
};

$increment();
$increment();
echo $count; // 2

Разбор:

  • &$count включает захват по ссылке, а не копию значения.
  • Каждая операция $count++ изменяет одну и ту же внешнюю переменную.
  • Повторный вызов closure накапливает состояние между вызовами.
  • Этот прием удобен для счетчиков, но требует аккуратности из-за побочных эффектов.
  • Без & значение осталось бы неизменным снаружи.

Обратите внимание: захват по ссылке — единственный способ изменить переменную из внешней области внутри анонимной функции. При захвате по значению копируется текущее значение переменной на момент объявления замыкания, и любые последующие изменения внешней переменной не повлияют на внутреннюю копию.

Захват переменной по ссылке в замыкании:

$замыкание = function() use (&$изменяемая_переменная) {
$изменяемая_переменная++;
};

Разбор:

  • Это минимальный шаблон closure с мутацией внешнего состояния.
  • use (&$...) сигнализирует, что функция будет менять переменную снаружи.
  • Оператор ++ увеличивает значение на единицу при каждом вызове.
  • Подобные конструкции стоит документировать, чтобы избежать скрытых сайд-эффектов.

Анонимные функции и замыкания — основа для функциональных паттернов в PHP — каррирования, частичного применения, создания фабрик, реализации стратегий и обработчиков событий. Их широкое применение наблюдается в фреймворках (Laravel, Symfony), маршрутизаторах, и в асинхронных библиотеках (например, ReactPHP).


Различие между function и fn()

Начиная с PHP 7.4 появилась краткая форма объявления анонимных функций — arrow functions (стрелочные функции), синтаксис fn(...) => выражение. Они всегда однострочные, всегда возвращают результат вычисления выражения (неявный return), и автоматически захватывают переменные из родительской области по значению — без необходимости use.

Пример:

$factor = 3;
$multiply = fn($x) => $x * $factor;

echo $multiply(5); // 15

Разбор:

  • fn($x) => ... объявляет стрелочную функцию с неявным return.
  • Переменная $factor автоматически захватывается по значению.
  • Вызов $multiply(5) вычисляет произведение аргумента и захваченного фактора.
  • Формат подходит для коротких математических или фильтрующих выражений.
  • Для многошаговой логики лучше использовать обычный function (...) { ... }.

Это эквивалентно:

$factor = 3;
$multiply = function($x) use ($factor) {
return $x * $factor;
};

Разбор:

  • Это эквивалент стрелочной функции в классическом синтаксисе closure.
  • use ($factor) явно показывает источник внешнего значения.
  • Явный return делает поток управления заметным в многострочных блоках.
  • Такой стиль расширяем — можно добавить if, логирование, try/catch.

Захват по значению исключает случайную модификацию внешнего состояния. Однако стрелочные функции не могут содержать блочный код (циклы, if без тернарного оператора, try/catch), не поддерживают return явно, и не позволяют захват по ссылке.

Стрелочная функция:

$переменная = fn(тип $параметр): тип_возврата => выражение;

Разбор:

  • Шаблон фиксирует полную сигнатуру стрелочной функции с типами.
  • В правой части допускается одно выражение, результат которого возвращается.
  • Синтаксис компактен и полезен для inline-callback в коллекциях.
  • Вложенные многошаговые операции в таком формате ухудшают читаемость.

Встроенные и пользовательские функции

PHP поставляется с обширной стандартной библиотекой, насчитывающей более 7000 встроенных функций, охватывающих работу с текстом, массивами, датами, файлами, сетью, базами данных, криптографией и др. Эти функции объявлены в глобальной области видимости и доступны всегда (если не отключены настройками disable_functions в php.ini).

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

namespace Utils;

function sanitize(string $input): string {
return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
}

Разбор:

  • namespace Utils; изолирует функцию от глобального пространства имен.
  • trim($input) убирает пробелы по краям входной строки.
  • htmlspecialchars(..., ENT_QUOTES, 'UTF-8') экранирует HTML-символы и кавычки.
  • Возврат string гарантирует безопасную строку для вывода в HTML-контекст.
  • Пример показывает типичный базовый этап нормализации и XSS-защиты.

Тогда вызов будет: \Utils\sanitize($str) или use function Utils\sanitize; sanitize(...).

Конфликты имён между встроенными и пользовательскими функциями недопустимы: PHP не позволяет переопределить существующую функцию, даже если она объявлена в другом пространстве имён с тем же именем. Повторное объявление функции (function foo() {} function foo() {}) приведёт к фатальной ошибке на этапе компиляции.


Производительность и компиляция

В современных версиях PHP (7.0+) функции компилируются в опкод (bytecode) и кэшируются в OPcache при повторных запросах. Это делает вызов пользовательских функций практически таким же быстрым, как и встроенных — накладные расходы минимальны. Однако следует избегать излишней вложенности вызовов ("функциональный ад"), особенно в критичных к производительности участках (циклах), поскольку каждый вызов всё же требует создания стекового фрейма и передачи параметров.

Типизация параметров и возвращаемых значений повышает надёжность кода и даёт OPcache дополнительную информацию для оптимизации: например, при string $s движок может пропустить проверки типа при внутренних операциях со строкой.


Рекурсивные функции и ограничения стека вызовов

Рекурсия в PHP реализуется так же, как и в большинстве императивных языков — функция вызывает саму себя, либо напрямую, либо косвенно через цепочку других функций. PHP не оптимизирует хвостовую рекурсию (tail call optimization), поэтому каждый рекурсивный вызов создаёт новый фрейм в стеке вызовов. Глубина стека ограничена настройкой zend.max_allowed_stack_size (задаётся в байтах) и, в меньшей степени, параметром xdebug.max_nesting_level, если установлен расширение Xdebug.

По умолчанию максимальная глубина вложенности вызовов составляет 256 (в Xdebug) или значительно выше в "чистом" Zend Engine — но точное значение зависит от конфигурации сервера и объёма данных в каждом фрейме. Превышение лимита приводит к фатальной ошибке: Fatal error: Maximum function nesting level of 'N' reached.

Пример простой рекурсивной функции — вычисление факториала:

function factorial(int $n): int {
if ($n <= 1) {
return 1;
}
return $n * factorial($n - 1);
}

Разбор:

  • Рекурсивная ветка вызывает factorial($n - 1) до достижения базового случая.
  • Условие $n <= 1 предотвращает бесконечную рекурсию.
  • Вычисление строится как произведение текущего n на факториал предыдущего числа.
  • Подход нагляден, но создает дополнительные кадры стека на каждом шаге.
  • Для больших значений безопаснее использовать итеративный алгоритм.

Хотя этот код корректен с математической точки зрения, его применение для больших n (например, factorial(10000)) гарантированно вызовет переполнение стека. В таких случаях рекомендуется использовать итеративную реализацию:

function factorial(int $n): int {
$result = 1;
for ($i = 2; $i <= $n; $i++) {
$result *= $i;
}
return $result;
}

Разбор:

  • Начальное значение $result = 1 нейтрально для операции умножения.
  • Цикл for последовательно домножает результат на числа от 2 до $n.
  • Итеративный подход экономит стек и устойчив при больших n.
  • По смыслу результат совпадает с рекурсивным вариантом.
  • Такой шаблон предпочтителен для production-кода с большими входами.

Рекурсия оправдана при работе с древовидными или вложенными структурами данных — обход файловой системы (RecursiveDirectoryIterator — это итератор, но его можно эмулировать через рекурсивную функцию), разбор вложенных массивов, JSON-объектов, XML-документов, AST (абстрактных синтаксических деревьев). В таких сценариях глубина рекурсии, как правило, ограничена природой данных (например, уровень вложенности директорий редко превышает 20–30), и риск переполнения минимален.

Важно: при проектировании рекурсивной функции всегда выделяйте базовый случай (условие завершения), иначе рекурсия станет бесконечной. PHP не обнаруживает бесконечную рекурсию заранее — она приведёт к зависанию процесса или исчерпанию памяти/стека.


Вариадические функции и оператор распаковки ...

Вариадическая функция — это функция, принимающая переменное число аргументов. В PHP поддержка таких функций реализована через оператор ... (так называемый splat operator или spread operator), введённый в PHP 5.6.

Есть два основных сценария использования:

  1. Сбор аргументов в массив — при объявлении функции:
function sum(int ...$numbers): int {
$total = 0;
foreach ($numbers as $n) {
$total += $n;
}
return $total;
}

echo sum(1, 2, 3, 4); // 10
echo sum(); // 0

Разбор:

  • int ...$numbers собирает любое число аргументов в массив.

  • foreach проходит по каждому значению и накапливает сумму.

  • Вызов sum() без аргументов работает, потому что массив пустой и $total остается 0.

  • Тип int у вариадика ограничивает тип каждого переданного аргумента.

  • Вариадик удобен для API, где число значений заранее неизвестно.

    Здесь ...$numbers означает: "собрать все переданные аргументы в массив $numbers". Типизация int ...$numbers гарантирует, что каждый аргумент будет приведён или проверен на соответствие типу int. Передача нецелого значения вызовет ошибку в strict mode или приведение — в coercive.

  1. Распаковка массива в аргументы — при вызове функции:
$values = [10, 20, 30];
echo sum(...$values); // эквивалентно sum(10, 20, 30)

Разбор:

  • Оператор ... в вызове распаковывает элементы массива в позиционные аргументы.

  • Это позволяет переиспользовать функции без ручного перечисления значений.

  • Подход полезен в прокси-методах и делегировании вызовов.

  • Порядок элементов массива определяет порядок аргументов в функции.

    Оператор ... перед массивом "раскрывает" его элементы как отдельные позиционные аргументы. Это особенно полезно при делегировании вызовов (например, в прокси-функциях, декораторах, или при переопределении методов в наследниках классов).

Вариадические параметры могут комбинироваться с обычными, но должны идти последними в сигнатуре:

function log(string $level, string $message, mixed ...$context) {
// $level и $message — обязательные
// $context — опциональные дополнительные данные
}

Разбор:

  • Первые два параметра формируют обязательный минимум контекста лога.
  • mixed ...$context принимает произвольное число дополнительных значений.
  • Вариадик всегда идет последним в сигнатуре.
  • Такой контракт удобно расширять без ломки существующих вызовов.
  • Пример отражает реальный формат логирования в прикладных сервисах.

Семантически, вариадические функции — это обобщение функций с параметрами по умолчанию, когда число параметров заранее неизвестно. Они позволяют строить гибкие интерфейсы без необходимости передавать массив вручную (log('error', '...', ['user_id' => 123])), сохраняя при этом читаемость вызова.

Вариадическая функция (переменное число аргументов):

function имя_функции(тип ...$аргументы): тип_возврата {
foreach ($аргументы as $значение) {
// обработка
}
return результат;
}

Разбор:

  • Это универсальный шаблон вариадической функции с типизированными аргументами.
  • Все переданные значения объединяются в массив $аргументы.
  • foreach описывает типичный паттерн пакетной обработки параметров.
  • Явный return завершает функцию единым результатом.

Вызов функции с распаковкой массива аргументов:

$аргументы = [значение1, значение2];
результат = имя_функции(...$аргументы);

Разбор:

  • Массив подготавливает набор аргументов динамически.
  • Распаковка ...$аргументы передает каждый элемент как отдельный параметр.
  • Прием удобен для адаптеров, оберток и повторных вызовов с разными наборами данных.
  • Код устраняет необходимость писать несколько перегрузок под разное число аргументов.

Обработка ошибок и семантика возврата

PHP исторически использует неоднородные подходы к сигнализированию об ошибках:

  • Возврат false — в функциях, возвращающих скаляр (strpos, file_get_contents, json_decode при нестрогом режиме и т.д.);
  • Возврат null — при отсутствии результата (например, array_search, если элемент не найден);
  • Генерация предупреждений/уведомленийE_WARNING, E_NOTICE через trigger_error() или внутренние механизмы;
  • Выброс исключений — начиная с PHP 5, особенно в объектно-ориентированном коде и в новых расширениях (например, DateTime, PDO, JsonException с PHP 7.3 при JSON_THROW_ON_ERROR).

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

Например, strpos() возвращает 0, если подстрока найдена в начале строки, и false, если не найдена. Поэтому неправильно писать:

if (strpos($haystack, $needle)) { /* ... */ } // ошибка: 0 интерпретируется как false

Разбор:

  • strpos() возвращает позицию найденной подстроки или false, если не найдено.
  • Значение 0 означает "найдено в начале строки", но в условии if трактуется как false.
  • Поэтому такой вариант дает логическую ошибку и пропускает валидное совпадение.
  • Пример показывает важность строгих сравнений для функций с неоднозначным возвратом.

Правильно — использовать строгое сравнение:

if (false !== strpos($haystack, $needle)) { /* найдено */ }

Разбор:

  • Сравнение false !== ... явно отделяет "не найдено" от позиции 0.
  • Условие корректно срабатывает для всех валидных индексов, включая начало строки.
  • Строгое сравнение защищает код от скрытых ошибок приведения типов.
  • Это стандартный безопасный шаблон работы с strpos().

Современные практики рекомендуют:

  • Использовать исключения вместо false/null в пользовательском коде, особенно в библиотеках и слоях ядра приложения. Исключения явно сигнализируют об исключительной ситуации, не смешиваются с валидными данными и обеспечивают стек вызовов для отладки.
  • Применять тип ?T (nullable) с явной проверкой на null, если отсутствие результата — ожидаемый сценарий (например, getUserById(int $id): ?User).
  • Избегать смешивания false и значимых данных в одном типе возврата. Если функция может вернуть строку или провалиться — лучше выбросить исключение, либо вернуть объект-результат (например, Result<string, Error> через пользовательский класс или библиотеку вроде spatie/result).

С PHP 8.1 появилась поддержка never-типа ( — never), используемого для функций, которые никогда не возвращают управление (например, всегда выбрасывают исключение или вызывают exit()):

function abort(int $code): never {
http_response_code($code);
exit;
}

Разбор:

  • Тип never сообщает, что функция не возвращает управление вызывающему коду.
  • http_response_code($code) устанавливает HTTP-статус перед завершением запроса.
  • exit немедленно останавливает выполнение скрипта.
  • Такой контракт улучшает анализ потока управления в PHPStan/Psalm.

Это помогает анализаторам кода (PHPStan, Psalm) точнее отслеживать поток управления.


Тип callable и интерфейс вызываемости

В PHP существует понятие callable — сущности, которую можно вызвать с помощью синтаксиса (...) или call_user_func(). К callable относятся:

  • Имена функций как строки: 'strlen', 'greet';
  • Анонимные функции и экземпляры Closure;
  • Массивы вида [$object, 'method'] — для вызова метода экземпляра;
  • Массивы вида ['ClassName', 'method'] — для статических методов;
  • Объекты, реализующие магический метод __invoke().

Проверка на callable осуществляется функцией is_callable(), а проверка типа в параметрах — через псевдотип callable:

function apply(callable $fn, $value) {
return $fn($value);
}

Разбор:

  • callable $fn требует вызываемый аргумент — функцию, closure, метод или invokable-объект.
  • Функция делегирует вычисление в переданный callback.
  • return $fn($value) возвращает результат вызова без дополнительной обработки.
  • Это базовый паттерн функций высшего порядка.

Однако важно понимать: callable — это структурный тип, а не номинальный. Он не гарантирует, что вызов завершится успешно — только то, что синтаксис вызова допустим. Например:

apply(['stdClass', 'nonExistentMethod'], 42); // is_callable() вернёт true, но вызов упадёт с Error

Разбор:

  • Массив в формате [класс, метод] интерпретируется как callable-ссылка.
  • При фактическом вызове PHP проверяет существование и доступность метода.
  • Если метод отсутствует, происходит ошибка времени выполнения.
  • Пример показывает, что структурная проверка callable не равна гарантиям успешного исполнения.

Поскольку call_user_func() и callable используют динамическое связывание, статические анализаторы не могут всегда предсказать корректность вызова. Поэтому в критичных участках рекомендуется:

  • Использовать строгую типизацию параметров методов (например, Closure вместо callable, если допустимы только замыкания);
  • Применять интерфейсы с единственным методом (например, interface Validator { public function validate(mixed $value): bool; }) — это даёт compile-time проверку и лучше отражает намерение;
  • Избегать передачи строк-имён функций в публичные API — это снижает рефакторингопригодность и усложняет поиск зависимостей.

Псевдофункции

Некоторые часто используемые "функции" в PHP на самом деле не являются функциями, а представляют собой языковые конструкции. К ним относятся:

  • echo, print — вывод данных;
  • include, require, include_once, require_once — загрузка файлов;
  • isset(), empty(), unset() — работа с существованием и содержимым переменных;
  • die(), exit() — принудительное завершение скрипта.

Главное отличие — языковые конструкции не требуют скобок (хотя для isset, empty, unset скобки обязательны синтаксически), не могут быть сохранены в переменную, не могут быть переданы как callable, и не подвержены обычным правилам области видимости.

Например, isset($var) может принимать недекларированные переменные без предупреждения — потому что проверка существования происходит до вычисления аргумента. Если бы isset была настоящей функцией, вызов isset($undefined) привёл бы к Notice: Undefined variable, так как аргументы функции вычисляются до передачи.

Аналогично, include 'file.php' интерпретируется как часть синтаксиса, а не как вызов — поэтому он может возвращать значение из подключаемого файла (return 42; внутри file.php), и это значение будет результатом выражения include.

Понимание этого различия критично при рефакторинге и при написании гибкого кода — нельзя, например, сделать $logger = echo; $logger("test"); — это синтаксическая ошибка. Вместо этого следует использовать обёртки: function output(string $s) { echo $s; }.


Лучшие практики проектирования функций в PHP

Завершая теоретическую часть, систематизируем рекомендации по написанию функций, основанные на принципах читаемости, тестируемости и сопровождаемости:

  • Одна функция — одна обязанность. Функция должна решать одну конкретную задачу на одном уровне абстракции. Если в названии появляются союзы "и", "или", "затем" — это сигнал к декомпозиции.
  • Короткие сигнатуры. Оптимально — до 3–4 параметров. При большем числе возникает когнитивная нагрузка, растёт риск ошибок при передаче. Альтернатива — передача объекта конфигурации (DTO) или применение шаблона Builder.
  • Явные имена. Название должно отражать действие и эффектcalculateTotalPrice(), validateEmailAddress(), fetchUserById(). Избегайте общих слов — process(), handle(), doSomething().
  • Избегайте побочных эффектов. Функция не должна изменять глобальное состояние, писать в файлы, отправлять HTTP-запросы или модифицировать переданные по значению аргументы — если это не является её прямой обязанностью. Побочные эффекты должны быть явными и локализованными.
  • Строгая типизация. Используйте declare(strict_types=1) в каждом файле. Декларируйте типы параметров и возвращаемых значений. Это предотвращает неявные ошибки приведения и улучшает работу IDE и статических анализаторов.
  • Обработка ошибок через исключения. Если функция не может выполнить свою задачу — она должна сообщить об этом не через false или null, а через выброс исключения, наследуемого от \Exception или \Throwable.
  • Документирование через PHPDoc и/или атрибуты. Даже при наличии типов, пояснения к поведению, ограничениям, исключениям и неочевидным граничным случаям необходимы. Например:
/**
* Парсит строку в формате ISO 8601 и возвращает объект DateTimeImmutable.
*
* @param string $isoString Должна содержать дату и время с временной зоной, например '2025-11-18T15:30:00+03:00'
* @throws \InvalidArgumentException если строка не соответствует формату
* @return \DateTimeImmutable
*/
function parseIsoDateTime(string $isoString): \DateTimeImmutable { /* ... */ }

Разбор:

  • Блок PHPDoc описывает контракт функции для IDE и статических анализаторов.
  • Теги @param, @throws, @return фиксируют входы, ошибки и тип результата.
  • Сигнатура возвращает \DateTimeImmutable, что исключает мутацию даты после создания.
  • Комментарий помогает понять ограничения без чтения внутренней реализации.

Практический шаблон функции прикладного уровня

Ниже минимальный каркас, который удобно использовать в бизнес-коде с вводом с формы или API:

Код ITЗагрузка примера кода…

Разбор:

  • trim и mb_strtolower нормализуют пользовательский email к единому формату.
  • Пустая строка и неверный формат приводят к InvalidArgumentException.
  • filter_var(..., FILTER_VALIDATE_EMAIL) выполняет стандартную серверную проверку формата.
  • Возврат всегда строковый, поэтому вызывающий код получает предсказуемый результат.
  • Такой шаблон удобно применять в сервисах, form-request и тестах.

Что здесь важно:

  • Строка нормализуется в начале, чтобы все вызывающие части системы работали с единым форматом.
  • Ошибки валидации сигнализируются исключениями, а не "магическими" false.
  • Функция возвращает только один согласованный тип, поэтому ее легче использовать и тестировать.

Для работы с исключениями в таком стиле см. обработку исключений в прикладном коде, для сохранения результата в БД — PDO.


Основа по протоколу

Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.