Skip to content

Глава 1.3: Функции

Создание, параметры, возврат значений, области видимости, анонимные функции


Зачем нужны функции?

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

php
<?php
// ❌ Без функций — копипаста
$area1 = $width1 * $height1;
$area2 = $width2 * $height2;
$area3 = $width3 * $height3;
// ... и так 10 раз

// А если формула изменится? Нужно менять в 10 местах!
php
<?php
// ✅ С функцией — один раз написал, используешь везде
function calculateArea($width, $height) {
    return $width * $height;
}

$area1 = calculateArea($width1, $height1);
$area2 = calculateArea($width2, $height2);
$area3 = calculateArea($width3, $height3);

// Изменить формулу? Меняем только в одном месте!

Функции дают:

┌─────────────────────────────────────────────────────────────────┐
│                    ПРЕИМУЩЕСТВА ФУНКЦИЙ                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   🔄 ПЕРЕИСПОЛЬЗОВАНИЕ                                          │
│      Написал один раз — используешь многократно                │
│                                                                 │
│   📦 АБСТРАКЦИЯ                                                 │
│      Скрываешь сложность за простым именем                     │
│      calculateTax() вместо формулы в 10 строк                  │
│                                                                 │
│   🧪 ТЕСТИРУЕМОСТЬ                                              │
│      Легко проверить: вход → выход                             │
│                                                                 │
│   📖 ЧИТАЕМОСТЬ                                                 │
│      Код рассказывает историю:                                 │
│      validateEmail(), sendNotification(), saveToDatabase()     │
│                                                                 │
│   🛠️ ПОДДЕРЖИВАЕМОСТЬ                                           │
│      Баг в логике? Исправь в одном месте                       │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

1. Создание функций

Базовый синтаксис

php
<?php
function имяФункции(параметры) {
    // Тело функции
    return результат;
}

Простейшая функция

php
<?php
// Определение функции
function sayHello() {
    echo "Привет, мир!";
}

// Вызов функции
sayHello();  // Привет, мир!
sayHello();  // Привет, мир!
sayHello();  // Привет, мир!

Правила именования

php
<?php
// ✅ Правильные имена (camelCase — рекомендуется)
function calculateTotal() {}
function getUserById() {}
function isValidEmail() {}
function sendNotification() {}

// ✅ snake_case тоже допустим
function calculate_total() {}
function get_user_by_id() {}

// ❌ Неправильные имена
function 123start() {}        // Нельзя начинать с цифры
function my-function() {}     // Нельзя дефис
function calculate total() {} // Нельзя пробел

Соглашения об именовании

php
<?php
// Глаголы для действий
function save() {}
function delete() {}
function update() {}
function create() {}
function send() {}
function validate() {}

// get/set для получения/установки
function getName() {}
function setName($name) {}

// is/has/can для проверок (возвращают bool)
function isActive() {}
function hasPermission() {}
function canEdit() {}

// Описательные имена
function calculateMonthlyPayment() {}  // ✅ Понятно
function calc() {}                      // ❌ Непонятно

Функция должна делать одну вещь!

php
<?php
// ❌ Плохо — функция делает слишком много
function processUser($data) {
    // Валидация
    if (empty($data['email'])) {
        return false;
    }
    // Сохранение в БД
    $db->insert('users', $data);
    // Отправка email
    mail($data['email'], 'Welcome!', 'Hello!');
    // Логирование
    file_put_contents('log.txt', 'User created');
    return true;
}

// ✅ Хорошо — каждая функция делает одно
function validateUserData($data) {
    return !empty($data['email']) && !empty($data['name']);
}

function saveUser($data) {
    return $db->insert('users', $data);
}

function sendWelcomeEmail($email) {
    return mail($email, 'Welcome!', 'Hello!');
}

function logAction($message) {
    file_put_contents('log.txt', $message, FILE_APPEND);
}

// Использование
if (validateUserData($data)) {
    saveUser($data);
    sendWelcomeEmail($data['email']);
    logAction('User created');
}

2. Параметры функций

Параметры vs Аргументы

php
<?php
//              параметры (при определении)
//                  ↓   ↓
function greet($name, $age) {
    echo "Привет, $name! Тебе $age лет.";
}

//       аргументы (при вызове)
//           ↓      ↓
greet("Иван", 25);
  • Параметры — переменные в определении функции
  • Аргументы — значения, передаваемые при вызове

Несколько параметров

php
<?php
function createFullName($firstName, $lastName, $middleName) {
    return "$lastName $firstName $middleName";
}

echo createFullName("Иван", "Петров", "Сергеевич");
// Петров Иван Сергеевич

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

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

echo greet("Иван");              // Привет, Иван!
echo greet("Иван", "Здравствуй"); // Здравствуй, Иван!

// Параметры по умолчанию ДОЛЖНЫ идти после обязательных!
// ❌ Неправильно
function wrong($a = 1, $b) {}  // $b обязательный после необязательного

