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 пока его не поддерживают.
Php именованные параметры функции
To experiment on performance of pass-by-reference and pass-by-value, I used this script. Conclusions are below.
#!/usr/bin/php
function sum ( $array , $max ) < //For Reference, use: "&$array"
$sum = 0 ;
for ( $i = 0 ; $i < 2 ; $i ++)#$array[$i]++; //Uncomment this line to modify the array within the function.
$sum += $array [ $i ];
>
return ( $sum );
>
$max = 1E7 //10 M data points.
$data = range ( 0 , $max , 1 );
$start = microtime ( true );
for ( $x = 0 ; $x < 100 ; $x ++)$sum = sum ( $data , $max );
>
$end = microtime ( true );
echo «Time: » .( $end — $start ). » s\n» ;
/* Run times:
# PASS BY MODIFIED? Time
— ——- ——— —-
1 value no 56 us
2 reference no 58 us
3 valuue yes 129 s
4 reference yes 66 us
1. PHP is already smart about zero-copy / copy-on-write. A function call does NOT copy the data unless it needs to; the data is
only copied on write. That’s why #1 and #2 take similar times, whereas #3 takes 2 million times longer than #4.
[You never need to use &$array to ask the compiler to do a zero-copy optimisation; it can work that out for itself.]
2. You do use &$array to tell the compiler «it is OK for the function to over-write my argument in place, I don’t need the original
any more.» This can make a huge difference to performance when we have large amounts of memory to copy.
(This is the only way it is done in C, arrays are always passed as pointers)
3. The other use of & is as a way to specify where data should be *returned*. (e.g. as used by exec() ).
(This is a C-like way of passing pointers for outputs, whereas PHP functions normally return complex types, or multiple answers
in an array)
5. Sometimes, pass by reference could be at the choice of the caller, NOT the function definitition. PHP doesn’t allow it, but it
would be meaningful for the caller to decide to pass data in as a reference. i.e. «I’m done with the variable, it’s OK to stomp
on it in memory».
*/
?>
Простая реализация именованных параметров в PHP
Именованные параметры функций могут быть удобны в случае, когда на вход принимается много значений, а многие вызовы опускают часть аргументов. Становится тяжело уследить за порядком аргументов. Кроме того, появляется следующая проблема. Представим, что у нас есть функция
function someFunction($a = 1, $b = 2)
В одном месте кода мы хотим вызвать ее, определив значение параметра $a , оставив $b по умолчанию:
Пока всё хорошо. Но, если мы захотим где-то еще вызвать ее с определенным значением параметра $b , сохранив значение $a по умолчанию, нам придется сделать так:
Это нехорошо, правда? Во-первых, непонятно, что это за магическое число такое, и вообще читаемость затрудняется. Во-вторых, копировать уже существующий код — плохо. Например, придется при каждом изменении дефолтного значения вручную менять их во всём коде. Еще одна проблема возникнет, если я добавлю третий параметр и захочу изменить самый вызов someFunction($expr); : мне придётся написать что-то вроде someFunction($expr, 2, $val); .
Наиболее простое и гибкое в реализации решение, которое я и предлагаю, — использовать ассоциативные массивы с текстовыми ключами и функцию array_merge (в случае с целочисленными ключами функция сломается):
function someFunction($args = []) < $defaultArgs = [ 'a' =>1, 'b' => 2, ]; $args = array_merge($defaultArgs, $args); . > . // вызов со всеми параметрами по умолчанию someFunction(); // вызов с определенным значением параметра b и остальными значениями по умолчанию. Нам неважно, каковы они и сколько их someFunction(['b' => 3]);
- php
- функции
- аргументы
- параметры
- аргументы по умолчанию
- параметры по умолчанию
- аргументы функции по умолчанию
- параметры функции по умолчанию
- именованные параметры
- именованные аргументы
- массивы в php