Іменовані аргументи в PHP 8

Іменовані аргументи в PHP 8
7 хв. читання

Іменовані аргументи або іменовані параметри, як їх часто називають, підтримуються в PHP 8. В цій статті розглянемо їх докладніше, але спочатку дозвольте показати як вони виглядають на декількох прикладах.

Іменовані аргументи використовуються у вбудованій PHP функції

setcookie(
    name: 'test',
    expires: time() + 60 * 60 * 2,
);

Використання іменованих аргументів і Constructor Promoted Properties в класі 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'],
);

Іменовані аргументи також підтримують поширення масиву (array spreading)

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

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

Вам напевно цікаво щодо деталей: що станеться якщо буде передане неправильне ім'я аргументу, як використовувати синтаксис з поширенням масиву? Ми розглянемо ці приклади в деталях.

Навіщо потрібні іменовані аргументи?

Скажімо так, цей функціонал багато обговорювали, були деякі аргументи проти. Однак їх користь значно перевершує страх проблем зі зворотною сумісністю, або роздутими API. Як я це бачу, вони дозволять нам писати прозоріший та гнучкіший код.

Наприклад, іменовані аргументи дозволяють вам пропускати значення за замовчуванням. Розглянемо приклад встановлення cookes повторно:

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

В показаному прикладі нам не потрібно передавати значення $value, але потрібно встановити терміт придатності $expires. Іменовані аргументи дозволяють викликати цю функцію трохи лаконічніше.

Приклад виклику функії setcookie без використання іменованих аргументів:

setcookie(
    'test',
    '',
    time() + 60 * 60 * 2,
);

Крім пропуску аргументів зі значеннями за замовчуванням ми також отримуємо кращу прозорість відносно переданих аргументів, що буде надзвичайно корисним при викликах функцій з великою кількістю аргументів. Звичайно, можна сказати що велика кількість аргументів означає що код не дуже якісний, але нам все одно доводиться мати з ними справу, так ще краще мати кращий спосіб це робити ніж не мати взагалі.

Докладніше про іменовані аргументи

Основи використання іменованих аргументів ми вже розглянули, тепер вивчимо докладніше як їх можна використовувати, а як ні.

По перше, іменовані аргументи можна комбінувати з не іменованими, які також називаються позиційними аргументами. В цьому випадку позиційні аргументи повинні йти першими.

Для прикладу візьмемо наш клас 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' => 'Joe',
    'email' => 'joe@example.com',
];

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

Однак, якщо в масиві відсутній обов'язковий аргумент, або присутній ключ якого немає серед іменованих аргументів ви отримаєте помилку:

$input = [
    'age' => 25,
    'name' => 'Joe',
    'email' => 'joe@example.com',
    'unknownProperty' => 'This is not allowed',
];

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

Також є можливість комбінувати іменовані та позиційні аргументи, але при умові що позиційні аргументи будуть йти першими, позиційні аргументи мають йти першими.

$input = [
    'Joe',
    'age' => 25,
    'email' => 'joe@example.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: 'joe@example.com',
    age: 25,
    name: 'Joe',
);

В цьому випадку $args в функції CustomerData::new отримає наступні дані:

[
    'age' => 25,
    'email' => 'joe@example.com',
    'name' => 'Joe',
]

Атрибути - також відомі як анотації, також підтримують іменовані аргументи:

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, але якщо ви спробуєте передавати іменовані аргументи використовуючи імена батьківського класу, ви отримаєте помилку:

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

Цей прагматичний підхід був вибраний для запобігання серйозних проблем коли всі успадковані аргументи будуть змушені використовувати одні й ті самі імена батьківського класу. Як на мене, це гарне рішення. 

Це майже все що потрібно знати про іменовані аргументи. Якщо ви хочете знати більше про те що змусило вибрати саме ці архітектурні рішення, рекомендую почитати RFC.

Джерело ENG: stitcher.io

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація