PHP 8: Именованные аргументы

PHP 8: Именованные аргументы

или именованные параметры

Именованные аргументы, также называемые именованными параметрами, получат поддержку в PHP 8. В этом посте я расскажу об их плюсах и минусах.

Но позвольте сначала спросить бывали ли вы когда-нибудь в ситуации, когда вы видели функцию и ее параметры и задавались вопросом, что это за параметры? Я почти уверен, что это было и не раз, например, возьмем следующее:


array_slice($array, $offset, $length, true);

Первые три параметра, переданные в array_slice , кажутся очевидными из-за информативных имен переменных, но как насчет четвертого параметра? Это просто true. Но что здесь означает это true? Что ж, чтобы это узнать, вам нужно перейти к определению функции или обратиться к документации. В случае array_sliceс определением будет:


function array_slice(
array $array,
$offset,
$length = null,
$preserve_keys = false
) { }

Итак, четвертый параметр - $preserve_keys это то, что вы можете определить, только взглянув на определение. И больше у вас ничего нет, что может подсказать, просто взглянув на объявление функции.

А что если мы перепишем данный пример вот так:


array_slice($array, $offset, $length, preserve_keys: true);

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


setcookie(
name: 'week',
expires: time() + 60 * 60 * 24 * 14,
);

А это, DTO, использующий продвигаемые свойства , а также именованные аргументы:


class CustomerData
{
public function __construct(
public string $name,
public string $email,
public int $age,
) {}
}

$data = new CustomerData(
name: $input['name'],
email: $input['email'],
age: $input['age'],
);

Именованные аргументы также поддерживают распаковку массива:


$data = new CustomerData(...$customerRequest->validated());

Как вы уже догадались, именованные аргументы позволяют передавать входные данные в функцию на основе имени аргумента, а не его порядка. 

RFC был первоначально предложен Никитой Поповым, который работает над редактором PHP в компании по разработке инструментов JetBrains. Как он выразился в недавнем подкасте: «Если у вас есть метод, например, с тремя логическими аргументами, это действительно выглядит ужасно, потому что вы называете его так: «true »,«true»,«false», например, что это означает? Если вы указали параметры и у вас есть те же три логических аргумента, тогда это больше не проблема».

Почему именованные аргументы?

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

Во-первых, именованные аргументы позволяют пропускать значения по умолчанию. Взглянем еще раз на пример c созданием cookie:


setcookie(
name: 'week',
expires: time() + 60 * 60 * 24 *14,
);

Сигнатура его метода на самом деле достаточно большая и выглядит так:


setcookie ( 
string $name,
string $value = "",
int $expires = 0,
string $path = "",
string $domain = "",
bool $secure = false,
bool $httponly = false,
) : bool

В показанном мной примере нам не нужно было устанавливать значение $value, но нам обязательно нужно установить время истечения куки. Именованные аргументы сделали вызов этого метода более лаконичным, итак вот это:


setcookie(
'week',
'',
time() + 60 * 60 * 24 *14,
);

против


setcookie(
name: 'week',
expires: time() + 60 * 60 * 24 *14,
);

Помимо пропуска аргументов со значениями по умолчанию, есть также преимущество ясности в отношении того, какая переменная что делает; то, что особенно полезно в функциях с большими сигнатурами. Теперь можно сказать, что множество аргументов - это плохой стиль кода; но нам по-прежнему приходится иметь с этим дело, несмотря ни на что, поэтому лучше иметь разумный способ сделать это, чем вообще ничего.

Подробнее об именованных аргументов

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

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

Посмотрим на наш предыдущий пример DTO:


class CustomerData
{
public function __construct(
public string $name,
public string $email,
public int $age,
) {}
}

Вы можете построить его так:


$data = new CustomerData(
$input['name'],
age: $input['age'],
email: $input['email'],
);

Однако наличие упорядоченного аргумента после именованного приведет к ошибке:


$data = new CustomerData(
age: $input['age'],
$input['name'],
email: $input['email'],
);

Затем можно использовать распакову массива в сочетании с именованными аргументами:


$input = [
'age' => 33,
'name' => 'Sergey',
'email' => 'sinbadxiii@gmail.com',
];

$data = new CustomerData(...$input);

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


$input = [
'age' => 33,
'name' => 'Sergey',
'email' => 'sinbadxiii@gmail.com',
'unknownProperty' => 'This is not allowed',
];

$data = new CustomerData(...$input);

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


$input = [
'Sergey',
'age' => 33,
'email' => 'sinbadxiii@gmail.com',
];

$data = new CustomerData(...$input);

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


class CustomerData
{
public static function new(...$args): self
{
return new self(...$args);
}

public function __construct(
public string $name,
public string $email,
public int $age,
) {}
}

$data = CustomerData::new(
email: 'sinbadxiii@gmail.com',
age: 33,
name: 'Sergey',
);

В этом случае массив $args будет содержать следующие данные: CustomerData::new


[
'age' => 33,
'email' => 'sinbadxiii@gmail.com',
'name' => 'Sergey',
]

Атрибуты, также известные как аннотации, тоже поддерживают именованные аргументы:


class ProductSubscriber
{
@@ListensTo(event: ProductCreated::class)
public function onProductCreated(ProductCreated $event) { /* … */ }
}

Невозможно использовать переменную в качестве имени аргумента:


$field = 'age';

$data = CustomerData::new(
$field: 33,
);

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


interface EventListener {
public function on($event, $handler);
}

class MyListener implements EventListener
{
public function on($myEvent, $myHandler)
{
// …
}
}

PHP автоматически разрешит изменение имени $event на $myEvent и $handler на $myHandler; но если вы решите использовать именованные аргументы, используя имя родителя, это приведет к ошибке выполнения:


public function register(EventListener $listener)
{
$listener->on(
event: $this->event,
handler: $this->handler,
);
}

Этот прагматический подход был выбран для предотвращения серьезных критических изменений, когда все унаследованные аргументы должны были бы иметь одно и то же имя. Мне кажется, это хорошее решение.

Ну что же, PHP присоединился к длинному списку языков, в которых уже есть эта функция, включая C#, Kotlin, PowerShell, Python, Ruby, Swift и Visual Basic. Однако те же Java и JavaScript пока его не поддерживают.


Сергей Мухин

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

Есть вопросы?

Я почти всегда в режиме онлайн

Связаться со мной