PHP: Middle
20 питань · ~18 хв · Версія v3.0
Увійдіть, щоб продовжити
ООП, traits, замикання, генератори, винятки, SPL.
- За спробу
- 20
- У пулі
- 100
- Проходжень
- 41
- Середній бал
- 84%
- Пройшли на 70%+
- 78%
Питання для підготовки
35 питаньДва способи оголосити глобальну константу:
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.
Backed enum кастується в моделі, і колонка починає повертати обʼєкт замість рядка:
enum VacancyLevel: string
{
case Junior = 'junior';
case Middle = 'middle';
case Senior = 'senior';
public function label(): string
{
return match ($this) {
self::Junior => 'Junior',
self::Middle => 'Middle',
self::Senior => 'Senior',
};
}
}
class Vacancy extends Model
{
protected function casts(): array
{
return ['level' => VacancyLevel::class];
}
}
Тепер $vacancy->level - це enum, а не рядок:
$vacancy->level->label();
$vacancy->level === VacancyLevel::Senior;
$vacancy->update(['level' => VacancyLevel::Middle]); // у базу піде 'middle'
Переваги над константами класу:
- Обмежена множина. Значення поза переліком не існує, тоді як константа не заважає передати будь-який рядок.
- Типізація.
function assign(VacancyLevel $level)не прийме випадковий рядок, і редактор підкаже варіанти. - Поведінка поруч зі значенням. Enum має методи, тож підпис, колір чи іконка живуть там само, а не в розкиданих
matchпо шаблонах. matchбезdefaultпідсвітить пропущений випадок, коли додасте новий кейс.
Дві практичні деталі. У валідації є правило Rule::enum(VacancyLevel::class). А tryFrom() повертає null замість винятку - саме він потрібен, коли значення приходить від користувача чи з URL.
Трейт - це шматок коду (методи, властивості), який підставляється в клас так, ніби його написали там. Механізм повторного використання без успадкування: клас може взяти кілька трейтів.
trait HasSlug
{
public function slug(): string
{
return Str::slug($this->title);
}
}
class Post
{
use HasSlug;
}
Підводні камені:
- Неявні залежності. Трейт вище звертається до
$this->title, але ніде цього не оголошує. Клас безtitleзламається лише під час виконання. Частково рятує абстрактний метод у трейті:abstract public function title(): string;. - Конфлікти імен. Два трейти з однаковим методом - фатальна помилка, доки не розв'язати її через
insteadofіas. - Трейт - не тип. Не можна написати
function f(HasSlug $x): перевірити, що об'єкт має поведінку трейту, можна лише через інтерфейс. - Прихована складність. Клас із п'ятьма трейтами важко читати: щоб зрозуміти, що в ньому є, треба відкрити шість файлів.
Добре працюють трейти для вузької допоміжної поведінки, як SoftDeletes чи HasFactory у Laravel. Погано - як спосіб розкидати велику модель по файлах: складність лишається, вона просто гірше видна.
Обидва об'єднують масиви, але по-різному поводяться з однаковими ключами:
$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.
Через 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']]).
Спершу почитати
Прочитати - ще не значить знати
20 питань, по одному на екран, ~18 хв. Після завершення - розбір кожної помилки з посиланням на питання.