Middle: питання на співбесіді з теми «ООП»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
4 питання
Трейт - це шматок коду (методи, властивості), який підставляється в клас так, ніби його написали там. Механізм повторного використання без успадкування: клас може взяти кілька трейтів.
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. Погано - як спосіб розкидати велику модель по файлах: складність лишається, вона просто гірше видна.
self:: завжди вказує на клас, у якому написано код. static:: - на клас, через який метод викликали насправді. Друге називають пізнім статичним зв'язуванням.
class Model
{
public static function create(): static
{
return new static(); // клас виклику
}
public static function make(): self
{
return new self(); // завжди Model
}
}
class User extends Model {}
User::create(); // User
User::make(); // Model
Де це важливо:
- Фабричні методи в базовому класі. Eloquent
User::create()повертає самеUser, бо всерединіnew static. - Перевизначені константи й методи.
static::TABLEвізьме константу нащадка,self::TABLE- базового класу. - Тип повернення
static(PHP 8.0) каже аналізатору й IDE, що метод повертає клас виклику - зручно для fluent-інтерфейсів.
Коли self: коли поведінка не має змінюватися в нащадках, наприклад виклик приватного методу. static:: до приватного методу нащадка не дістанеться.
Магічні методи - методи з подвійним підкресленням, які PHP викликає сам у певних ситуаціях:
__construct,__destruct- створення й знищення об'єкта;__get,__set,__isset,__unset- звернення до недоступної (неіснуючої чи приватної) властивості;__call,__callStatic- виклик недоступного методу;__toString- перетворення на рядок;__invoke- виклик об'єкта як функції:$validator($value);__clone- післяclone;__serialize,__unserialize- серіалізація;__debugInfo- що показувати уvar_dump().
__get і __call - основа «магії» Eloquent: $user->email читає атрибут з масиву, $user->posts() - зв'язок, User::where(...) через __callStatic перенаправляється в query builder.
Чим вони небезпечні:
- Опечатки не ловляться.
$user->emialне дасть помилки компіляції - лишеnullчи виняток під час виконання. - IDE й статичний аналіз сліпі. Автодоповнення, «перейти до визначення», перевірка типів - не працюють без додаткових підказок (
@property,@methodу PHPDoc, ide-helper, Larastan). - Складно налагоджувати: незрозуміло, звідки береться значення.
- Продуктивність: магічний виклик повільніший за прямий (рідко має значення, але в гарячих циклах помітно).
Правило: у власному коді віддавати перевагу явним методам і властивостям. Магію - лише там, де вона дає великий виграш у зручності API (як ORM), і з PHPDoc-підказками для інструментів.
final class- від класу не можна успадкуватися.final public function- метод не можна перевизначити в нащадку.final const(PHP 8.1) - константу не можна перевизначити.
final class InvoiceNumberGenerator
{
public function next(): string { /* ... */ }
}
class Report extends InvoiceNumberGenerator {} // Fatal error
Чому final за замовчуванням:
- Успадкування - найсильніший зв'язок. Нащадок залежить від усіх деталей предка. Будь-яка зміна в класі може зламати чужого нащадка, про якого автор навіть не знає.
- Свобода змінювати клас. Якщо клас
final, його внутрішню будову (protected-методи, порядок викликів) можна змінювати вільно: зовні доступний лише публічний API. - Свідомі рішення. Прибрати
final, коли з'явилася реальна потреба в успадкуванні, - одна правка. Повернутиfinal, коли на клас уже успадковуються, - ламаюча зміна. - Підштовхує до композиції й інтерфейсів замість ієрархій.
Типове заперечення - «а як мокати в тестах?». Мокати слід інтерфейси, а не конкретні класи. Якщо клас треба підмінити, - він реалізує інтерфейс, і тест підставляє іншу реалізацію. Для зовнішніх final-класів бібліотек є обхідні шляхи (пакет dg/bypass-finals), але це сигнал щодо дизайну.
Де final не ставлять: базові класи, призначені для успадкування (Model, Controller, абстрактні класи), і класи, які фреймворк проксує чи розширює під час виконання (наприклад, моделі Doctrine з лінивими проксі).