// ✅ Правильно
function right($b, $a = 1) {}

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

php
<?php
function createUser($name, $email, $age = null, $role = 'user') {
    return [
        'name' => $name,
        'email' => $email,
        'age' => $age,
        'role' => $role,
    ];
}

// Позиционные аргументы — порядок важен
$user1 = createUser("Иван", "ivan@test.com", 25, "admin");

// Именованные аргументы — порядок не важен!
$user2 = createUser(
    email: "maria@test.com",
    name: "Мария",
    role: "editor"
    // age пропущен — будет null
);

// Можно смешивать (позиционные должны быть первыми)
$user3 = createUser("Пётр", "peter@test.com", role: "admin");

Передача по значению vs по ссылке

php
<?php
// По умолчанию — передача по ЗНАЧЕНИЮ (копия)
function addOne($num) {
    $num = $num + 1;
    echo "Внутри функции: $num\n";
}

$x = 5;
addOne($x);
echo "Снаружи: $x\n";
// Внутри функции: 6
// Снаружи: 5  ← Не изменилось!

// Передача по ССЫЛКЕ (& перед параметром)
function addOneByRef(&$num) {
    $num = $num + 1;
    echo "Внутри функции: $num\n";
}

$x = 5;
addOneByRef($x);
echo "Снаружи: $x\n";
// Внутри функции: 6
// Снаружи: 6  ← Изменилось!

Когда использовать ссылки?

php
<?php
// ✅ Хороший случай — модификация большого массива
function sortByPrice(&$products) {
    usort($products, fn($a, $b) => $a['price'] <=> $b['price']);
}

$products = [/* большой массив */];
sortByPrice($products);  // Сортирует на месте, без копирования

// ❌ Плохой случай — неочевидное поведение
function calculate(&$a, &$b) {
    $a = $a * 2;
    $b = $b * 2;
    return $a + $b;
}
// Лучше вернуть новые значения, чем менять исходные

Совет: Избегай передачи по ссылке, если можно вернуть результат. Ссылки делают код менее предсказуемым.

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

php
<?php
// ... (splat operator) — собирает аргументы в массив
function sum(...$numbers) {
    $total = 0;
    foreach ($numbers as $num) {
        $total += $num;
    }
    return $total;
}

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

// Можно комбинировать с обычными параметрами
function greetAll($greeting, ...$names) {
    foreach ($names as $name) {
        echo "$greeting, $name!\n";
    }
}

greetAll("Привет", "Иван", "Мария", "Пётр");
// Привет, Иван!
// Привет, Мария!
// Привет, Пётр!

// Распаковка массива при вызове
$numbers = [1, 2, 3, 4, 5];
echo sum(...$numbers);  // 15

3. Возврат значений

return — возврат результата

php
<?php
function add($a, $b) {
    return $a + $b;  // Возвращает результат и ЗАВЕРШАЕТ функцию
}

$result = add(5, 3);
echo $result;  // 8

// Можно использовать результат сразу
echo add(10, 20);  // 30
echo add(1, 2) + add(3, 4);  // 10

return без значения

php
<?php
function printIfPositive($num) {
    if ($num <= 0) {
        return;  // Ранний выход, возвращает null
    }
    echo "Число: $num\n";
}

$result = printIfPositive(-5);  // Ничего не выведет
var_dump($result);  // NULL

Функция без return

php
<?php
function sayHello($name) {
    echo "Привет, $name!";
    // Нет return — функция возвращает null
}

$result = sayHello("Иван");
var_dump($result);  // NULL

Ранний выход (Early Return)

php
<?php
// ❌ Глубокая вложенность
function processOrder($order) {
    if ($order !== null) {
        if ($order['status'] === 'pending') {
            if ($order['total'] > 0) {
                // Основная логика
                return processPayment($order);
            } else {
                return "Пустой заказ";
            }
        } else {
            return "Неверный статус";
        }
    } else {
        return "Заказ не найден";
    }
}

// ✅ Ранний выход — плоский код
function processOrder($order) {
    if ($order === null) {
        return "Заказ не найден";
    }
    
    if ($order['status'] !== 'pending') {
        return "Неверный статус";
    }
    
    if ($order['total'] <= 0) {
        return "Пустой заказ";
    }
    
    // Основная логика
    return processPayment($order);
}

Правило: Проверяй ошибочные случаи в начале и выходи сразу. Основная логика — в конце.

Возврат нескольких значений

php
<?php
// Способ 1: Массив
function getMinMax($numbers) {
    return [
        'min' => min($numbers),
        'max' => max($numbers),
    ];
}

$result = getMinMax([3, 1, 4, 1, 5, 9]);
echo "Min: {$result['min']}, Max: {$result['max']}";

