Функции и include/require

Функции спасают от копипасты. А include/require спасают от файла на 500 строк (ну... хотя бы помогают).

Обычные функции

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

echo greet('Иван'); // Привет, Иван!

Функция без return вернёт null:

<?php
function sayHello(string $name): void {
    echo "Hello, $name!\n";
    // return ничего не возвращаем - void
}

Аргументы по умолчанию

<?php
function createUser(string $name, string $role = 'student'): array {
    return ['name' => $name, 'role' => $role];
}

$user1 = createUser('Иван');           // role = 'student'
$user2 = createUser('Анна', 'admin');  // role = 'admin'
Аргументы с дефолтами должны идти после обязательных. `function f($a = 1, $b)` - можно, но вызов `f(b: 2)` потребует named arguments (PHP 8+). Лучше так не делать.

Nullable-типы

Если аргумент или возврат может быть null:

<?php
function findUser(int $id): ?array {
    // ?array = может вернуть array или null
    $users = [1 => ['name' => 'Иван']];
    return $users[$id] ?? null;
}

$user = findUser(99); // null

Вариативные аргументы (...$args)

<?php
function sum(int ...$numbers): int {
    $total = 0;
    foreach ($numbers as $n) {
        $total += $n;
    }
    return $total;
}

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

Spread-оператор работает и при вызове:

<?php
$nums = [10, 20, 30];
echo sum(...$nums); // 60

Именованные аргументы (PHP 8+)

<?php
function createPost(string $title, string $body, bool $published = false): array {
    return ['title' => $title, 'body' => $body, 'published' => $published];
}

// Можно указать только нужные, в любом порядке:
$post = createPost(
    title: 'Привет',
    published: true,
    body: 'Текст поста',
);

strict_types

В начале файла:

<?php
declare(strict_types=1);

function add(int $a, int $b): int {
    return $a + $b;
}

echo add(2, 3);     // 5
echo add('2', '3'); // TypeError!

Без strict_types PHP молча приведёт "2" к 2. С ним - выбросит TypeError (подробнее о типах - в уроке про переменные).

`declare(strict_types=1)` работает только в том файле, где объявлен. Если `a.php` подключает `b.php`, строгость в `a.php` не распространяется на `b.php`. Каждый файл решает за себя.

Анонимные функции (closures)

<?php
$multiply = function(int $a, int $b): int {
    return $a * $b;
};

echo $multiply(3, 4); // 12

Анонимные функции часто передают в array_map, array_filter, usort:

<?php
$prices = [100, 250, 50, 400];
$expensive = array_filter($prices, function(int $price): bool {
    return $price > 200;
});
// [250, 400]

use - захват переменных из внешней области

В PHP функции НЕ видят внешние переменные автоматически (в отличие от JavaScript):

<?php
$tax = 0.2;

$withTax = function(float $price) use ($tax): float {
    return $price * (1 + $tax);
};

echo $withTax(100); // 120
`use ($tax)` передаёт копию значения на момент создания функции. Если потом `$tax` изменится - замыкание этого не увидит. Для ссылки: `use (&$tax)`.

Стрелочные функции (PHP 7.4+)

Короткий синтаксис для однострочных:

<?php
$double = fn(int $n): int => $n * 2;

echo $double(5); // 10

// В array_map:
$nums = [1, 2, 3];
$doubled = array_map(fn($n) => $n * 2, $nums);
// [2, 4, 6]

Стрелочные функции захватывают внешние переменные автоматически (без use):

<?php
$rate = 1.1;
$applyRate = fn(float $price) => $price * $rate;
echo $applyRate(100); // 110

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

В PHP каждая функция имеет свою область видимости. Внешние переменные не доступны:

<?php
$name = 'Иван';

function sayName(): void {
    // echo $name; // Warning: Undefined variable
    // Нужно либо передать аргументом, либо use (для замыканий)
}

Глобальные переменные доступны через global (но это антипаттерн):

<?php
$counter = 0;

function increment(): void {
    global $counter; // не делай так в реальном коде
    $counter++;
}

Функции как аргументы (callable)

<?php
function applyToAll(array $items, callable $fn): array {
    $result = [];
    foreach ($items as $item) {
        $result[] = $fn($item);
    }
    return $result;
}

