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

Middle: питання на співбесіді з теми «Основи мови»

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

4 питання

Два способи оголосити глобальну константу:

const MAX_UPLOAD_MB = 20;              // на етапі компіляції
define('MAX_UPLOAD_MB', 20);           // під час виконання, звичайна функція
const define()
коли визначається під час компіляції файлу під час виконання
усередині if, циклу, функції ні так
ім'я з виразу ні так: define("LIMIT_{$plan}", 10)
у класах і інтерфейсах так ні
простір імен враховує поточний namespace ім'я задається повністю рядком

На практиці в сучасному коді define() майже не потрібен: глобальні константи взагалі краще замінити конфігурацією чи константами класу.

Константи класу - значення, що належать класу й не змінюються:

final class Invoice
{
    public const int PAYMENT_TERM_DAYS = 14;
    protected const string NUMBER_PREFIX = 'INV-';

    public function dueDate(): CarbonImmutable
    {
        return $this->issued_at->addDays(self::PAYMENT_TERM_DAYS);
    }
}
  • видимість (public, protected, private) з PHP 7.1;
  • типізовані константи (const int) з PHP 8.3 - нащадок не може перевизначити константу значенням іншого типу;
  • final const (PHP 8.1) - заборонити перевизначення в нащадках;
  • static::CONST замість self::CONST - взяти значення з класу-нащадка (пізнє статичне зв'язування).

Магічні значення - числа й рядки без пояснення посеред коду:

if ($user->status === 3 && $order->total > 50000) { ... }   // що таке 3? чому 50000?

Замінювати їх варто так:

  • набір пов'язаних варіантів (статуси, типи, ролі) - enum, а не група констант: тип перевіряється, варіанти можна перебрати, у них можуть бути методи;
  • одиночне незмінне значення предметної області (термін оплати, максимальна довжина) - константа класу, де воно використовується;
  • значення, що відрізняється між середовищами чи може змінитися без деплою (ліміти, ключі, адреси) - конфігурація (config('billing.limit')), а не константа.

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

Динамічний доступ: constant(Invoice::class . '::PAYMENT_TERM_DAYS') чи з PHP 8.3 Invoice::{$name} - корисно в рідкісних випадках, але ламає пошук використань в IDE.

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

Простір імен - префікс до назв класів, функцій і констант, щоб різні бібліотеки могли мати класи з однаковими іменами: App\Models\User і Laravel\Socialite\Two\User - різні класи.

namespace App\Services\Billing;

use App\Models\Order;
use Illuminate\Support\Facades\Log;
use Stripe\Invoice as StripeInvoice;   // псевдонім, щоб не конфліктувати з нашим Invoice

final class Invoice
{
    public function sync(Order $order, StripeInvoice $remote): void { /* ... */ }
}

use не підключає файл і нічого не завантажує - лише створює коротке ім'я для повного. Клас завантажить автозавантажувач у момент першого використання.

Як PHP розв'язує ім'я класу:

  • Повне ім'я з \ на початку - як є: \DateTime.
  • Ім'я з use - підставляється повне.
  • Інакше - до імені додається поточний простір імен: new DateTime() у App\Services шукатиме App\Services\DateTime. Звідси класична помилка «Class App\Services\DateTime not found» - потрібен use DateTime; чи \DateTime.

Функції й константи розв'язуються інакше: якщо в поточному просторі функції немає, PHP шукає глобальну (strlen працює без \). Явний \strlen() чи use function strlen; трохи пришвидшує виклик, бо рушій не перевіряє простір імен, і дозволяє OPcache оптимізувати вбудовані функції.

Простори імен і PSR-4: простір імен відповідає каталогу (App\Services\Billing → app/Services/Billing), а ім'я класу - файлу. На цьому збігу тримається автозавантаження.

::class дає повне ім'я класу рядком: Invoice::class → 'App\Services\Billing\Invoice' - так посилаються на класи в конфігурації без помилок у рядках.

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

Одинарні лапки - рядок «як є». Змінні не підставляються, з екранувань працюють лише \' і \\.

'Привіт, $name\n';   // буквально: Привіт, $name\n

Подвійні лапки - з інтерполяцією змінних і екрануваннями (\n, \t, \u{1F600}, \$):

"Привіт, $name\n";
"Сума: {$order->total} грн";          // складні вирази - у фігурних дужках
"Перший: {$items[0]['title']}";

Heredoc - багаторядковий рядок з інтерполяцією, як подвійні лапки:

$html = <<<HTML
    <p>Привіт, {$user->name}</p>
    <p>Замовлень: {$count}</p>
    HTML;

Nowdoc - багаторядковий без інтерполяції, як одинарні лапки (мітка в лапках):

$template = <<<'SQL'
    SELECT * FROM users WHERE email = :email
    SQL;

З PHP 7.3 закривальну мітку можна відступати - відступ прибирається з усіх рядків, що зручно для коду всередині класів.

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

  • Інтерполяція ${name} застаріла з PHP 8.2 - лише {$name}.
  • Швидкодія однакова: історична порада «одинарні лапки швидші» сьогодні не має значення - рядки компілюються один раз і кешуються в OPcache.
  • Виклики методів і функцій у рядку не працюють без змінної: "{$user->name()}" - так, "{strtoupper($name)}" - ні.
  • Nowdoc зручний для SQL, шаблонів і регулярних виразів, де $ має бути буквальним.
  • Інтерполяція - не для HTML і SQL з даними користувача: екранування (htmlspecialchars) і прив'язка параметрів обов'язкові незалежно від способу складання рядка.

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

Суперглобальні змінні доступні в будь-якому місці коду без global:

  • $_GET, $_POST - параметри рядка запиту й тіла форми;
  • $_COOKIE, $_FILES, $_REQUEST (суміш перших трьох);
  • $_SERVER - заголовки, метод, шлях, IP, змінні середовища сервера;
  • $_SESSION, $_ENV, $GLOBALS.

Чому не брати дані напряму:

  • Усе - від користувача і все - рядки чи масиви. $_GET['id'] може бути '5', '5 OR 1=1', масивом ['x'] (?id[]=x) чи взагалі відсутнім. Без валідації це шлях до SQL-ін'єкцій, XSS і помилок типів.
  • Глобальний стан. Код, що читає $_POST у глибині сервісу, неможливо протестувати без підробки глобальних змінних і неможливо перевикористати в консольній команді чи черзі.
  • $_REQUEST змішує джерела, і порядок пріоритету залежить від налаштувань (request_order) - незрозуміло, звідки прийшло значення.
  • $_SERVER теж від клієнта: HTTP_HOST, HTTP_X_FORWARDED_FOR, будь-які HTTP_* - це заголовки, які можна підробити.

У фреймворку - об'єкт запиту і валідація:

public function update(Request $request, Post $post)
{
    $data = $request->validate([
        'title' => ['required', 'string', 'max:200'],
        'published' => ['boolean'],
    ]);

    $post->update($data);
}

Запит передається явно (його легко підробити в тесті), дані проходять перевірку, а $request->ip() враховує налаштування довірених проксі замість сирого заголовка.

У довгоживучих серверах (Octane) суперглобальні змінні й зовсім ненадійні: фреймворк сам формує об'єкт запиту для кожного запиту.

Докладніше в документації: Суперглобальні змінні