// Способ 2: list() / деструктуризация
function divide($a, $b) {
    $quotient = intdiv($a, $b);
    $remainder = $a % $b;
    return [$quotient, $remainder];
}

[$q, $r] = divide(17, 5);
echo "17 / 5 = $q, остаток $r";  // 17 / 5 = 3, остаток 2

// Способ 3: Объект (для сложных случаев)
class Result {
    public bool $success;
    public mixed $data;
    public ?string $error;
}

4. Типизация функций (PHP 7+)

Типы параметров

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

greet("Иван");   // ✅ OK
greet(123);      // ⚠️ Автоприведение: "123"

// Строгий режим — ошибка при несоответствии типов
declare(strict_types=1);

greet(123);  // ❌ TypeError!

Типы возврата

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

function getUser(int $id): array {
    return ['id' => $id, 'name' => 'Test'];
}

function isValid(string $email): bool {
    return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}

function save(array $data): void {
    // void — функция ничего не возвращает
    file_put_contents('data.json', json_encode($data));
}

Nullable типы

php
<?php
// ?тип — может быть этот тип ИЛИ null
function findUser(int $id): ?array {
    $user = searchInDb($id);
    return $user ?: null;  // Может вернуть null
}

// Nullable параметр
function greet(?string $name): string {
    return "Привет, " . ($name ?? "Гость") . "!";
}

greet("Иван");  // Привет, Иван!
greet(null);    // Привет, Гость!

Union типы (PHP 8+)

php
<?php
// Несколько возможных типов
function processId(int|string $id): void {
    if (is_int($id)) {
        echo "Числовой ID: $id";
    } else {
        echo "Строковый ID: $id";
    }
}

processId(123);      // Числовой ID: 123
processId("abc");    // Строковый ID: abc

// В возвращаемом типе
function findUser(int $id): User|null {
    // ...
}

// Или короче (то же самое что |null)
function findUser(int $id): ?User {
    // ...
}

Mixed тип (PHP 8+)

php
<?php
// mixed = любой тип (включая null)
function dump(mixed $value): void {
    var_dump($value);
}

dump(123);
dump("text");
dump([1, 2, 3]);
dump(null);

Таблица типов

┌───────────────┬─────────────────────────────────────────────┐
│ Тип           │ Описание                                    │
├───────────────┼─────────────────────────────────────────────┤
│ int           │ Целое число                                 │
│ float         │ Дробное число                               │
│ string        │ Строка                                      │
│ bool          │ true/false                                  │
│ array         │ Массив                                      │
│ object        │ Любой объект                                │
│ callable      │ Функция/callback                            │
│ iterable      │ Массив или Traversable                      │
│ mixed         │ Любой тип (PHP 8+)                          │
│ void          │ Ничего не возвращает                        │
│ never         │ Никогда не возвращает (exit/throw) PHP 8.1+ │
│ null          │ Только null                                 │
│ true/false    │ Только true или false (PHP 8.2+)            │
│ ClassName     │ Объект конкретного класса                   │
│ ?type         │ type или null                               │
│ type1|type2   │ type1 или type2 (PHP 8+)                    │
└───────────────┴─────────────────────────────────────────────┘

5. Область видимости (Scope)

Локальные переменные

Переменные, созданные внутри функции, видны только внутри неё:

php
<?php
function test() {
    $localVar = "Я локальная";
    echo $localVar;  // ✅ Работает
}

test();
echo $localVar;  // ❌ Ошибка! Переменная не существует снаружи

Глобальные переменные

Переменные снаружи функции не видны внутри (по умолчанию):

php
<?php
$globalVar = "Я глобальная";

function test() {
    echo $globalVar;  // ❌ Ошибка! Переменная не определена
}

test();

Доступ к глобальным переменным: global

php
<?php
$counter = 0;

function increment() {
    global $counter;  // Объявляем, что используем глобальную
    $counter++;
}

increment();
increment();
increment();
echo $counter;  // 3

$GLOBALS — суперглобальный массив

php
<?php
$name = "Иван";
$age = 25;

function showInfo() {
    echo $GLOBALS['name'] . ", " . $GLOBALS['age'] . " лет";
}

showInfo();  // Иван, 25 лет

⚠️ Почему global — это плохо?

php
<?php
// ❌ Плохо — зависимость от глобальной переменной
$config = ['debug' => true];

function log($message) {
    global $config;
    if ($config['debug']) {
        echo $message;
    }
}

// Проблемы:
// 1. Неочевидная зависимость
// 2. Трудно тестировать
// 3. Трудно переиспользовать

// ✅ Хорошо — передавай зависимости явно
function log($message, $config) {
    if ($config['debug']) {
        echo $message;
    }
}

// Или ещё лучше — передай только нужное
function log($message, bool $debug = false) {
    if ($debug) {
        echo $message;
    }
}

Правило: Избегай global. Передавай данные через параметры, возвращай через return.

Статические переменные

Переменная сохраняет значение между вызовами функции:

