Урок 05

Named arguments и spread

Named arguments

PHP 8.0+ — передача аргументов по имени:

function createUser(string $name, int $age, bool $admin = false): User
{
    // ...
}

// Позиционно
createUser('Иван', 25, true);

// По имени
createUser(name: 'Иван', age: 25, admin: true);

Зачем

Читаемость:

// Что это за true? Непонятно
createUser('Иван', 25, true);

// Понятно
createUser(name: 'Иван', age: 25, admin: true);

Пропуск необязательных:

createUser(name: 'Иван', age: 25);   // admin по умолчанию

Порядок не важен:

createUser(age: 25, name: 'Иван');

Смешивание

Можно смешивать позиционные и именованные:

createUser('Иван', age: 25, admin: true);

Правило: позиционные до именованных.

Ошибка:

createUser(name: 'Иван', 25, admin: true);   // нельзя

Работа с внутренними функциями

Встроенные функции тоже поддерживают:

htmlspecialchars($text, double_encode: false);

array_slice($arr, offset: 1, length: 2);

str_replace(search: 'a', replace: 'b', subject: $text);

Полезно для функций с длинным списком параметров.

С опциональными

function formatDate(
    string $date,
    string $format = 'Y-m-d',
    ?string $timezone = null,
): string {
    // ...
}

formatDate('2026-10-15', timezone: 'Europe/Moscow');
// format остаётся по умолчанию

С variadic

function sum(int ...$numbers): int
{
    return array_sum($numbers);
}

// Named args не применимы к variadic

Нельзя sum(numbers: 1, 2, 3).

Spread

Распаковка массива:

$args = [1, 2, 3];
sum(...$args);

PHP 8.1+ — именованный spread:

$args = ['a' => 1, 'b' => 2];
sum(...$args);
// эквивалентно sum(a: 1, b: 2)

Смешанный:

$args = [10, 'b' => 20];
sum(...$args);   // sum(10, b: 20)

Практический пример

Конфиг с defaults:

function connect(
    string $host,
    int $port = 5432,
    string $database = 'postgres',
    string $user = 'postgres',
    string $password = '',
    array $options = [],
): PDO {
    // ...
}

// Хочу только host и password
connect(
    host: 'localhost',
    password: 'secret',
);

Без named args:

connect('localhost', 5432, 'postgres', 'postgres', 'secret');
// или через массив

С массивами

array_merge:

$base = ['a' => 1, 'b' => 2];
$override = ['b' => 3, 'c' => 4];

$merged = [...$base, ...$override];
// ['a' => 1, 'b' => 3, 'c' => 4]

С числовыми ключами:

$a = [1, 2];
$b = [3, 4];

$c = [...$a, ...$b];   // [1, 2, 3, 4]

PHP 8.1+ — со строковыми ключами тоже:

$a = ['x' => 1];
$b = ['y' => 2];

$c = [...$a, ...$b];   // ['x' => 1, 'y' => 2]

До 8.1 — строковые ключи не поддерживались.

Spread в аргументах

function log(string $level, string ...$messages): void { }

$messages = ['msg1', 'msg2', 'msg3'];
log('ERROR', ...$messages);

Named args и рефакторинг

⚠️ Осторожно:

function createUser(string $name, int $age): void { }

// В коде
createUser(name: 'Иван', age: 25);

// Переименовали параметр
function createUser(string $fullName, int $age): void { }

// Код сломался: Unknown named parameter $name

Правило: публичные API — стабильные имена параметров.

PHPDoc @no-named-arguments — пометить функцию, где named args не гарантируются:

/**
 * @no-named-arguments
 */
function f($a, $b) { }

Named args в классах

В конструктор:

class User
{
    public function __construct(
        public string $name,
        public int $age,
        public bool $admin = false,
    ) {}
}

$user = new User(
    name: 'Иван',
    age: 25,
    admin: true,
);

Очень удобно для DTO и value objects.

Позиция по умолчанию

Порядок не важен, если все переданы по имени:

$user = new User(
    admin: true,
    age: 25,
    name: 'Иван',
);

Динамические ключи

Можно через переменную:

$argName = 'age';
createUser(name: 'Иван', ...[$argName => 25]);

Используется редко.

Отладка

Ошибка при опечатке:

createUser(nam: 'Иван', age: 25);
// Error: Unknown named parameter $nam

Хорошо — ловится сразу.

Сравнение с массивами-опциями

Старый стиль (массив):

function createUser(array $options): void
{
    $name = $options['name'] ?? '';
    $age = $options['age'] ?? 0;
    // ...
}

createUser(['name' => 'Иван', 'age' => 25]);

Минусы:

  • Нет типизации внутри.
  • Опечатки в ключах не ловятся.
  • Нужны проверки.

Named args:

  • Типы проверяются.
  • Опечатки ловятся.
  • IDE подсказывает.

Вывод: для 3+ параметров — named args.

Практика

<?php

declare(strict_types=1);

final class Email
{
    public function __construct(
        public readonly string $to,
        public readonly string $subject,
        public readonly string $body,
        public readonly array $cc = [],
        public readonly array $bcc = [],
        public readonly bool $html = false,
    ) {}
}

$email = new Email(
    to: 'user@example.com',
    subject: 'Привет',
    body: '<h1>Hello</h1>',
    html: true,
    cc: ['manager@example.com'],
);

function send(Email ...$emails): void
{
    foreach ($emails as $email) {
        echo "Отправка на {$email->to}\n";
    }
}

$emails = [
    new Email(to: 'a@ex.com', subject: 'A', body: 'A body'),
    new Email(to: 'b@ex.com', subject: 'B', body: 'B body'),
];

send(...$emails);

Итог

  • Named args — по имени, читаемо, порядок не важен.
  • Позиционные до именованных.
  • Опциональные можно пропускать.
  • Spread — ...$arr в вызовах и массивах.
  • PHP 8.1+ — именованный spread.
  • Переименование параметров ломает код — будь аккуратен.
  • Для 3+ параметров — почти всегда лучше.

Дальше — nullsafe operator.