$names = ['иван', 'анна', 'пётр'];
$upper = applyToAll($names, 'mb_strtoupper');
// ['ИВАН', 'АННА', 'ПЁТР']

// Или с анонимной функцией:
$lengths = applyToAll($names, fn($s) => mb_strlen($s));
// [4, 4, 4]

include vs require

Разница - в поведении при ошибке:

<?php
// include - если файла нет, будет warning, код продолжит выполняться
include __DIR__ . '/optional-helpers.php';

// require - если файла нет, fatal error (скрипт остановится)
require __DIR__ . '/helpers.php';

В реальных проектах почти всегда используй require - если файл нужен, его отсутствие должно быть ошибкой.

include_once / require_once

Гарантируют, что файл подключится только один раз:

<?php
require_once __DIR__ . '/config.php';
require_once __DIR__ . '/config.php'; // второй вызов ничего не сделает

Это важно для файлов с определениями функций и классов - повторное подключение вызовет Cannot redeclare function.

Всегда используй `__DIR__` для подключения файлов. Без него PHP ищет файл относительно текущей рабочей директории (`cwd`), которая может быть любой. `__DIR__` - это директория текущего файла, и путь всегда предсказуем.

Структура проекта с include

project/
├── index.php          # точка входа
├── config.php         # настройки
├── helpers.php        # утилитарные функции
└── templates/
    ├── header.php     # шапка HTML
    └── footer.php     # подвал HTML
<?php
// index.php
require __DIR__ . '/config.php';
require __DIR__ . '/helpers.php';

$title = 'Главная';
require __DIR__ . '/templates/header.php';

echo '<h1>Добро пожаловать!</h1>';

require __DIR__ . '/templates/footer.php';

Типичные ошибки

  • Сигнатура без type hints. function save($user, $data) принимает что угодно, баги вылезают в рантайме. Добавь типы аргументов и возврата: function save(User $user, array $data): void. В Symfony-контроллерах type hints обязательны - autowiring без них не сработает.
  • Функция без return там, где он ожидается. Молча возвращает null, вызывающий код ловит TypeError на следующем шаге. Поставь : void для процедур и : ReturnType для функций - IDE подсветит пропущенный return.
  • Длинная сигнатура с 6+ параметрами. createUser(string $name, string $email, int $age, string $role, bool $active, ?string $phone) - нарушение SRP и боль на вызывающей стороне. Сверни в DTO/readonly-класс или массив с типизированными ключами.
  • Позиционный вызов вместо named arguments. createPost("Title", "Body", false, true, null) - никто не помнит, что значит четвёртый true. Используй createPost(title: ..., body: ..., published: true) - PHP 8 это поддерживает нативно.
  • Скрытые побочные эффекты. Функция calculatePrice($items) внутри пишет в БД или мутирует $_SESSION - её нельзя протестировать и переиспользовать. Pure-логика отдельно, IO - отдельным сервисом, который явно инжектится.

Best practices

  • Ставь type hints и return types на каждую функцию, включая void и never. В Symfony это база - без типов не работает DI.
  • Используй union (int|string), nullable (?T) и mixed точечно - это документация в сигнатуре, читается с одного взгляда.
  • Variadic (...$args) применяй, когда количество аргументов реально переменное (array_merge-стиль), а не как замену массиву.
  • Named arguments - для функций с 3+ параметрами или булевыми флагами: вызов читается без подглядывания в сигнатуру.
  • Держи функции pure: вход → выход, без обращения к глобалкам, файлам, БД. Сайд-эффекты - в отдельный слой (репозиторий, сервис).

Мини-задание

  • Сделай helpers.php с функцией dd($value): never (var_dump + die)
  • Подключи его через require и вызови dd(["test" => 123])
  • Напиши функцию sum(int ...$numbers): int с вариативными аргументами
  • Напиши анонимную функцию, которая фильтрует массив строк по минимальной длине (используй use)
  • Перепиши её как стрелочную функцию (fn() =>)
  • Добавь declare(strict_types=1) и проверь, что sum("1", "2") вызывает ошибку

Зарегистрируйтесь бесплатно, чтобы пройти квиз, решить задание с автопроверкой и вести прогресс.