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 пока его не поддерживают.
Что думаешь?