php
<?php
function counter() {
    static $count = 0;  // Инициализируется только один раз!
    $count++;
    return $count;
}

echo counter();  // 1
echo counter();  // 2
echo counter();  // 3
echo counter();  // 4

// Без static — каждый раз 1
function counterNoStatic() {
    $count = 0;
    $count++;
    return $count;
}

echo counterNoStatic();  // 1
echo counterNoStatic();  // 1 (всегда 1)

Практическое применение static

php
<?php
// Кеширование результата
function getExpensiveData() {
    static $cache = null;
    
    if ($cache === null) {
        echo "Вычисляем...\n";
        $cache = heavyCalculation();
    }
    
    return $cache;
}

getExpensiveData();  // Вычисляем... (первый раз)
getExpensiveData();  // (из кеша)
getExpensiveData();  // (из кеша)

// Генератор уникальных ID
function generateId(): int {
    static $id = 0;
    return ++$id;
}

echo generateId();  // 1
echo generateId();  // 2
echo generateId();  // 3

6. Анонимные функции (Closures)

Что такое анонимная функция?

Функция без имени, которую можно сохранить в переменную:

php
<?php
// Обычная функция
function add($a, $b) {
    return $a + $b;
}

// Анонимная функция
$add = function($a, $b) {
    return $a + $b;
};  // Точка с запятой обязательна!

echo add(2, 3);     // 5
echo $add(2, 3);    // 5 (вызов через переменную)

Зачем нужны анонимные функции?

php
<?php
// 1. Callback для функций высшего порядка
$numbers = [3, 1, 4, 1, 5, 9, 2, 6];

// Сортировка с кастомным сравнением
usort($numbers, function($a, $b) {
    return $b - $a;  // По убыванию
});
// [9, 6, 5, 4, 3, 2, 1, 1]

// Фильтрация
$evenNumbers = array_filter($numbers, function($n) {
    return $n % 2 === 0;
});
// [6, 4, 2]

// Трансформация
$doubled = array_map(function($n) {
    return $n * 2;
}, $numbers);
// [18, 12, 10, 8, 6, 4, 2, 2]

Замыкания (Closures) — захват переменных

Анонимная функция может "захватить" переменные из внешней области:

php
<?php
$multiplier = 3;

// use — захватывает переменные из внешней области
$multiply = function($n) use ($multiplier) {
    return $n * $multiplier;
};

echo $multiply(5);   // 15
echo $multiply(10);  // 30

// Без use — переменная недоступна
$broken = function($n) {
    return $n * $multiplier;  // ❌ Ошибка! $multiplier не определена
};

use по значению vs по ссылке

php
<?php
$value = 10;

// По значению (копия)
$byValue = function() use ($value) {
    return $value;
};

// По ссылке
$byRef = function() use (&$value) {
    return $value;
};

echo $byValue();  // 10
echo $byRef();    // 10

$value = 20;

echo $byValue();  // 10 (копия, не изменилась)
echo $byRef();    // 20 (ссылка, отражает изменение)

Практический пример: фильтр по порогу

php
<?php
function createFilter($threshold) {
    return function($value) use ($threshold) {
        return $value >= $threshold;
    };
}

$numbers = [1, 5, 10, 15, 20, 25];

$above10 = createFilter(10);
$above20 = createFilter(20);

$filtered1 = array_filter($numbers, $above10);  // [10, 15, 20, 25]
$filtered2 = array_filter($numbers, $above20);  // [20, 25]

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

Синтаксис

php
<?php
// Обычная анонимная функция
$add = function($a, $b) {
    return $a + $b;
};

// Стрелочная функция — короче!
$add = fn($a, $b) => $a + $b;

echo $add(2, 3);  // 5

Особенности стрелочных функций

php
<?php
// 1. Автоматический захват переменных (без use)
$multiplier = 3;

$multiply = fn($n) => $n * $multiplier;  // use не нужен!

echo $multiply(5);  // 15

// 2. Только одно выражение (неявный return)
$double = fn($n) => $n * 2;

// ❌ Нельзя несколько строк
$broken = fn($n) => {
    $result = $n * 2;
    return $result;
};

// 3. Захват всегда по значению (не по ссылке)
$value = 10;
$getValue = fn() => $value;

$value = 20;
echo $getValue();  // 10 (не 20!)

Когда использовать

php
<?php
// ✅ Стрелочные функции — для коротких callback
$numbers = [1, 2, 3, 4, 5];

$doubled = array_map(fn($n) => $n * 2, $numbers);
$even = array_filter($numbers, fn($n) => $n % 2 === 0);
$sum = array_reduce($numbers, fn($carry, $n) => $carry + $n, 0);

// Сортировка
usort($users, fn($a, $b) => $a['age'] <=> $b['age']);

// ✅ Обычные анонимные — для сложной логики
$process = function($item) use ($config, $logger) {
    $logger->log("Processing: " . $item['id']);
    
    if (!$this->validate($item)) {
        return null;
    }
    
    return $this->transform($item, $config);
};

8. Функции высшего порядка

Функция, которая принимает функцию как аргумент или возвращает функцию:

array_map — применить функцию к каждому элементу

php
<?php
$numbers = [1, 2, 3, 4, 5];

// Удвоить каждый элемент
$doubled = array_map(fn($n) => $n * 2, $numbers);
// [2, 4, 6, 8, 10]

// Несколько массивов
$a = [1, 2, 3];
$b = [10, 20, 30];
$sum = array_map(fn($x, $y) => $x + $y, $a, $b);
// [11, 22, 33]

// Без callback — объединение
$result = array_map(null, $a, $b);
// [[1, 10], [2, 20], [3, 30]]

array_filter — оставить элементы, прошедшие проверку

php
<?php
$numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// Только чётные
$even = array_filter($numbers, fn($n) => $n % 2 === 0);
// [2, 4, 6, 8, 10]

// Без callback — убрать falsy значения
$array = [0, 1, '', 'hello', null, [], [1]];
$filtered = array_filter($array);
// [1, 'hello', [1]]

// Фильтр по ключу
$data = ['a' => 1, 'b' => 2, 'c' => 3];
$filtered = array_filter($data, fn($key) => $key !== 'b', ARRAY_FILTER_USE_KEY);
// ['a' => 1, 'c' => 3]

// Фильтр по ключу и значению
$filtered = array_filter($data, fn($val, $key) => $key !== 'b' && $val > 1, ARRAY_FILTER_USE_BOTH);
// ['c' => 3]

array_reduce — свернуть массив в одно значение

php
<?php
$numbers = [1, 2, 3, 4, 5];

// Сумма
$sum = array_reduce($numbers, fn($carry, $n) => $carry + $n, 0);
// 15

// Произведение
$product = array_reduce($numbers, fn($carry, $n) => $carry * $n, 1);
// 120

// Собрать в строку
$words = ['Hello', 'World', 'PHP'];
$sentence = array_reduce($words, fn($carry, $word) => "$carry $word", '');
// " Hello World PHP"

// Сгруппировать
$users = [
    ['name' => 'Иван', 'role' => 'admin'],
    ['name' => 'Мария', 'role' => 'user'],
    ['name' => 'Пётр', 'role' => 'admin'],
];

$grouped = array_reduce($users, function($carry, $user) {
    $carry[$user['role']][] = $user['name'];
    return $carry;
}, []);
// ['admin' => ['Иван', 'Пётр'], 'user' => ['Мария']]

usort — сортировка с callback

php
<?php
$users = [
    ['name' => 'Иван', 'age' => 25],
    ['name' => 'Мария', 'age' => 30],
    ['name' => 'Пётр', 'age' => 20],
];

// Сортировка по возрасту
usort($users, fn($a, $b) => $a['age'] <=> $b['age']);
// Пётр (20), Иван (25), Мария (30)

// По убыванию
usort($users, fn($a, $b) => $b['age'] <=> $a['age']);
// Мария (30), Иван (25), Пётр (20)

// По имени
usort($users, fn($a, $b) => strcmp($a['name'], $b['name']));
// Иван, Мария, Пётр

Создание своей функции высшего порядка

php
<?php
// Функция, возвращающая функцию
function multiplier(int $factor): callable {
    return fn($n) => $n * $factor;
}

$double = multiplier(2);
$triple = multiplier(3);

echo $double(5);  // 10
echo $triple(5);  // 15

// Функция, принимающая функцию
function applyTwice(callable $func, $value) {
    return $func($func($value));
}

echo applyTwice(fn($n) => $n + 1, 5);   // 7 (5+1+1)
echo applyTwice(fn($n) => $n * 2, 3);   // 12 (3*2*2)

9. Рекурсия

Что такое рекурсия?

Функция вызывает сама себя:

php
<?php
// Факториал: n! = n * (n-1) * (n-2) * ... * 1
function factorial(int $n): int {
    if ($n <= 1) {
        return 1;  // Базовый случай — остановка рекурсии
    }
    return $n * factorial($n - 1);  // Рекурсивный вызов
}

echo factorial(5);  // 120 (5 * 4 * 3 * 2 * 1)

// Как это работает:
// factorial(5)
// → 5 * factorial(4)
// → 5 * 4 * factorial(3)
// → 5 * 4 * 3 * factorial(2)
// → 5 * 4 * 3 * 2 * factorial(1)
// → 5 * 4 * 3 * 2 * 1
// → 120

Обязательные компоненты рекурсии

php
<?php
function recursiveFunction($input) {
    // 1. БАЗОВЫЙ СЛУЧАЙ — условие остановки
    if (условие_остановки) {
        return результат;
    }
    
    // 2. РЕКУРСИВНЫЙ СЛУЧАЙ — функция вызывает себя с другими аргументами
    return recursiveFunction(изменённый_input);
}

