Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Middle: питання на співбесіді з теми «Масиви й рядки»

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

4 питання

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

  • $a + $b - бере все з $a, а з $b додає лише ключі, яких у $a немає. Перемагає лівий.
  • array_merge($a, $b) - для рядкових ключів перемагає правий, а числові ключі перенумеровує з нуля й дописує в кінець.
$defaults = ['color' => 'red', 'size' => 'M'];
$options  = ['color' => 'blue'];

$options + $defaults;            // ['color' => 'blue', 'size' => 'M']
array_merge($defaults, $options); // ['color' => 'blue', 'size' => 'M']

[0 => 'a', 1 => 'b'] + [0 => 'c', 1 => 'd', 2 => 'e']; // ['a', 'b', 'e']
array_merge(['a', 'b'], ['c', 'd', 'e']);              // ['a', 'b', 'c', 'd', 'e']

Коли що:

  • + - накласти значення за замовчуванням: «мої опції, а чого бракує - з дефолтів».
  • array_merge або spread [...$a, ...$b] - склеїти списки.

Пастка: числові ключі, які насправді ідентифікатори ([15 => 'Київ']), array_merge перенумерує й загубить. Для таких масивів потрібен + або array_replace.

Докладніше в документації: array_merge

Через usort і оператор <=> («космічний корабель»), який повертає -1, 0 або 1. Масиви теж можна порівнювати через <=> - поелементно, зліва направо, і це дає сортування за кількома полями одним рядком:

usort($users, fn (array $a, array $b): int =>
    [$b['score'], $a['name']] <=> [$a['score'], $b['name']]
);

Тут спершу за score за спаданням (тому $b і $a поміняні місцями), а за однакового рахунку - за name за зростанням.

Що ще варто знати:

  • usort скидає ключі. Щоб зберегти їх, потрібен uasort.
  • З PHP 8.0 сортування стабільне: рівні за критерієм елементи лишаються в початковому порядку. На цьому можна будувати сортування в кілька проходів.
  • Рядки <=> порівнює побайтово, тож «Ярослав» і «Андрій» стануть не за абеткою. Для тексту людською мовою - Collator з розширення intl.

У Laravel колекції роблять те саме читабельніше: collect($users)->sortBy([['score', 'desc'], ['name', 'asc']]).

Докладніше в документації: usort

PHP використовує бібліотеку PCRE: функції preg_* приймають шаблон з роздільниками (/.../, ~...~, #...#) і модифікаторами після них.

preg_match('/^\d{4}-\d{2}-\d{2}$/', $date);                  // 1, 0 або false при помилці
preg_match('/(?<year>\d{4})-(?<month>\d{2})/', $s, $m);      // $m['year'], $m['month']
preg_match_all('/#(\w+)/u', $text, $matches);               // усі хештеги
preg_replace('/\s+/', ' ', $text);                          // стиснути пробіли
preg_replace_callback('/\d+/', fn ($m) => $m[0] * 2, $s);
preg_split('/[,;]\s*/', $list);

Модифікатор u вмикає режим UTF-8:

  • шаблон і рядок обробляються як UTF-8, а не як байти;
  • . відповідає одному символу, а не байту;
  • працюють Unicode-класи: \p{L} (будь-яка літера), \p{Lu} (велика), \p{Cyrillic}.

Без u шаблон /^.{3}$/ для «кіт» не спрацює (там 6 байтів), а \w не вважає кирилицю літерами.

preg_match('/^[\p{L}\s\'-]+$/u', "Мар'яна Іваненко");   // 1

Інші корисні модифікатори: i - без урахування регістру, m - ^/$ для кожного рядка, s - . охоплює й переведення рядка, x - пробіли й коментарі в шаблоні для читабельності.

Пастки:

  • Помилка ≠ збіг: preg_match повертає false при помилці (зламаний UTF-8 у рядку з u, перевищення ліміту backtracking). Причина - у preg_last_error_msg().
  • Катастрофічний backtracking: шаблони на кшталт (a+)+$ на зловмисному введенні виконуються експоненційно довго (ReDoS). Уникайте вкладених квантифікаторів.
  • Екранування введення користувача в шаблоні - через preg_quote($input, '/').
  • Для простих випадків швидші й зрозуміліші str_contains, str_starts_with, str_replace.

Докладніше в документації: preg_match

Деструктуризація - розпакування масиву в окремі змінні (короткий синтаксис замість list()):

[$first, $second] = [10, 20];
[, $second] = [10, 20];                         // пропустити елемент
['id' => $id, 'name' => $name] = $user;         // за ключами
[$a, [$b, $c]] = [1, [2, 3]];                   // вкладене
[$a, $b] = [$b, $a];                            // обмін значеннями

foreach ($rows as ['id' => $id, 'email' => $email]) {
    // ...
}

Якщо ключа немає, змінна отримає null з попередженням.

Spread-оператор ... розпаковує масив у місці виклику чи в літералі масиву:

$all = [...$defaults, ...$overrides];   // склеїти масиви
$merged = [...['a' => 1], ...['a' => 2, 'b' => 3]];   // ['a' => 2, 'b' => 3] (PHP 8.1+)

function sum(int ...$numbers): int        // збір аргументів (variadic)
{
    return array_sum($numbers);
}

sum(...[1, 2, 3]);                        // передача масиву як аргументів

Що варто знати:

  • Рядкові ключі в spread дозволені з PHP 8.1 і поводяться як array_merge: пізніший ключ перемагає. Числові ключі перенумеровуються.
  • Spread ітератора: [...$generator] працює з будь-яким Traversable.
  • Іменовані аргументи зі spread: create(...['name' => 'Оля', 'email' => '...']) (PHP 8.1+) - ключі масиву стають іменами параметрів.
  • Не для великих масивів у циклі: $result = [...$result, $item] у циклі щоразу копіює весь масив - квадратична складність. Для накопичення - $result[] = $item.

Докладніше в документації: Деструктуризація масивів