Представляємо Psalm 5

November 30, 2022 by Команда підтримки Psalm - 1 minute read

Ця стаття англійською, французською та італійською.


Хто хоч раз не хотів би повернутися в минуле, чи задля того, щоб виправити якусь історичну помилку, або сказати близькій людині, як багато вона для нас значила, чи може виправити незначне архітектурне рішення в інструменті статичного аналізу PHP?

На жаль, машини часу ще не винайдено, але мажорні версії є. Найбільша зміна у Psalm 5, яка стосується користувачів — це відносно незначне виправлення: шейпи (array shapes) тепер вважаються запечатаними (sealed) за замовчуванням.

Відмінності запечатаних шейпів від незапечатаних

Якщо ви раніше використовували шейпи, вам має бути знайомий базовий синтаксис:

<?php
/**
 * @param array{id: string, name: string} $user
 * @return array{id: string, name: string}
 */
function takeUserData(array $user): array {
  return $user;
}

У наведеному вище прикладі takesUserData приймає тільки масив із рівно двома елементами, id і name. З докблоку функції також бачимо, що вона повертає масив рівно з двома елементами.

Psalm також дозволяє передавати шейп array{id: string, name: string} у будь-яку функцію, яка очікує array<string>. Це має сенс — масив містить лише елементи типу string, тому передати його у функцію (наприклад, implode()), яка очікує масив рядків, не має викликати жодних проблем.

Але що, як ми змінимо нашу функцію, щоб додати інший елемент у коді takesUserData?

<?php
/**
 * @param array{id: string, name: string} $user
 * @return array{id: string, name: string}
 */
function takeUserData(array $user): array {
  $user['extra_data'] = new stdClass();
  return $user;
}

Тепер Psalm скаржиться, що ми не повертаємо те, що ми обіцяли — це змінилося в порівнянні з попередніми версіями Psalm, які дозволяли таку поведінку.

Попередня (неправильна поведінка) означала, що ми могли зробити щось на кшталт implode('', takeUserData($foo)) і Psalm би цього не помітив. Це може призвести до коду, який не буде працювати.

Коли засіб перевірки типів допускає поведінку, яка призводить до проблем під час виконання, ми називаємо цей засіб перевірки типів ненадійним. Є кілька наріжних випадків у PHP, де ненадійної перевірки типів не уникнути, але Psalm намагається уникати цього, де це можливо. Ми вирішили дещо змінити поведінку Psalm таким чином, щоб, як ми сподіваємося, завдасть мінімум болі користувачам Psalm.

Примітка від Метта Брауна, автора Psalm:

Це все моя вина. Я придумав конвенцію array{id: string, name: string}, але не визначив всю семантику.

На момент написання цієї статті інші інструменти статичного аналізу PHP (Phan , PHPStan) дозволяють таку поведінку, і ми сподіваємось, що з часом вони також приймуть конвенцію ... і усунуть неправильне поводження з шейпами.

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

<?php
/**
 * @param array{id: string, name: string, ...} $user
 * @return array{id: string, name: string, ...}
 */
function takeUserData(array $user): array {
  $user['extra_data'] = new stdClass();
  return $user;
}

Це збігається з тим, як ... використовується у Hack.

Psalm запобігатиме використанню результату цієї функції takesUserData (з незапечатаним шейпом) у викликах implode:

<?php
/**
 * @param array{id: string, name: string, ...} $user
 * @return array{id: string, name: string, ...}
 */
function takeUserData(array $user): array {
  $user['extra_data'] = new stdClass();
  return $user;
}
$foo = ['id' => 'DP42', 'name' => 'Дуглас Адамс'];
echo implode('', takeUserData($foo));

Що це означає для вас? Мабуть нічого! У коді Psalm використовується багато шейпів, і лише один із них допускає додаткові поля. Ми сподіваємось, що (негативний) вплив цього оновлення буде дуже незначним.

Що ще є у Psalm 5?

Ми додали довгоочікувану підтримку перетину типів та інших нових функцій PHP 8.

Psalm 5 також додає кілька нових типів:

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

Під капотом ми внесли деякі значні зміни у внутрішні елементи Psalm. Вся система типів тепер незмінна (immutable), що виправляє цілий клас проблем з багатопоточністю і підвищує швидкість роботи Psalm на 15-20% як в однопоточному, так і в багатопоточному режимі (переважно за рахунок зменшення використання __clone).

Ми також припинили підтримку застарілого API плагінів (введеного в Psalm 3), оскільки новий існує вже пару років.

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

У найближчі місяці ми працюватимемо над повною підтримкою PHP 8.2 і над багато чим іншим!