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) суперглобальні змінні й зовсім ненадійні: фреймворк сам формує об'єкт запиту для кожного запиту.