⚠️ Без базового случая — бесконечная рекурсия!

Примеры рекурсии

php
<?php
// Числа Фибоначчи
function fibonacci(int $n): int {
    if ($n <= 1) {
        return $n;
    }
    return fibonacci($n - 1) + fibonacci($n - 2);
}

echo fibonacci(10);  // 55

// Обход вложенного массива
function sumNestedArray(array $arr): int {
    $sum = 0;
    foreach ($arr as $item) {
        if (is_array($item)) {
            $sum += sumNestedArray($item);  // Рекурсия для вложенных
        } else {
            $sum += $item;
        }
    }
    return $sum;
}

$nested = [1, [2, 3], [4, [5, 6]]];
echo sumNestedArray($nested);  // 21

// Обход директории
function listFiles(string $dir): array {
    $files = [];
    foreach (scandir($dir) as $item) {
        if ($item === '.' || $item === '..') continue;
        
        $path = "$dir/$item";
        if (is_dir($path)) {
            $files = array_merge($files, listFiles($path));
        } else {
            $files[] = $path;
        }
    }
    return $files;
}

Когда использовать рекурсию?

✅ Хорошо для:
   • Древовидные структуры (DOM, файловая система)
   • Вложенные данные (JSON, XML)
   • Математические последовательности
   • Алгоритмы "разделяй и властвуй"

❌ Плохо для:
   • Простых циклов (используй for/foreach)
   • Глубокой рекурсии (> 100-1000 уровней — stack overflow)
   • Случаев, когда важна производительность

10. Встроенные функции PHP

PHP имеет огромное количество встроенных функций. Вот самые важные:

Строковые функции

php
<?php
$str = "  Hello World  ";

strlen($str);           // 15 (длина)
trim($str);             // "Hello World" (убрать пробелы)
strtolower($str);       // "  hello world  "
strtoupper($str);       // "  HELLO WORLD  "
ucfirst("hello");       // "Hello"
ucwords("hello world"); // "Hello World"

str_replace("World", "PHP", $str);  // "  Hello PHP  "
substr($str, 2, 5);                 // "Hello"
strpos($str, "World");              // 8 (позиция)
explode(" ", trim($str));           // ["Hello", "World"]
implode("-", ["a", "b", "c"]);      // "a-b-c"

sprintf("Имя: %s, Возраст: %d", "Иван", 25);  // Форматирование

Функции для массивов

php
<?php
$arr = [3, 1, 4, 1, 5, 9];

count($arr);                    // 6
in_array(4, $arr);              // true
array_search(4, $arr);          // 2 (индекс)
array_key_exists('a', $arr);    // false

array_push($arr, 2, 6);         // Добавить в конец
array_pop($arr);                // Удалить с конца
array_shift($arr);              // Удалить с начала
array_unshift($arr, 0);         // Добавить в начало

sort($arr);                     // Сортировка (изменяет массив)
rsort($arr);                    // Обратная сортировка
array_reverse($arr);            // Обратный порядок (новый массив)

array_merge([1, 2], [3, 4]);    // [1, 2, 3, 4]
array_unique([1, 1, 2, 2, 3]);  // [1, 2, 3]
array_slice($arr, 1, 3);        // Вырезать часть
array_splice($arr, 1, 2);       // Удалить часть

Математические функции

php
<?php
abs(-5);           // 5
round(3.7);        // 4
floor(3.7);        // 3
ceil(3.2);         // 4
max(1, 2, 3);      // 3
min(1, 2, 3);      // 1
pow(2, 8);         // 256
sqrt(16);          // 4
rand(1, 100);      // Случайное число

Функции для дат

php
<?php
time();                              // Текущий timestamp
date("Y-m-d H:i:s");                 // "2025-01-27 14:30:00"
date("d.m.Y", strtotime("+1 week")); // Через неделю
strtotime("2025-12-31");             // Timestamp из строки

Функции для работы с типами

php
<?php
gettype($var);       // Тип как строка
is_string($var);     // Проверка типа
is_array($var);
is_int($var);
is_null($var);
isset($var);         // Существует и не null?
empty($var);         // Пустой? (false, 0, "", null, [])

11. Практические примеры

Пример 1: Валидатор данных

php
<?php
function validateEmail(string $email): bool {
    return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}

function validatePhone(string $phone): bool {
    // Только цифры, 10-15 символов
    $digits = preg_replace('/\D/', '', $phone);
    return strlen($digits) >= 10 && strlen($digits) <= 15;
}

function validateAge(int $age): bool {
    return $age >= 0 && $age <= 150;
}

