Это было на грани, но именованные аргументы — их также называют именованными параметрами — теперь поддерживаются в PHP 8!
В этом посте я подробно разберу их особенности, но сначала давайте посмотрим, как они выглядят на реальных примерах:
setcookie(
name: 'test',
expires: time() + 60 * 60 * 2,
);Именованные аргументы во встроенной функции PHP
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'],
);DTO, использующий продвижение свойств в конструкторе и именованные аргументы
$data = new CustomerData(...$customerRequest->validated());
Именованные аргументы также поддерживают распаковку массивов
Как вы уже могли догадаться по примерам, именованные аргументы позволяют передавать входные данные в функцию на основе имени аргумента, а не его порядка.
Я считаю, что именованные аргументы — отличная фича, которая сильно повлияет на мою повседневную разработку. Но вас, скорее всего, интересуют детали: что если передать неверное имя, и как работает синтаксис распаковки массива? Что ж, давайте подробно рассмотрим все эти вопросы.
Зачем нужны именованные аргументы?
Скажем так: эта фича вызвала много споров, и были аргументы против её добавления. Однако, на мой взгляд, их польза намного перевешивает страх проблем с обратной совместимостью или раздувания API. По-моему, они позволят нам писать более чистый и гибкий код.
Во-первых, именованные аргументы позволяют пропускать значения по умолчанию. Взгляните еще раз на пример с cookie:
setcookie(
name: 'test',
expires: time() + 60 * 60 * 2,
);Сигнатура этого метода на самом деле выглядит так:
setcookie (
string $name,
string $value = "",
int $expires = 0,
string $path = "",
string $domain = "",
bool $secure = false,
bool $httponly = false,
) : boolВ показанном примере нам не нужно было устанавливать cookie $value , но потребовалось задать время истечения. Именованные аргументы сделали вызов этого метода более лаконичным:
setcookie(
'test',
'',
time() + 60 * 60 * 2,
);setcookie без именованных аргументов
setcookie(
name: 'test',
expires: time() + 60 * 60 * 2,
);setcookie с именованными аргументами
Помимо пропуска аргументов со значениями по умолчанию, есть и преимущество в наглядности: сразу понятно, какая переменная за что отвечает. Это особенно полезно в функция с длинными сигнатурами методов. Можно сказать, что большое количество аргументов — это «запах кода» (code smell), но с ними всё равно приходится работать, так что лучше иметь адекватный способ делать это, чем не иметь никакого.
Именованные аргументы подробнее
Разобравшись с основами, давайте посмотрим, что именованные аргументы могут, а чего не могут.
Прежде всего, именованные аргументы можно комбинировать с безымянными — также называемыми позиционными — аргументами. В этом случае позиционные аргументы всегда должны идти первыми.
Возьмем наш предыдущий пример с 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'],
);Далее, с именованными аргументами можно использовать распаковку массива (array spreading):
$input = [
'age' => 25,
'name' => 'Brent',
'email' => 'brent@stitcher.io',
];
$data = new CustomerData(...$input);Однако, если в массиве отсутствуют обязательные элементы или есть ключ, который не указан как именованный аргумент, будет выброшена ошибка:
$input = [
'age' => 25,
'name' => 'Brent',
'email' => 'brent@stitcher.io',
'unknownProperty' => 'This is not allowed',
];
$data = new CustomerData(...$input);Входной массив можно комбинировать с именованными и позиционными аргументами, но только если позиционные аргументы следуют тому же правилу, что и раньше: они должны идти первыми!
$input = [
'Brent',
'age' => 25,
'email' => 'brent@stitcher.io',
];
$data = new CustomerData(...$input);Посмотрите мое последнее видео: DX matters
Если вы используете variadic-функции, именованные аргументы будут переданы со своим именем ключа в массив variadic-аргументов. Рассмотрим следующий пример:
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: 'brent@stitcher.io',
age: 25,
name: 'Brent',
);В этом случае $args в CustomerData::new будет содержать следующие данные:
[
'age' => 25,
'email' => 'brent@stitcher.io',
'name' => 'Brent',
]Атрибуты — также известные как аннотации — тоже поддерживают именованные аргументы:
class ProductSubscriber
{
#[ListensTo(event: ProductCreated::class)]
public function onProductCreated(ProductCreated $event) { /* … */ }
}Использовать переменную в качестве имени аргумента нельзя:
$field = 'age';
$data = CustomerData::new(
$field: 25,
);И наконец, именованные аргументы прагматично справляются с изменением имен при наследовании. Рассмотрим пример:
interface EventListener {
public function on($event, $handler);
}
class MyListener implements EventListener
{
public function on($myEvent, $myHandler)
{
// …
}
}PHP без лишних сообщений разрешит изменить имя $event на $myEvent , а $handler на $myHandler ; но если вы решите использовать именованные аргументы, используя имя из родительского класса, это приведет к ошибке во время выполнения (runtime error):
public function register(EventListener $listener)
{
$listener->on(
event: $this->event,
handler: $this->handler,
);
}Ошибка во время выполнения, если $listener является экземпляром MyListener
Этот прагматичный подход был выбран для предотвращения существенных ломающих изменений, при которых все унаследованные аргументы должны были бы сохранять одно и то же имя. Мне кажется, это хорошее решение.
Вот и всё основное, что можно рассказать об именованных аргументах. Если вы хотите узнать немного больше о предыстории некоторых архитектурных решений, советую прочитать RFC.
Комментарии (0)
Пока нет комментариев — будьте первым.