function validateUserData(array $data): array {
    $errors = [];
    
    if (empty($data['email']) || !validateEmail($data['email'])) {
        $errors['email'] = 'Некорректный email';
    }
    
    if (empty($data['phone']) || !validatePhone($data['phone'])) {
        $errors['phone'] = 'Некорректный телефон';
    }
    
    if (!isset($data['age']) || !validateAge($data['age'])) {
        $errors['age'] = 'Некорректный возраст';
    }
    
    return $errors;
}

// Использование
$data = [
    'email' => 'test@example.com',
    'phone' => '+7 (999) 123-45-67',
    'age' => 25,
];

$errors = validateUserData($data);
if (empty($errors)) {
    echo "Данные валидны!";
} else {
    foreach ($errors as $field => $message) {
        echo "$field: $message\n";
    }
}

Пример 2: Форматтер данных

php
<?php
function formatPrice(float $price, string $currency = '₽'): string {
    return number_format($price, 2, ',', ' ') . ' ' . $currency;
}

function formatPhone(string $phone): string {
    $digits = preg_replace('/\D/', '', $phone);
    if (strlen($digits) === 11 && $digits[0] === '7') {
        return sprintf(
            '+7 (%s) %s-%s-%s',
            substr($digits, 1, 3),
            substr($digits, 4, 3),
            substr($digits, 7, 2),
            substr($digits, 9, 2)
        );
    }
    return $phone;
}

function formatDate(string $date, string $format = 'd.m.Y'): string {
    $timestamp = strtotime($date);
    return $timestamp ? date($format, $timestamp) : $date;
}

// Использование
echo formatPrice(1234567.89);       // 1 234 567,89 ₽
echo formatPhone("79991234567");    // +7 (999) 123-45-67
echo formatDate("2025-01-27");      // 27.01.2025

Пример 3: Работа с массивами данных

php
<?php
function findById(array $items, int $id): ?array {
    foreach ($items as $item) {
        if ($item['id'] === $id) {
            return $item;
        }
    }
    return null;
}

function filterByField(array $items, string $field, mixed $value): array {
    return array_filter($items, fn($item) => $item[$field] === $value);
}

function sortByField(array $items, string $field, string $direction = 'asc'): array {
    usort($items, function($a, $b) use ($field, $direction) {
        $cmp = $a[$field] <=> $b[$field];
        return $direction === 'desc' ? -$cmp : $cmp;
    });
    return $items;
}

function pluck(array $items, string $field): array {
    return array_map(fn($item) => $item[$field], $items);
}

// Использование
$users = [
    ['id' => 1, 'name' => 'Иван', 'age' => 25, 'active' => true],
    ['id' => 2, 'name' => 'Мария', 'age' => 30, 'active' => false],
    ['id' => 3, 'name' => 'Пётр', 'age' => 28, 'active' => true],
];

$user = findById($users, 2);                    // Мария
$active = filterByField($users, 'active', true); // Иван, Пётр
$sorted = sortByField($users, 'age', 'desc');    // Мария, Пётр, Иван
$names = pluck($users, 'name');                  // ['Иван', 'Мария', 'Пётр']

12. Упражнения

Упражнение 1: Базовые функции (15 минут)

Напиши функции:

php
<?php
// 1. Функция, которая возвращает приветствие
function greet(string $name, string $greeting = "Привет"): string {
    // Твой код
}
echo greet("Иван");           // Привет, Иван!
echo greet("Мария", "Здравствуй");  // Здравствуй, Мария!

// 2. Функция проверки чётности
function isEven(int $n): bool {
    // Твой код
}
var_dump(isEven(4));  // true
var_dump(isEven(7));  // false

// 3. Функция вычисления площади круга
function circleArea(float $radius): float {
    // Твой код (π * r²)
}
echo circleArea(5);  // 78.54...

Упражнение 2: Работа с массивами (20 минут)

php
<?php
$products = [
    ['name' => 'Яблоки', 'price' => 120, 'quantity' => 5],
    ['name' => 'Бананы', 'price' => 80, 'quantity' => 3],
    ['name' => 'Молоко', 'price' => 60, 'quantity' => 2],
    ['name' => 'Хлеб', 'price' => 40, 'quantity' => 1],
];

// 1. Напиши функцию calculateTotal — сумма (price * quantity) всех товаров
// 2. Напиши функцию findExpensive($products, $minPrice) — товары дороже minPrice
// 3. Напиши функцию formatCart($products) — вернуть строку вида:
//    "Яблоки x5 = 600₽, Бананы x3 = 240₽, ..."

Упражнение 3: Рекурсия (20 минут)

php
<?php
// 1. Напиши функцию sumDigits — сумма цифр числа
function sumDigits(int $n): int {
    // 123 → 1 + 2 + 3 = 6
    // Подсказка: $n % 10 — последняя цифра, $n / 10 — остальные
}

// 2. Напиши функцию isPalindrome — проверка палиндрома
function isPalindrome(string $str): bool {
    // "radar" → true, "hello" → false
}

// 3. Напиши функцию flatten — превратить вложенный массив в плоский
function flatten(array $arr): array {
    // [1, [2, 3], [4, [5, 6]]] → [1, 2, 3, 4, 5, 6]
}

Упражнение 4: Функции высшего порядка (25 минут)

php
<?php
$transactions = [
    ['type' => 'income', 'amount' => 5000, 'category' => 'salary'],
    ['type' => 'expense', 'amount' => 1500, 'category' => 'food'],
    ['type' => 'expense', 'amount' => 3000, 'category' => 'rent'],
    ['type' => 'income', 'amount' => 1000, 'category' => 'freelance'],
    ['type' => 'expense', 'amount' => 500, 'category' => 'food'],
];

// Используя array_filter, array_map, array_reduce:
// 1. Получи все расходы (expense)
// 2. Получи сумму всех доходов
// 3. Получи суммы по категориям: ['food' => 2000, 'rent' => 3000, ...]
// 4. Получи баланс (доходы - расходы)

13. Вопросы для самопроверки

  1. Чем отличается параметр от аргумента?

  2. Что произойдёт, если не указать return в функции?

  3. Как передать переменную в функцию по ссылке?

  4. Что делает оператор ... (splat)?

  5. Почему использование global — плохая практика?

  6. Чем отличается static переменная от обычной?

  7. Как захватить внешнюю переменную в анонимной функции?

  8. В чём разница между fn() => и function() {}?


14. Частые ошибки

Ошибка 1: Изменение параметра по умолчанию

php
<?php
// ❌ Мутация дефолтного массива
function addItem($item, $list = []) {
    $list[] = $item;
    return $list;
}

// Работает корректно в PHP, но плохой паттерн
// В других языках это вызывает баги

// ✅ Лучше
function addItem($item, $list = null) {
    $list = $list ?? [];
    $list[] = $item;
    return $list;
}

Ошибка 2: Забыт return

php
<?php
// ❌ Забыли return
function add($a, $b) {
    $result = $a + $b;
    // return забыт!
}

$sum = add(2, 3);
echo $sum;  // Ничего (null)

// ✅ Не забывай return
function add($a, $b) {
    return $a + $b;
}

Ошибка 3: Ссылка в foreach без unset

php
<?php
// ❌ Классическая ошибка
function doubleAll(&$arr) {
    foreach ($arr as &$val) {
        $val *= 2;
    }
    // Забыли unset($val)!
}

// ✅ Всегда unset после foreach по ссылке
function doubleAll(&$arr) {
    foreach ($arr as &$val) {
        $val *= 2;
    }
    unset($val);
}

Ошибка 4: Бесконечная рекурсия

php
<?php
// ❌ Нет базового случая
function countdown($n) {
    echo $n;
    countdown($n - 1);  // Никогда не остановится!
}

// ✅ Есть базовый случай
function countdown($n) {
    if ($n < 0) return;  // Базовый случай
    echo $n;
    countdown($n - 1);
}

Резюме главы

┌────────────────────────────────────────────────────────────────┐
│                      ЗАПОМНИ ГЛАВНОЕ                           │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│  СОЗДАНИЕ ФУНКЦИЙ                                              │
│  • function имя(параметры) { return результат; }              │
│  • Одна функция — одна задача                                 │
│  • Описательные имена: calculateTotal, validateEmail          │
│                                                                │
│  ПАРАМЕТРЫ                                                     │
│  • По умолчанию передаются по значению (копия)                │
│  • &$param — передача по ссылке (изменяет оригинал)           │
│  • ...$args — variadic (переменное число аргументов)          │
│  • name: value — именованные аргументы (PHP 8+)               │
│                                                                │
│  ВОЗВРАТ                                                       │
│  • return завершает функцию и возвращает значение             │
│  • Без return функция возвращает null                         │
│  • Ранний выход — проверяй ошибки в начале                    │
│                                                                │
│  ОБЛАСТЬ ВИДИМОСТИ                                             │
│  • Локальные переменные видны только внутри функции           │
│  • Избегай global — передавай через параметры                 │
│  • static — сохраняет значение между вызовами                 │
│                                                                │
│  АНОНИМНЫЕ ФУНКЦИИ                                             │
│  • $fn = function() {} — сохраняется в переменную             │
│  • use ($var) — захват внешней переменной                     │
│  • fn() => — стрелочная функция (PHP 7.4+)                    │
│                                                                │
│  ФУНКЦИИ ВЫСШЕГО ПОРЯДКА                                       │
│  • array_map — применить к каждому                            │
│  • array_filter — отфильтровать                               │
│  • array_reduce — свернуть в одно значение                    │
│                                                                │
└────────────────────────────────────────────────────────────────┘

Следующая глава: Глава 1.4: Массивы глубоко — индексированные, ассоциативные, многомерные, функции для работы с массивами

Выпущено под лицензией MIT